feat(reporters): add built-in "json" reporter and rename "default" to "pretty"

This commit is contained in:
Joakim Carlstein 2024-02-06 09:15:16 +01:00 committed by Joakim Carlstein
parent 4e8ac5294d
commit 18382ce961
11 changed files with 102 additions and 16 deletions

View file

@ -0,0 +1,5 @@
---
'@emigrate/cli': minor
---
Add a built-in "json" reporter for outputting a single JSON object

View file

@ -0,0 +1,5 @@
---
'@emigrate/cli': minor
---
Rename the "default" reporter to "pretty" and make it possible to specify it using the `--reporter` CLI option or in the configuration file

View file

@ -104,7 +104,7 @@ Options:
-p, --plugin <name> The plugin(s) to use (can be specified multiple times) -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 -r, --reporter <name> The reporter to use for reporting the migration progress (default: pretty)
-l, --limit <count> Limit the number of migrations to run -l, --limit <count> Limit the number of migrations to run
@ -261,7 +261,7 @@ Options:
-h, --help Show this help message and exit -h, --help Show this help message and exit
-d, --directory <path> The directory where the migration files are located (required) -d, --directory <path> The directory where the migration files are located (required)
-r, --reporter <name> The reporter to use for reporting the migration file creation progress -r, --reporter <name> The reporter to use for reporting the migration file creation progress (default: pretty)
-p, --plugin <name> The plugin(s) to use (can be specified multiple times) -p, --plugin <name> The plugin(s) to use (can be specified multiple times)
-t, --template <path> A template file to use as contents for the new migration file -t, --template <path> A template file to use as contents for the new migration file
(if the extension option is not provided the template file's extension will be used) (if the extension option is not provided the template file's extension will be used)
@ -358,7 +358,7 @@ Options:
-d, --directory <path> The directory where the migration files are located (required) -d, --directory <path> The directory where the migration files are located (required)
-i, --import <module> Additional modules/packages to import before listing the migrations (can be specified multiple times) -i, --import <module> Additional modules/packages to import before listing the migrations (can be specified multiple times)
For example if you want to use Dotenv to load environment variables For example if you want to use Dotenv to load environment variables
-r, --reporter <name> The reporter to use for reporting the migrations -r, --reporter <name> The reporter to use for reporting the migrations (default: pretty)
-s, --storage <name> The storage to use to get the migration history (required) -s, --storage <name> The storage to use to get the migration history (required)
--color Force color output (this option is passed to the reporter) --color Force color output (this option is passed to the reporter)
--no-color Disable color output (this option is passed to the reporter) --no-color Disable color output (this option is passed to the reporter)
@ -456,7 +456,7 @@ Options:
-d, --directory <path> The directory where the migration files are located (required) -d, --directory <path> The directory where the migration files are located (required)
-i, --import <module> Additional modules/packages to import before removing the migration (can be specified multiple times) -i, --import <module> Additional modules/packages to import before removing the migration (can be specified multiple times)
For example if you want to use Dotenv to load environment variables For example if you want to use Dotenv to load environment variables
-r, --reporter <name> The reporter to use for reporting the removal process -r, --reporter <name> The reporter to use for reporting the removal process (default: pretty)
-s, --storage <name> The storage to use to get the migration history (required) -s, --storage <name> The storage to use to get the migration history (required)
-f, --force Force removal of the migration history entry even if the migration is not in a failed state -f, --force Force removal of the migration history entry even if the migration is not in a failed state
--color Force color output (this option is passed to the reporter) --color Force color output (this option is passed to the reporter)

View file

@ -5,8 +5,7 @@ import { exec } from '../exec.js';
import { migrationRunner } from '../migration-runner.js'; import { migrationRunner } from '../migration-runner.js';
import { collectMigrations } from '../collect-migrations.js'; import { collectMigrations } from '../collect-migrations.js';
import { version } from '../get-package-info.js'; import { version } from '../get-package-info.js';
import { getStandardReporter } from '../reporters/get.js';
const lazyDefaultReporter = async () => import('../reporters/default.js');
type ExtraFlags = { type ExtraFlags = {
cwd: string; cwd: string;
@ -29,7 +28,7 @@ export default async function listCommand({
throw BadOptionError.fromOption('storage', 'No storage found, please specify a storage using the storage option'); throw BadOptionError.fromOption('storage', 'No storage found, please specify a storage using the storage option');
} }
const reporter = await getOrLoadReporter([reporterConfig ?? lazyDefaultReporter]); const reporter = getStandardReporter(reporterConfig) ?? (await getOrLoadReporter([reporterConfig]));
if (!reporter) { if (!reporter) {
throw BadOptionError.fromOption( throw BadOptionError.fromOption(

View file

@ -15,8 +15,7 @@ import { type Config } from '../types.js';
import { withLeadingPeriod } from '../with-leading-period.js'; import { withLeadingPeriod } from '../with-leading-period.js';
import { version } from '../get-package-info.js'; import { version } from '../get-package-info.js';
import { getDuration } from '../get-duration.js'; import { getDuration } from '../get-duration.js';
import { getStandardReporter } from '../reporters/get.js';
const lazyDefaultReporter = async () => import('../reporters/default.js');
type ExtraFlags = { type ExtraFlags = {
cwd: string; cwd: string;
@ -38,7 +37,7 @@ export default async function newCommand(
throw MissingOptionError.fromOption(['extension', 'template', 'plugin']); throw MissingOptionError.fromOption(['extension', 'template', 'plugin']);
} }
const reporter = await getOrLoadReporter([reporterConfig ?? lazyDefaultReporter]); const reporter = getStandardReporter(reporterConfig) ?? (await getOrLoadReporter([reporterConfig]));
if (!reporter) { if (!reporter) {
throw BadOptionError.fromOption( throw BadOptionError.fromOption(

View file

@ -18,6 +18,7 @@ import { collectMigrations } from '../collect-migrations.js';
import { migrationRunner } from '../migration-runner.js'; import { migrationRunner } from '../migration-runner.js';
import { arrayMapAsync } from '../array-map-async.js'; import { arrayMapAsync } from '../array-map-async.js';
import { type GetMigrationsFunction } from '../get-migrations.js'; import { type GetMigrationsFunction } from '../get-migrations.js';
import { getStandardReporter } from '../reporters/get.js';
type ExtraFlags = { type ExtraFlags = {
cwd: string; cwd: string;
@ -27,8 +28,6 @@ type ExtraFlags = {
type RemovableMigrationMetadata = MigrationMetadata & { originalStatus?: 'done' | 'failed' }; type RemovableMigrationMetadata = MigrationMetadata & { originalStatus?: 'done' | 'failed' };
const lazyDefaultReporter = async () => import('../reporters/default.js');
export default async function removeCommand( export default async function removeCommand(
{ {
directory, directory,
@ -55,7 +54,7 @@ export default async function removeCommand(
throw BadOptionError.fromOption('storage', 'No storage found, please specify a storage using the storage option'); throw BadOptionError.fromOption('storage', 'No storage found, please specify a storage using the storage option');
} }
const reporter = await getOrLoadReporter([reporterConfig ?? lazyDefaultReporter]); const reporter = getStandardReporter(reporterConfig) ?? (await getOrLoadReporter([reporterConfig]));
if (!reporter) { if (!reporter) {
throw BadOptionError.fromOption( throw BadOptionError.fromOption(

View file

@ -16,6 +16,7 @@ import { exec } from '../exec.js';
import { migrationRunner } from '../migration-runner.js'; import { migrationRunner } from '../migration-runner.js';
import { collectMigrations } from '../collect-migrations.js'; import { collectMigrations } from '../collect-migrations.js';
import { version } from '../get-package-info.js'; import { version } from '../get-package-info.js';
import { getStandardReporter } from '../reporters/get.js';
type ExtraFlags = { type ExtraFlags = {
cwd: string; cwd: string;
@ -29,7 +30,6 @@ type ExtraFlags = {
abortRespite?: number; abortRespite?: number;
}; };
const lazyDefaultReporter = async () => import('../reporters/default.js');
const lazyPluginLoaderJs = async () => import('../plugin-loader-js.js'); const lazyPluginLoaderJs = async () => import('../plugin-loader-js.js');
export default async function upCommand({ export default async function upCommand({
@ -58,7 +58,7 @@ export default async function upCommand({
throw BadOptionError.fromOption('storage', 'No storage found, please specify a storage using the storage option'); throw BadOptionError.fromOption('storage', 'No storage found, please specify a storage using the storage option');
} }
const reporter = await getOrLoadReporter([reporterConfig ?? lazyDefaultReporter]); const reporter = getStandardReporter(reporterConfig) ?? (await getOrLoadReporter([reporterConfig]));
if (!reporter) { if (!reporter) {
throw BadOptionError.fromOption( throw BadOptionError.fromOption(

View file

@ -0,0 +1,14 @@
import { type Config } from '../types.js';
import * as reporters from './index.js';
export const getStandardReporter = (reporter?: Config['reporter']) => {
if (!reporter) {
return reporters.pretty;
}
if (typeof reporter === 'string' && reporter in reporters) {
return reporters[reporter as keyof typeof reporters];
}
return; // eslint-disable-line no-useless-return
};

View file

@ -0,0 +1,2 @@
export { default as pretty } from './default.js';
export { default as json } from './json.js';

View file

@ -0,0 +1,60 @@
import { type ReporterInitParameters, type EmigrateReporter, type MigrationMetadataFinished } from '@emigrate/types';
import { toSerializedError } from '../errors.js';
class JsonReporter implements EmigrateReporter {
#parameters!: ReporterInitParameters;
#startTime!: number;
onInit(parameters: ReporterInitParameters): void {
this.#startTime = Date.now();
this.#parameters = parameters;
}
onFinished(migrations: MigrationMetadataFinished[], error?: Error | undefined): void {
const { command, version } = this.#parameters;
let numberDoneMigrations = 0;
let numberSkippedMigrations = 0;
let numberFailedMigrations = 0;
let numberPendingMigrations = 0;
for (const migration of migrations) {
// eslint-disable-next-line unicorn/prefer-switch
if (migration.status === 'done') {
numberDoneMigrations++;
} else if (migration.status === 'skipped') {
numberSkippedMigrations++;
} else if (migration.status === 'failed') {
numberFailedMigrations++;
} else {
numberPendingMigrations++;
}
}
const result = {
command,
version,
numberTotalMigrations: migrations.length,
numberDoneMigrations,
numberSkippedMigrations,
numberFailedMigrations,
numberPendingMigrations,
success: !error,
startTime: this.#startTime,
endTime: Date.now(),
error: error ? toSerializedError(error) : undefined,
migrations: migrations.map((migration) => ({
name: migration.filePath,
status: migration.status,
duration: 'duration' in migration ? migration.duration : 0,
error: 'error' in migration ? toSerializedError(migration.error) : undefined,
})),
};
console.log(JSON.stringify(result, undefined, 2));
}
}
const jsonReporter = new JsonReporter() as EmigrateReporter;
export default jsonReporter;

View file

@ -1,4 +1,7 @@
import { type EmigrateStorage, type Awaitable, type Plugin, type EmigrateReporter } from '@emigrate/types'; import { type EmigrateStorage, type Awaitable, type Plugin, type EmigrateReporter } from '@emigrate/types';
import type * as reporters from './reporters/index.js';
export type StandardReporter = keyof typeof reporters;
export type EmigratePlugin = Plugin; export type EmigratePlugin = Plugin;
@ -6,7 +9,7 @@ type StringOrModule<T> = string | T | (() => Awaitable<T>) | (() => Awaitable<{
export type Config = { export type Config = {
storage?: StringOrModule<EmigrateStorage>; storage?: StringOrModule<EmigrateStorage>;
reporter?: StringOrModule<EmigrateReporter>; reporter?: StandardReporter | StringOrModule<EmigrateReporter>;
plugins?: Array<StringOrModule<EmigratePlugin>>; plugins?: Array<StringOrModule<EmigratePlugin>>;
directory?: string; directory?: string;
template?: string; template?: string;