Skip to content

Commit d9294c0

Browse files
authored
fix: enhance cli errors debugging (#1166)
* fix: enhance cli errors debugging * chore: fix formatting
1 parent 66b474e commit d9294c0

4 files changed

Lines changed: 306 additions & 12 deletions

File tree

.changeset/honest-jokes-tie.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"lingo.dev": patch
3+
---
4+
5+
enhance cli errors debugging

packages/cli/src/cli/cmd/i18n.ts

Lines changed: 100 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,19 @@ import _ from "lodash";
1010
import * as path from "path";
1111
import { getConfig } from "../utils/config";
1212
import { getSettings } from "../utils/settings";
13-
import { CLIError } from "../utils/errors";
13+
import {
14+
ConfigError,
15+
AuthenticationError,
16+
ValidationError,
17+
LocalizationError,
18+
BucketProcessingError,
19+
getCLIErrorType,
20+
isLocalizationError,
21+
isBucketProcessingError,
22+
ErrorDetail,
23+
aggregateErrorAnalytics,
24+
createPreviousErrorContext,
25+
} from "../utils/errors";
1426
import Ora from "ora";
1527
import createBucketLoader from "../loaders";
1628
import { createAuthenticator } from "../utils/auth";
@@ -79,7 +91,23 @@ export default new Command()
7991
updateGitignore();
8092

8193
const ora = Ora();
82-
const flags = parseFlags(options);
94+
let flags: ReturnType<typeof parseFlags>;
95+
96+
try {
97+
flags = parseFlags(options);
98+
} catch (parseError: any) {
99+
// Handle flag validation errors (like invalid locale codes)
100+
await trackEvent("unknown", "cmd.i18n.error", {
101+
errorType: "validation_error",
102+
errorName: parseError.name || "ValidationError",
103+
errorMessage: parseError.message || "Invalid command line options",
104+
errorStack: parseError.stack,
105+
fatal: true,
106+
errorCount: 1,
107+
stage: "flag_validation",
108+
});
109+
throw parseError;
110+
}
83111

84112
if (flags.debug) {
85113
// wait for user input, use inquirer
@@ -94,6 +122,7 @@ export default new Command()
94122

95123
let hasErrors = false;
96124
let authId: string | null = null;
125+
const errorDetails: ErrorDetail[] = [];
97126
try {
98127
ora.start("Loading configuration...");
99128
const i18nConfig = getConfig();
@@ -469,9 +498,23 @@ export default new Command()
469498
);
470499
}
471500
} catch (_error: any) {
472-
const error = new Error(
501+
const error = new LocalizationError(
473502
`[${sourceLocale} -> ${targetLocale}] Localization failed: ${_error.message}`,
503+
{
504+
bucket: bucket.type,
505+
sourceLocale,
506+
targetLocale,
507+
pathPattern: bucketPath.pathPattern,
508+
},
474509
);
510+
errorDetails.push({
511+
type: "locale_error",
512+
bucket: bucket.type,
513+
locale: `${sourceLocale} -> ${targetLocale}`,
514+
pathPattern: bucketPath.pathPattern,
515+
message: _error.message,
516+
stack: _error.stack,
517+
});
475518
if (flags.strict) {
476519
throw error;
477520
} else {
@@ -488,9 +531,16 @@ export default new Command()
488531
}
489532
}
490533
} catch (_error: any) {
491-
const error = new Error(
534+
const error = new BucketProcessingError(
492535
`Failed to process bucket ${bucket.type}: ${_error.message}`,
536+
bucket.type,
493537
);
538+
errorDetails.push({
539+
type: "bucket_error",
540+
bucket: bucket.type,
541+
message: _error.message,
542+
stack: _error.stack,
543+
});
494544
if (flags.strict) {
495545
throw error;
496546
} else {
@@ -503,21 +553,59 @@ export default new Command()
503553
if (!hasErrors) {
504554
ora.succeed("Localization completed.");
505555
await trackEvent(authId, "cmd.i18n.success", {
506-
i18nConfig,
556+
i18nConfig: {
557+
sourceLocale: i18nConfig!.locale.source,
558+
targetLocales: i18nConfig!.locale.targets,
559+
bucketTypes: Object.keys(i18nConfig!.buckets),
560+
},
507561
flags,
562+
bucketCount: buckets.length,
563+
localeCount: targetLocales.length,
564+
processedSuccessfully: true,
508565
});
509566
} else {
510567
ora.warn("Localization completed with errors.");
511568
await trackEvent(authId || "unknown", "cmd.i18n.error", {
512569
flags,
570+
...aggregateErrorAnalytics(
571+
errorDetails,
572+
buckets,
573+
targetLocales,
574+
i18nConfig!,
575+
),
513576
});
514577
}
515578
} catch (error: any) {
516579
ora.fail(error.message);
517580

581+
// Use robust error type detection
582+
const errorType = getCLIErrorType(error);
583+
584+
// Extract additional context from typed errors
585+
let errorContext: any = {};
586+
if (isLocalizationError(error)) {
587+
errorContext = {
588+
bucket: error.bucket,
589+
sourceLocale: error.sourceLocale,
590+
targetLocale: error.targetLocale,
591+
pathPattern: error.pathPattern,
592+
};
593+
} else if (isBucketProcessingError(error)) {
594+
errorContext = {
595+
bucket: error.bucket,
596+
};
597+
}
598+
518599
await trackEvent(authId || "unknown", "cmd.i18n.error", {
519600
flags,
520-
error,
601+
errorType,
602+
errorName: error.name || "Error",
603+
errorMessage: error.message,
604+
errorStack: error.stack,
605+
errorContext,
606+
fatal: true,
607+
errorCount: errorDetails.length + 1,
608+
previousErrors: createPreviousErrorContext(errorDetails),
521609
});
522610
}
523611
});
@@ -541,7 +629,7 @@ function parseFlags(options: any) {
541629
// Export validateAuth for use in other commands
542630
export async function validateAuth(settings: ReturnType<typeof getSettings>) {
543631
if (!settings.auth.apiKey) {
544-
throw new CLIError({
632+
throw new AuthenticationError({
545633
message:
546634
"Not authenticated. Please run `lingo.dev login` to authenticate.",
547635
docUrl: "authError",
@@ -554,7 +642,7 @@ export async function validateAuth(settings: ReturnType<typeof getSettings>) {
554642
});
555643
const user = await authenticator.whoami();
556644
if (!user) {
557-
throw new CLIError({
645+
throw new AuthenticationError({
558646
message: "Invalid API key. Please run `lingo.dev login` to authenticate.",
559647
docUrl: "authError",
560648
});
@@ -568,21 +656,21 @@ function validateParams(
568656
flags: ReturnType<typeof parseFlags>,
569657
) {
570658
if (!i18nConfig) {
571-
throw new CLIError({
659+
throw new ConfigError({
572660
message:
573661
"i18n.json not found. Please run `lingo.dev init` to initialize the project.",
574662
docUrl: "i18nNotFound",
575663
});
576664
} else if (!i18nConfig.buckets || !Object.keys(i18nConfig.buckets).length) {
577-
throw new CLIError({
665+
throw new ConfigError({
578666
message:
579667
"No buckets found in i18n.json. Please add at least one bucket containing i18n content.",
580668
docUrl: "bucketNotFound",
581669
});
582670
} else if (
583671
flags.locale?.some((locale) => !i18nConfig.locale.targets.includes(locale))
584672
) {
585-
throw new CLIError({
673+
throw new ValidationError({
586674
message: `One or more specified locales do not exist in i18n.json locale.targets. Please add them to the list and try again.`,
587675
docUrl: "localeTargetNotFound",
588676
});
@@ -592,7 +680,7 @@ function validateParams(
592680
!i18nConfig.buckets[bucket as keyof typeof i18nConfig.buckets],
593681
)
594682
) {
595-
throw new CLIError({
683+
throw new ValidationError({
596684
message: `One or more specified buckets do not exist in i18n.json. Please add them to the list and try again.`,
597685
docUrl: "bucketNotFound",
598686
});

0 commit comments

Comments
 (0)