feat: add upward config discovery and enhanced error messages#1235
feat: add upward config discovery and enhanced error messages#1235davidturnbull wants to merge 1415 commits intomainfrom
Conversation
* feat(cli): add --sound flag for task completion * fix: address review comments * style: format code with prettier * chore: formatting * chore: changeset --------- Co-authored-by: Veronica Pril <veranika.prilutskaya@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix: enhance cli errors debugging * chore: fix formatting
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* chore: upd i18n.json * chore: add changeset
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
…sed labeling automation to simplify CI and remove external AI dependency. (#1169) Co-authored-by: Capy <capy@capy.ai>
* feat: add el-CY en-IE fr-LU locales * chore: add changeset
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
- Add @lingo.dev/_locales as dependency - Create locale-codes re-export following existing patterns - Update package.json exports and typesVersions - Update tsup config to build locale-codes entry point This enables users to import locale utilities directly from lingo.dev/locale-codes
* feat: add locale-codes export to CLI package - Add @lingo.dev/_locales as dependency - Create locale-codes re-export following existing patterns - Update package.json exports and typesVersions - Update tsup config to build locale-codes entry point This enables users to import locale utilities directly from lingo.dev/locale-codes * chore: add changeset for locale-codes export
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix: lockedKeys in xcstrings * chore: formatting
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Lingo.dev <support@lingo.dev>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* feat: add biome formatter * chore: formatting * chore: removed console.log * chore: changeset
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix: upd biome formatter * chore: add changeset
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix: upd biome formatting logging * chore: add changeset
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix: biome JS API v3 bug * chore: add changeset
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
… (#1409) * feat: add init cursor command * feat: add init cursor command * feat: add init cursor command * Delete package-lock.json * Rename cursor.md to agents.md * Delete package-lock.json * feat(cli): lingo.dev init cursor Command for .cursorrules Setup (#1101) refactored * Delete agents.md * feat(cli): lingo.dev init cursor Command for .cursorrules Setup (#1101) changeset * feat(cli): lingo.dev init cursor Command for .cursorrules Setup (#1101) minor fixes * Update packages/cli/src/cli/cmd/init/cursor.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update packages/cli/src/cli/cmd/init/cursor.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update packages/cli/src/cli/cmd/init/cursor.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update packages/cli/src/cli/cmd/init/cursor.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Restore accidentally deleted file * Restore accidentally deleted file * Add i18n rules for Cursor AI agents * Update localization guidelines for Lingo.dev CLI Change after discussion with The-Best-Codes over Discord * Removing out of scope changes * remove out of scope changes * remove out of scope changes * feat(cli): add agents.md in package.json Signed-off-by: SK8-infi <shivansh.katiyar1712@gmail.com> * feat(cli): add agents.md in package.json Signed-off-by: SK8-infi <shivansh.katiyar1712@gmail.com> * Refactor path resolution for agents.md Updated path resolution for AGENTS_MD to handle both development and production environments. Was causing error of "agents.md not found" with help from @the_best_codes resolved the issue --------- Signed-off-by: SK8-infi <shivansh.katiyar1712@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* fix: tailwind css version * chore: empty changeset * chore: upd lockfile
* chore(compiler): add vite demo app back to the build * chore: empty changeset
Co-authored-by: Max Prilutskiy <maks.prilutskiy@gmail.com>
Co-authored-by: Lingo.dev <support@lingo.dev>
… concurrency (#1738) Co-authored-by: Veronica Prilutskaya <veronica@lingo.dev>
* feat(cli): add CSV per locale loader and associated i18n configurations * feat(cli): enhance i18n support with per-locale CSV configuration and new demo files * chore(cli): add changeset for csv-per-locale loader update * fix(cli): normalize CSV patterns in bucket loader and update i18n configurations * refactor(cli): update CSV files for English and Spanish locales with new structure and content --------- Co-authored-by: Veronica Prilutskaya <veronica@lingo.dev>
* chore: create community space for demo apps etc * chore: add changeset --------- Co-authored-by: Sumit Saurabh <sumitm4pro@192.168.1.9>
Co-authored-by: Max Prilutskiy <maks.prilutskiy@gmail.com>
* Add Malayalam translation for README and update i18n.json * chore: add changeset for Malayalam translation update * Update readme/ml.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update readme/ml.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * chore: fix Malayalam README and i18n.json (remove 'or') * chore: add signed commit --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Sumit Saurabh <62152915+sumitsaurabh927@users.noreply.github.com> Co-authored-by: Max Prilutskiy <5614659+maxprilutskiy@users.noreply.github.com> Co-authored-by: Veronica Prilutskaya <veronica@lingo.dev>
Co-authored-by: Max Prilutskiy <maks.prilutskiy@gmail.com>
Co-authored-by: Lingo.dev <support@lingo.dev>
* chore: fix readme * chore: empty changeset
Co-authored-by: Andrei Hirsa <--global>
Co-authored-by: Max Prilutskiy <maks.prilutskiy@gmail.com>
* chore: upgrade to ai sdk 5 to resolve security vulns Signed-off-by: The-Best-Codes <bestcodes.official@gmail.com> * chore: changeset Signed-off-by: The-Best-Codes <bestcodes.official@gmail.com> * feat: upgrade to zod 4 Signed-off-by: The-Best-Codes <bestcodes.official@gmail.com> * chore: remove zod-to-json-schema from the codebase Signed-off-by: The-Best-Codes <bestcodes.official@gmail.com> * fix: use "Z" not "z" Signed-off-by: The-Best-Codes <bestcodes.official@gmail.com> * fix: use error.issues Signed-off-by: The-Best-Codes <bestcodes.official@gmail.com> * fix: perform more zod 4 migrations Signed-off-by: The-Best-Codes <bestcodes.official@gmail.com> * fix: resolve type errors in mcp.ts (used any, might manually verify everything still works fine) Signed-off-by: The-Best-Codes <bestcodes.official@gmail.com> * fix: remove defaults from optional fields in zod 4 (fixes failing tests) Signed-off-by: The-Best-Codes <bestcodes.official@gmail.com> * chore: fmt Signed-off-by: The-Best-Codes <bestcodes.official@gmail.com> * chore: remove unused zod-to-json-schema dep Signed-off-by: The-Best-Codes <bestcodes.official@gmail.com> * fix: use prefault Signed-off-by: The-Best-Codes <bestcodes.official@gmail.com> * fix: revert Signed-off-by: The-Best-Codes <bestcodes.official@gmail.com> * fix: upgrade new deps Signed-off-by: The-Best-Codes <bestcodes.official@gmail.com> * fix: sort deps Signed-off-by: The-Best-Codes <bestcodes.official@gmail.com> * fix: revert to zod 3 for docs script Signed-off-by: The-Best-Codes <bestcodes.official@gmail.com> * chore: upgrade to ai sdk 6 Signed-off-by: The-Best-Codes <bestcodes.official@gmail.com> * chore: sync lockfile Signed-off-by: The-Best-Codes <bestcodes.official@gmail.com> --------- Signed-off-by: The-Best-Codes <bestcodes.official@gmail.com> Co-authored-by: Max Prilutskiy <5614659+maxprilutskiy@users.noreply.github.com>
|
@copilot does it make sense to use cosmiconfig here? WDYT? |
|
@maxprilutskiy I've opened a new pull request, #1756, to work on those changes. Once the pull request is ready, I'll request review from you. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| entry.name === ".git" || | ||
| entry.name === "dist" || | ||
| entry.name === "build" || | ||
| entry.name.startsWith(".") |
There was a problem hiding this comment.
The function silently skips directories starting with a dot (line 100), but this excludes legitimate use cases like .github, .vscode, or other dot-prefixed project directories that might contain i18n configs. While this is reasonable for most cases, consider documenting this behavior in the function's JSDoc or making it configurable.
| let _cachedConfigPath: string | null = null; | ||
| let _cachedConfigRoot: string | null = null; |
There was a problem hiding this comment.
The module-level cache variables can become stale when process.chdir() is called (which happens in CI flows at packages/cli/src/cli/cmd/ci/flows/in-branch.ts line 131 and packages/cli/src/cli/cmd/ci/platforms/gitlab.ts line 14). After a directory change, the cached path would still point to the old location relative to the new cwd, causing config resolution to fail or use the wrong config file. Consider clearing the cache when the working directory changes, or store absolute paths instead of relying on process.cwd().
| throw new Error( | ||
| `i18n.json not found in current directory or parent directories.\n\n` + | ||
| `Found ${foundBelow.length} config file(s) in subdirectories:\n` + | ||
| configList + | ||
| moreText + | ||
| `\n\nPlease cd into one of these directories, or run \`lingo.dev init\` to initialize a new project.`, | ||
| ); | ||
| } else { | ||
| throw new Error( | ||
| `i18n.json not found. Please run \`lingo.dev init\` to initialize the project.`, | ||
| ); | ||
| } |
There was a problem hiding this comment.
The error message format is inconsistent with the standard error structure used elsewhere in the codebase. Other commands use CLIError or ConfigError with message and docUrl properties (see status.ts, i18n.ts). This plain Error without a docUrl prevents users from accessing documentation links that could help resolve the issue.
| export function saveConfig(config: I18nConfig) { | ||
| const configFilePath = _getConfigFilePath(); | ||
| const configInfo = _findConfigPath(); | ||
| if (!configInfo) { | ||
| throw new Error("Cannot save config: i18n.json not found"); | ||
| } | ||
|
|
||
| const serialized = JSON.stringify(config, null, 2); | ||
| fs.writeFileSync(configFilePath, serialized); | ||
| fs.writeFileSync(configInfo.configPath, serialized); | ||
|
|
||
| return config; | ||
| } |
There was a problem hiding this comment.
The saveConfig function will fail silently in a race condition scenario: if the config file is deleted between the _findConfigPath() check and the fs.writeFileSync() call, the write will create the file in a potentially incorrect location (wherever the last found config was). Consider using the configPath from the initial config load or validating the path still exists before writing.
| export function findConfigsDownwards( | ||
| startDir: string = process.cwd(), | ||
| maxDepth: number = 3, | ||
| ): string[] { | ||
| const found: string[] = []; | ||
|
|
||
| function search(dir: string, depth: number) { | ||
| if (depth > maxDepth) return; | ||
|
|
||
| try { | ||
| const entries = fs.readdirSync(dir, { withFileTypes: true }); | ||
|
|
||
| for (const entry of entries) { | ||
| if (entry.isDirectory()) { | ||
| // Skip common directories that shouldn't contain configs | ||
| if ( | ||
| entry.name === "node_modules" || | ||
| entry.name === ".git" || | ||
| entry.name === "dist" || | ||
| entry.name === "build" || | ||
| entry.name.startsWith(".") | ||
| ) { | ||
| continue; | ||
| } | ||
|
|
||
| const subDir = path.join(dir, entry.name); | ||
| const configPath = path.join(subDir, "i18n.json"); | ||
|
|
||
| if (fs.existsSync(configPath)) { | ||
| found.push(path.relative(startDir, configPath)); | ||
| } | ||
|
|
||
| search(subDir, depth + 1); | ||
| } | ||
| } | ||
| } catch (error) { | ||
| // Ignore permission errors, etc. | ||
| } | ||
| } | ||
|
|
||
| search(startDir, 0); | ||
| return found; | ||
| } |
There was a problem hiding this comment.
The findConfigsDownwards function has quadratic time complexity due to checking fs.existsSync() for every subdirectory individually. In a monorepo with many directories, this could cause significant performance degradation. Consider collecting all directories first, then batch-checking for config files, or using a more efficient traversal strategy.
| } | ||
|
|
||
| const fileContents = fs.readFileSync(configFilePath, "utf8"); | ||
| const { configPath, configRoot } = configInfo; |
There was a problem hiding this comment.
Unused variable configRoot.
| const { configPath, configRoot } = configInfo; | |
| const { configPath } = configInfo; |
Problem
Users must run CLI commands from the exact directory containing
i18n.json, which creates a poor user experience:lingo.dev runfrom a subdirectory fails with a generic errorsrc/,components/, etc.) is brokenBefore:
Solution
Commands now automatically search parent directories for
i18n.json(similar to how Git finds.git) and provide helpful guidance when the config isn't found.After:
Key Improvements
i18n.jsonor reach the filesystem rootrun,status,i18n) behave identicallyTesting Instructions
Test 1: Run from subdirectory
Test 2: Helpful error messages
Test 3: Monorepo scenario
Test 4: All commands work consistently
Behavior Notes
What's Changed
utils/config.tsgetConfigOrThrow()for consistent error handling