diff --git a/.changeset/famous-elephants-fail.md b/.changeset/famous-elephants-fail.md new file mode 100644 index 0000000..b97a70a --- /dev/null +++ b/.changeset/famous-elephants-fail.md @@ -0,0 +1,5 @@ +--- +'@emigrate/cli': minor +--- + +Add a built-in "json" reporter for outputting a single JSON object diff --git a/.changeset/slimy-tomatoes-taste.md b/.changeset/slimy-tomatoes-taste.md new file mode 100644 index 0000000..1f3b288 --- /dev/null +++ b/.changeset/slimy-tomatoes-taste.md @@ -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 diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 2ba0513..377cffd 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -104,7 +104,7 @@ Options: -p, --plugin The plugin(s) to use (can be specified multiple times) - -r, --reporter The reporter to use for reporting the migration progress + -r, --reporter The reporter to use for reporting the migration progress (default: pretty) -l, --limit Limit the number of migrations to run @@ -261,7 +261,7 @@ Options: -h, --help Show this help message and exit -d, --directory The directory where the migration files are located (required) - -r, --reporter The reporter to use for reporting the migration file creation progress + -r, --reporter The reporter to use for reporting the migration file creation progress (default: pretty) -p, --plugin The plugin(s) to use (can be specified multiple times) -t, --template 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) @@ -358,7 +358,7 @@ Options: -d, --directory The directory where the migration files are located (required) -i, --import 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 - -r, --reporter The reporter to use for reporting the migrations + -r, --reporter The reporter to use for reporting the migrations (default: pretty) -s, --storage The storage to use to get the migration history (required) --color Force 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 The directory where the migration files are located (required) -i, --import 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 - -r, --reporter The reporter to use for reporting the removal process + -r, --reporter The reporter to use for reporting the removal process (default: pretty) -s, --storage 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 --color Force color output (this option is passed to the reporter) diff --git a/packages/cli/src/commands/list.ts b/packages/cli/src/commands/list.ts index 0dcdcf9..ec8fc22 100644 --- a/packages/cli/src/commands/list.ts +++ b/packages/cli/src/commands/list.ts @@ -5,8 +5,7 @@ import { exec } from '../exec.js'; import { migrationRunner } from '../migration-runner.js'; import { collectMigrations } from '../collect-migrations.js'; import { version } from '../get-package-info.js'; - -const lazyDefaultReporter = async () => import('../reporters/default.js'); +import { getStandardReporter } from '../reporters/get.js'; type ExtraFlags = { 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'); } - const reporter = await getOrLoadReporter([reporterConfig ?? lazyDefaultReporter]); + const reporter = getStandardReporter(reporterConfig) ?? (await getOrLoadReporter([reporterConfig])); if (!reporter) { throw BadOptionError.fromOption( diff --git a/packages/cli/src/commands/new.ts b/packages/cli/src/commands/new.ts index 81351b8..c921808 100644 --- a/packages/cli/src/commands/new.ts +++ b/packages/cli/src/commands/new.ts @@ -15,8 +15,7 @@ import { type Config } from '../types.js'; import { withLeadingPeriod } from '../with-leading-period.js'; import { version } from '../get-package-info.js'; import { getDuration } from '../get-duration.js'; - -const lazyDefaultReporter = async () => import('../reporters/default.js'); +import { getStandardReporter } from '../reporters/get.js'; type ExtraFlags = { cwd: string; @@ -38,7 +37,7 @@ export default async function newCommand( throw MissingOptionError.fromOption(['extension', 'template', 'plugin']); } - const reporter = await getOrLoadReporter([reporterConfig ?? lazyDefaultReporter]); + const reporter = getStandardReporter(reporterConfig) ?? (await getOrLoadReporter([reporterConfig])); if (!reporter) { throw BadOptionError.fromOption( diff --git a/packages/cli/src/commands/remove.ts b/packages/cli/src/commands/remove.ts index 6b3d977..de5490c 100644 --- a/packages/cli/src/commands/remove.ts +++ b/packages/cli/src/commands/remove.ts @@ -18,6 +18,7 @@ import { collectMigrations } from '../collect-migrations.js'; import { migrationRunner } from '../migration-runner.js'; import { arrayMapAsync } from '../array-map-async.js'; import { type GetMigrationsFunction } from '../get-migrations.js'; +import { getStandardReporter } from '../reporters/get.js'; type ExtraFlags = { cwd: string; @@ -27,8 +28,6 @@ type ExtraFlags = { type RemovableMigrationMetadata = MigrationMetadata & { originalStatus?: 'done' | 'failed' }; -const lazyDefaultReporter = async () => import('../reporters/default.js'); - export default async function removeCommand( { 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'); } - const reporter = await getOrLoadReporter([reporterConfig ?? lazyDefaultReporter]); + const reporter = getStandardReporter(reporterConfig) ?? (await getOrLoadReporter([reporterConfig])); if (!reporter) { throw BadOptionError.fromOption( diff --git a/packages/cli/src/commands/up.ts b/packages/cli/src/commands/up.ts index e0faba4..e47897b 100644 --- a/packages/cli/src/commands/up.ts +++ b/packages/cli/src/commands/up.ts @@ -16,6 +16,7 @@ import { exec } from '../exec.js'; import { migrationRunner } from '../migration-runner.js'; import { collectMigrations } from '../collect-migrations.js'; import { version } from '../get-package-info.js'; +import { getStandardReporter } from '../reporters/get.js'; type ExtraFlags = { cwd: string; @@ -29,7 +30,6 @@ type ExtraFlags = { abortRespite?: number; }; -const lazyDefaultReporter = async () => import('../reporters/default.js'); const lazyPluginLoaderJs = async () => import('../plugin-loader-js.js'); 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'); } - const reporter = await getOrLoadReporter([reporterConfig ?? lazyDefaultReporter]); + const reporter = getStandardReporter(reporterConfig) ?? (await getOrLoadReporter([reporterConfig])); if (!reporter) { throw BadOptionError.fromOption( diff --git a/packages/cli/src/reporters/get.ts b/packages/cli/src/reporters/get.ts new file mode 100644 index 0000000..c3935c3 --- /dev/null +++ b/packages/cli/src/reporters/get.ts @@ -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 +}; diff --git a/packages/cli/src/reporters/index.ts b/packages/cli/src/reporters/index.ts new file mode 100644 index 0000000..c1784a9 --- /dev/null +++ b/packages/cli/src/reporters/index.ts @@ -0,0 +1,2 @@ +export { default as pretty } from './default.js'; +export { default as json } from './json.js'; diff --git a/packages/cli/src/reporters/json.ts b/packages/cli/src/reporters/json.ts new file mode 100644 index 0000000..4a3dcc3 --- /dev/null +++ b/packages/cli/src/reporters/json.ts @@ -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; diff --git a/packages/cli/src/types.ts b/packages/cli/src/types.ts index 7d6b400..c45b744 100644 --- a/packages/cli/src/types.ts +++ b/packages/cli/src/types.ts @@ -1,4 +1,7 @@ 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; @@ -6,7 +9,7 @@ type StringOrModule = string | T | (() => Awaitable) | (() => Awaitable<{ export type Config = { storage?: StringOrModule; - reporter?: StringOrModule; + reporter?: StandardReporter | StringOrModule; plugins?: Array>; directory?: string; template?: string;