-
Notifications
You must be signed in to change notification settings - Fork 662
feat: added lingo.dev validate command #1425
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feat: added lingo.dev validate command #1425
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces a new lingo.dev validate command that performs comprehensive pre-flight configuration validation before running translations. The command validates configuration files, locale formats, bucket types, and file accessibility, providing users with clear feedback on potential issues.
Key changes:
- Added
validatecommand with--strictand--api-keyoptions - Validates configuration file, locale codes, bucket types, and file accessibility
- Comprehensive test suite with 9 new tests covering various validation scenarios
Reviewed Changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| packages/cli/src/cli/index.ts | Imports and registers the new validate command |
| packages/cli/src/cli/cmd/validate.ts | Implements the validate command with configuration and file validation logic |
| packages/cli/src/cli/cmd/validate.spec.ts | Test suite for the validate command functionality |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| // Check source file | ||
| const sourceFilePath = path.resolve( | ||
| bucketPath.pathPattern.replaceAll("[locale]", sourceLocale), |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] Using replaceAll() is correct for replacing all occurrences, but consider using replace() with a placeholder substitution function or regex for safer replacement. If [locale] appears in unexpected places (e.g., within string values), this could lead to unintended replacements. However, if the pattern is strictly controlled, this is acceptable.
| bucketPath.pathPattern.replaceAll("[locale]", sourceLocale), | |
| bucketPath.pathPattern.replace(/\[locale\]/g, sourceLocale), |
| try { | ||
| await bucketLoader.pull(sourceLocale); | ||
| fs.accessSync(sourceFilePath, fs.constants.R_OK); | ||
| console.log( | ||
| chalk.green("✓") + ` Source file exists: ${sourceFilePath}`, | ||
| ); | ||
| checksPassedCount++; | ||
| } catch (error) { | ||
| console.log( | ||
| chalk.red("✗") + | ||
| ` Source file exists but is not readable: ${sourceFilePath}`, |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code calls bucketLoader.pull() before checking file readability with fs.accessSync(). If the file is readable but the pull operation fails, this could be misleading. Consider checking file access first before attempting to pull, or handle the pull error separately to distinguish between file access issues and loader-specific problems.
| try { | |
| await bucketLoader.pull(sourceLocale); | |
| fs.accessSync(sourceFilePath, fs.constants.R_OK); | |
| console.log( | |
| chalk.green("✓") + ` Source file exists: ${sourceFilePath}`, | |
| ); | |
| checksPassedCount++; | |
| } catch (error) { | |
| console.log( | |
| chalk.red("✗") + | |
| ` Source file exists but is not readable: ${sourceFilePath}`, | |
| // First, check file readability | |
| try { | |
| fs.accessSync(sourceFilePath, fs.constants.R_OK); | |
| } catch (error) { | |
| console.log( | |
| chalk.red("✗") + | |
| ` Source file exists but is not readable: ${sourceFilePath}`, | |
| ); | |
| errorCount++; | |
| allSourceFilesReadable = false; | |
| continue; | |
| } | |
| // Then, try to pull with the loader | |
| try { | |
| await bucketLoader.pull(sourceLocale); | |
| console.log( | |
| chalk.green("✓") + ` Source file exists: ${sourceFilePath}`, | |
| ); | |
| checksPassedCount++; | |
| } catch (error) { | |
| console.log( | |
| chalk.red("✗") + | |
| ` Source file is readable but bucketLoader.pull failed: ${sourceFilePath}\n ${error instanceof Error ? error.message : error}`, |
| try { | ||
| // Check read access | ||
| fs.accessSync(targetFilePath, fs.constants.R_OK); | ||
|
|
||
| // Check write access | ||
| try { | ||
| fs.accessSync(targetFilePath, fs.constants.W_OK); | ||
| console.log( | ||
| chalk.green("✓") + | ||
| ` Target file exists: ${targetFilePath}`, | ||
| ); | ||
| checksPassedCount++; | ||
| } catch (error) { | ||
| console.log( | ||
| chalk.red("✗") + | ||
| ` Target file exists but is not writable: ${targetFilePath}`, | ||
| ); | ||
| errorCount++; | ||
| allTargetDirsWritable = false; | ||
| } | ||
| } catch (error) { | ||
| console.log( | ||
| chalk.red("✗") + | ||
| ` Target file exists but is not readable: ${targetFilePath}`, | ||
| ); | ||
| errorCount++; | ||
| } |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The read access check on line 239 is not wrapped in a try-catch, but it's within an outer try-catch block that starts at line 237. This means if the read access fails, it will be caught by the outer catch at line 257, which outputs 'not readable'. However, the nested try-catch for write access (lines 242-256) can make the control flow confusing. Consider restructuring to separate read and write access checks more clearly for better maintainability.
| try { | |
| // Check read access | |
| fs.accessSync(targetFilePath, fs.constants.R_OK); | |
| // Check write access | |
| try { | |
| fs.accessSync(targetFilePath, fs.constants.W_OK); | |
| console.log( | |
| chalk.green("✓") + | |
| ` Target file exists: ${targetFilePath}`, | |
| ); | |
| checksPassedCount++; | |
| } catch (error) { | |
| console.log( | |
| chalk.red("✗") + | |
| ` Target file exists but is not writable: ${targetFilePath}`, | |
| ); | |
| errorCount++; | |
| allTargetDirsWritable = false; | |
| } | |
| } catch (error) { | |
| console.log( | |
| chalk.red("✗") + | |
| ` Target file exists but is not readable: ${targetFilePath}`, | |
| ); | |
| errorCount++; | |
| } | |
| // Check read access | |
| try { | |
| fs.accessSync(targetFilePath, fs.constants.R_OK); | |
| } catch (error) { | |
| console.log( | |
| chalk.red("✗") + | |
| ` Target file exists but is not readable: ${targetFilePath}`, | |
| ); | |
| errorCount++; | |
| continue; | |
| } | |
| // Check write access | |
| try { | |
| fs.accessSync(targetFilePath, fs.constants.W_OK); | |
| console.log( | |
| chalk.green("✓") + | |
| ` Target file exists: ${targetFilePath}`, | |
| ); | |
| checksPassedCount++; | |
| } catch (error) { | |
| console.log( | |
| chalk.red("✗") + | |
| ` Target file exists but is not writable: ${targetFilePath}`, | |
| ); | |
| errorCount++; | |
| allTargetDirsWritable = false; | |
| } |
| @@ -0,0 +1,376 @@ | |||
| import { | |||
| bucketTypeSchema, | |||
| I18nConfig, | |||
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused import I18nConfig.
| I18nConfig, |
| import * as path from "path"; | ||
| import { getConfig } from "../utils/config"; | ||
| import { getSettings } from "../utils/settings"; | ||
| import { CLIError } from "../utils/errors"; |
Copilot
AI
Nov 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unused import CLIError.
| import { CLIError } from "../utils/errors"; |
|
@navyansh007 please resolve the comments |
|
Hey @navyansh007! Just checking in - are you still working on this PR? We noticed there are some comments that may need addressing. If you need more time, no problem! Just let us know. If we don't hear back within a week, we'll close this to keep the repo tidy, but you can always reopen when ready. |
Adds new
lingo.dev validatecommand for comprehensive configuration validation before running translations.Features
Commands
lingo.dev validate- Basic validationlingo.dev validate --strict- Treat missing target files as errorslingo.dev validate --api-key <key>- Include authentication checkValidates
Output
Simple linear format with ✓ (success), ⚠ (warning), ✗ (error)
Testing
Use Cases
Closes #1300
Screen.Recording.2025-10-30.at.8.34.52.PM.mov