fix(cli): handle migration history entries without file extensions correctly
...even when the migration file names include periods in their names.
This commit is contained in:
parent
ea327bbc49
commit
b56b6daf73
5 changed files with 168 additions and 61 deletions
99
packages/cli/src/collect-migrations.test.ts
Normal file
99
packages/cli/src/collect-migrations.test.ts
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert';
|
||||
import { collectMigrations } from './collect-migrations.js';
|
||||
import { toEntries, toEntry, toMigration, toMigrations } from './test-utils.js';
|
||||
import { arrayFromAsync } from './array-from-async.js';
|
||||
import { MigrationHistoryError } from './errors.js';
|
||||
|
||||
describe('collect-migrations', () => {
|
||||
it('returns all migrations from the history and all pending migrations', async () => {
|
||||
const cwd = '/cwd';
|
||||
const directory = 'directory';
|
||||
const history = {
|
||||
async *[Symbol.asyncIterator]() {
|
||||
yield* toEntries(['migration1.js', 'migration2.js']);
|
||||
},
|
||||
};
|
||||
const getMigrations = async () => toMigrations(cwd, directory, ['migration1.js', 'migration2.js', 'migration3.js']);
|
||||
|
||||
const result = await arrayFromAsync(collectMigrations(cwd, directory, history, getMigrations));
|
||||
|
||||
assert.deepStrictEqual(result, [
|
||||
{
|
||||
...toMigration(cwd, directory, 'migration1.js'),
|
||||
duration: 0,
|
||||
status: 'done',
|
||||
},
|
||||
{
|
||||
...toMigration(cwd, directory, 'migration2.js'),
|
||||
duration: 0,
|
||||
status: 'done',
|
||||
},
|
||||
toMigration(cwd, directory, 'migration3.js'),
|
||||
]);
|
||||
});
|
||||
|
||||
it('includes any errors from the history', async () => {
|
||||
const entry = toEntry('migration1.js', 'failed');
|
||||
const cwd = '/cwd';
|
||||
const directory = 'directory';
|
||||
const history = {
|
||||
async *[Symbol.asyncIterator]() {
|
||||
yield* [entry];
|
||||
},
|
||||
};
|
||||
const getMigrations = async () => toMigrations(cwd, directory, ['migration1.js', 'migration2.js', 'migration3.js']);
|
||||
|
||||
const result = await arrayFromAsync(collectMigrations(cwd, directory, history, getMigrations));
|
||||
|
||||
assert.deepStrictEqual(result, [
|
||||
{
|
||||
...toMigration(cwd, directory, 'migration1.js'),
|
||||
duration: 0,
|
||||
status: 'failed',
|
||||
error: MigrationHistoryError.fromHistoryEntry(entry),
|
||||
},
|
||||
toMigration(cwd, directory, 'migration2.js'),
|
||||
toMigration(cwd, directory, 'migration3.js'),
|
||||
]);
|
||||
});
|
||||
|
||||
it('can handle a migration history without file extensions', async () => {
|
||||
const cwd = '/cwd';
|
||||
const directory = 'directory';
|
||||
const history = {
|
||||
async *[Symbol.asyncIterator]() {
|
||||
yield* toEntries(['migration1']);
|
||||
},
|
||||
};
|
||||
const getMigrations = async () => toMigrations(cwd, directory, ['migration1.js', 'migration2.js', 'migration3.js']);
|
||||
|
||||
const result = await arrayFromAsync(collectMigrations(cwd, directory, history, getMigrations));
|
||||
|
||||
assert.deepStrictEqual(result, [
|
||||
{ ...toMigration(cwd, directory, 'migration1.js'), duration: 0, status: 'done' },
|
||||
toMigration(cwd, directory, 'migration2.js'),
|
||||
toMigration(cwd, directory, 'migration3.js'),
|
||||
]);
|
||||
});
|
||||
|
||||
it('can handle a migration history without file extensions even if the migration name contains periods', async () => {
|
||||
const cwd = '/cwd';
|
||||
const directory = 'directory';
|
||||
const history = {
|
||||
async *[Symbol.asyncIterator]() {
|
||||
yield* toEntries(['mig.ration1']);
|
||||
},
|
||||
};
|
||||
const getMigrations = async () =>
|
||||
toMigrations(cwd, directory, ['mig.ration1.js', 'migration2.js', 'migration3.js']);
|
||||
|
||||
const result = await arrayFromAsync(collectMigrations(cwd, directory, history, getMigrations));
|
||||
|
||||
assert.deepStrictEqual(result, [
|
||||
{ ...toMigration(cwd, directory, 'mig.ration1.js'), duration: 0, status: 'done' },
|
||||
toMigration(cwd, directory, 'migration2.js'),
|
||||
toMigration(cwd, directory, 'migration3.js'),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
import { extname } from 'node:path';
|
||||
import { type MigrationHistoryEntry, type MigrationMetadata, type MigrationMetadataFinished } from '@emigrate/types';
|
||||
import { toMigrationMetadata } from './to-migration-metadata.js';
|
||||
import { getMigrations as getMigrationsOriginal } from './get-migrations.js';
|
||||
|
|
@ -12,18 +11,18 @@ export async function* collectMigrations(
|
|||
const allMigrations = await getMigrations(cwd, directory);
|
||||
const seen = new Set<string>();
|
||||
|
||||
for await (const entry_ of history) {
|
||||
const entry = extname(entry_.name) === '' ? { ...entry_, name: `${entry_.name}.js` } : entry_;
|
||||
for await (const entry of history) {
|
||||
const migration = allMigrations.find((migrationFile) => {
|
||||
return migrationFile.name === entry.name || migrationFile.name === `${entry.name}.js`;
|
||||
});
|
||||
|
||||
const index = allMigrations.findIndex((migrationFile) => migrationFile.name === entry.name);
|
||||
|
||||
if (index === -1) {
|
||||
if (!migration) {
|
||||
continue;
|
||||
}
|
||||
|
||||
yield toMigrationMetadata(entry, { cwd, directory });
|
||||
yield toMigrationMetadata({ ...entry, name: migration.name }, { cwd, directory });
|
||||
|
||||
seen.add(entry.name);
|
||||
seen.add(migration.name);
|
||||
}
|
||||
|
||||
yield* allMigrations.filter((migration) => !seen.has(migration.name));
|
||||
|
|
|
|||
|
|
@ -1,15 +1,11 @@
|
|||
import { describe, it, mock, type Mock } from 'node:test';
|
||||
import assert from 'node:assert';
|
||||
import path from 'node:path';
|
||||
import {
|
||||
type EmigrateReporter,
|
||||
type MigrationHistoryEntry,
|
||||
type MigrationMetadata,
|
||||
type Storage,
|
||||
type Plugin,
|
||||
type SerializedError,
|
||||
type FailedMigrationHistoryEntry,
|
||||
type NonFailedMigrationHistoryEntry,
|
||||
type MigrationMetadataFinished,
|
||||
} from '@emigrate/types';
|
||||
import { deserializeError, serializeError } from 'serialize-error';
|
||||
|
|
@ -22,6 +18,7 @@ import {
|
|||
MigrationRunError,
|
||||
StorageInitError,
|
||||
} from '../errors.js';
|
||||
import { toEntries, toEntry, toMigrations } from '../test-utils.js';
|
||||
import upCommand from './up.js';
|
||||
|
||||
type Mocked<T> = {
|
||||
|
|
@ -619,55 +616,6 @@ function getErrorCause(error: Error | undefined): Error | SerializedError | unde
|
|||
return undefined;
|
||||
}
|
||||
|
||||
function toMigration(cwd: string, directory: string, name: string): MigrationMetadata {
|
||||
return {
|
||||
name,
|
||||
filePath: `${cwd}/${directory}/${name}`,
|
||||
relativeFilePath: `${directory}/${name}`,
|
||||
extension: path.extname(name),
|
||||
directory,
|
||||
cwd,
|
||||
};
|
||||
}
|
||||
|
||||
function toMigrations(cwd: string, directory: string, names: string[]): MigrationMetadata[] {
|
||||
return names.map((name) => toMigration(cwd, directory, name));
|
||||
}
|
||||
|
||||
function toEntry(name: MigrationHistoryEntry): MigrationHistoryEntry;
|
||||
function toEntry<S extends MigrationHistoryEntry['status']>(
|
||||
name: string,
|
||||
status?: S,
|
||||
): S extends 'failed' ? FailedMigrationHistoryEntry : NonFailedMigrationHistoryEntry;
|
||||
|
||||
function toEntry(name: string | MigrationHistoryEntry, status?: 'done' | 'failed'): MigrationHistoryEntry {
|
||||
if (typeof name !== 'string') {
|
||||
return name.status === 'failed' ? name : name;
|
||||
}
|
||||
|
||||
if (status === 'failed') {
|
||||
return {
|
||||
name,
|
||||
status,
|
||||
date: new Date(),
|
||||
error: { name: 'Error', message: 'Failed' },
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
status: status ?? 'done',
|
||||
date: new Date(),
|
||||
};
|
||||
}
|
||||
|
||||
function toEntries(
|
||||
names: Array<string | MigrationHistoryEntry>,
|
||||
status?: MigrationHistoryEntry['status'],
|
||||
): MigrationHistoryEntry[] {
|
||||
return names.map((name) => (typeof name === 'string' ? toEntry(name, status) : name));
|
||||
}
|
||||
|
||||
async function noop() {
|
||||
// noop
|
||||
}
|
||||
|
|
|
|||
56
packages/cli/src/test-utils.ts
Normal file
56
packages/cli/src/test-utils.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import path from 'node:path';
|
||||
import {
|
||||
type FailedMigrationHistoryEntry,
|
||||
type MigrationHistoryEntry,
|
||||
type MigrationMetadata,
|
||||
type NonFailedMigrationHistoryEntry,
|
||||
} from '@emigrate/types';
|
||||
|
||||
export function toMigration(cwd: string, directory: string, name: string): MigrationMetadata {
|
||||
return {
|
||||
name,
|
||||
filePath: `${cwd}/${directory}/${name}`,
|
||||
relativeFilePath: `${directory}/${name}`,
|
||||
extension: path.extname(name),
|
||||
directory,
|
||||
cwd,
|
||||
};
|
||||
}
|
||||
|
||||
export function toMigrations(cwd: string, directory: string, names: string[]): MigrationMetadata[] {
|
||||
return names.map((name) => toMigration(cwd, directory, name));
|
||||
}
|
||||
|
||||
export function toEntry(name: MigrationHistoryEntry): MigrationHistoryEntry;
|
||||
export function toEntry<S extends MigrationHistoryEntry['status']>(
|
||||
name: string,
|
||||
status?: S,
|
||||
): S extends 'failed' ? FailedMigrationHistoryEntry : NonFailedMigrationHistoryEntry;
|
||||
|
||||
export function toEntry(name: string | MigrationHistoryEntry, status?: 'done' | 'failed'): MigrationHistoryEntry {
|
||||
if (typeof name !== 'string') {
|
||||
return name.status === 'failed' ? name : name;
|
||||
}
|
||||
|
||||
if (status === 'failed') {
|
||||
return {
|
||||
name,
|
||||
status,
|
||||
date: new Date(),
|
||||
error: { name: 'Error', message: 'Failed' },
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
name,
|
||||
status: status ?? 'done',
|
||||
date: new Date(),
|
||||
};
|
||||
}
|
||||
|
||||
export function toEntries(
|
||||
names: Array<string | MigrationHistoryEntry>,
|
||||
status?: MigrationHistoryEntry['status'],
|
||||
): MigrationHistoryEntry[] {
|
||||
return names.map((name) => (typeof name === 'string' ? toEntry(name, status) : name));
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue