From c72ca190bffacfb5302ae5f2bc53f1f1891fc294 Mon Sep 17 00:00:00 2001 From: Cedric van Putten Date: Tue, 12 Mar 2024 21:20:09 +0100 Subject: [PATCH] feature: add error handling when stats file is outdated --- src/cli/bin.ts | 19 +++++++++++++++-- src/cli/resolveOptions.ts | 6 +----- src/metro/serializeStatsFile.ts | 5 +++-- src/utils/errors.ts | 38 +++++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 9 deletions(-) create mode 100644 src/utils/errors.ts diff --git a/src/cli/bin.ts b/src/cli/bin.ts index 7503786..cad2a71 100644 --- a/src/cli/bin.ts +++ b/src/cli/bin.ts @@ -1,6 +1,7 @@ #!/usr/bin/env node import arg from 'arg'; import open from 'open'; +import path from 'path'; import { resolveOptions } from './resolveOptions'; import { createServer } from './server'; @@ -39,8 +40,6 @@ if (args['--help']) { process.on('SIGINT', () => process.exit(0)); process.on('SIGTERM', () => process.exit(0)); -run(); - async function run() { const options = await resolveOptions(args); const server = createServer(options); @@ -54,3 +53,19 @@ async function run() { open(href); }); } + +run().catch((error) => { + if (error.type !== 'AtlasError') { + throw error; + } + + if (error.code === 'STATS_FILE_INCOMPATIBLE') { + const statsFile = path.relative(process.cwd(), error.statsFile); + console.error('Stats file is incompatible with this version, use this instead:'); + console.error(` npx expo-atlas@${error.incompatibleVersion} ${statsFile}`); + } else { + console.error(`${error.message} (${error.code})`); + } + + process.exit(1); +}); diff --git a/src/cli/resolveOptions.ts b/src/cli/resolveOptions.ts index 8fb8a8b..2d09c0f 100644 --- a/src/cli/resolveOptions.ts +++ b/src/cli/resolveOptions.ts @@ -20,11 +20,7 @@ async function resolveStatsFile(input: Input) { throw new Error(`Could not find stats file "${statsFile}".`); } - try { - await validateStatsFile(statsFile); - } catch (error) { - throw new Error(`Stats file is incompatible with this version.`, { cause: error }); - } + await validateStatsFile(statsFile); return path.resolve(statsFile); } diff --git a/src/metro/serializeStatsFile.ts b/src/metro/serializeStatsFile.ts index d20d155..6075c50 100644 --- a/src/metro/serializeStatsFile.ts +++ b/src/metro/serializeStatsFile.ts @@ -4,6 +4,7 @@ import path from 'path'; import { type MetroStatsEntry } from './convertGraphToStats'; import { name, version } from '../../package.json'; import { env } from '../utils/env'; +import { AtlasValidationError } from '../utils/errors'; import { mapLines, readFirstLine, readLine } from '../utils/file'; export type StatsMetadata = { name: string; version: string }; @@ -21,7 +22,7 @@ export function getStatsMetdata(): StatsMetadata { /** Validate if the stats file is compatible with this library version */ export async function validateStatsFile(statsFile: string, metadata = getStatsMetdata()) { if (!fs.existsSync(statsFile)) { - throw new Error(`Stats file "${statsFile}" not found.`); + throw new AtlasValidationError('STATS_FILE_NOT_FOUND', statsFile); } if (env.EXPO_NO_STATS_VALIDATION) { @@ -32,7 +33,7 @@ export async function validateStatsFile(statsFile: string, metadata = getStatsMe const data = line ? JSON.parse(line) : {}; if (data.name !== metadata.name || data.version !== metadata.version) { - throw new Error(`Stats file "${statsFile}" is incompatible with this version of the plugin.`); + throw new AtlasValidationError('STATS_FILE_INCOMPATIBLE', statsFile, data.version); } } diff --git a/src/utils/errors.ts b/src/utils/errors.ts new file mode 100644 index 0000000..2c9c1c0 --- /dev/null +++ b/src/utils/errors.ts @@ -0,0 +1,38 @@ +export class AtlasError extends Error { + /** A property to determine if any of the extended errors are atlas-specific errors */ + public readonly type = 'AtlasError'; + /** The error (class) name */ + public readonly name: string; + /** The error code, specifically for these types of errors */ + public readonly code?: string; + + constructor(code: string, message = '', options?: ErrorOptions) { + super(message, options); + this.name = this.constructor.name; + this.code = code; + } +} + +export class AtlasValidationError extends AtlasError { + constructor( + code: 'STATS_FILE_NOT_FOUND' | 'STATS_FILE_INCOMPATIBLE', + public readonly statsFile: string, + public readonly incompatibleVersion?: string + ) { + super( + code, + code === 'STATS_FILE_NOT_FOUND' + ? `Stats file not found: ${statsFile}` + : `Stats file is incompatible with this version.` + ); + } +} + +export function handleError(error: Error) { + if (error instanceof AtlasError) { + console.error(`${error.message} (${error.code})`); + return true; + } + + throw error; +}