Skip to content

feat: vNext migration#2035

Merged
AndreyHirsa merged 6 commits intomainfrom
feat/vnext-migration
Mar 7, 2026
Merged

feat: vNext migration#2035
AndreyHirsa merged 6 commits intomainfrom
feat/vnext-migration

Conversation

@AndreyHirsa
Copy link
Contributor

@AndreyHirsa AndreyHirsa commented Mar 5, 2026

Summary

Unify CLI and SDK to a single API auth path using X-API-Key header, move engineId from URL path to request body, and remove the legacy vNext/non-vNext split.

Changes

  • SDK: Single auth path with X-API-Key header (removed Authorization: Bearer path); engineId is now an optional body parameter instead of a URL path segment; URL is always /process/localize
  • CLI: Merged lingodotdev-vnext.ts localizer into lingodotdev.ts; removed isVNext branching in CI flow
  • Spec: Added config v1.15 with engineId field; auto-migrates vNextengineId on config load
  • Auth: All packages now use GET /users/me with X-API-Key (was POST /whoami with Authorization: Bearer in legacy path)
  • Compiler/New-compiler: Updated observability to use X-API-Key + GET /users/me; fixed Content-Type header typo

Testing

Business logic tests added:

  • SDK: engineId included in request body when provided
  • SDK: engineId absent from body when not configured
  • SDK: Observability resolves identity via GET /users/me with X-API-Key
  • SDK: Falls back to API key hash when whoami fails or returns no email
  • SDK: Identity cache works across calls and per API key
  • All tests pass locally

Visuals

N/A — no UI changes.

Checklist

  • Changeset added (if version bump needed)
  • Tests cover business logic (not just happy path)
  • No breaking changes (or documented below)

Summary by CodeRabbit

  • New Features

    • Added optional engineId setting with automatic migration from legacy vNext configs
  • Chores

    • Moved SDK/CLI to unified api.lingo.dev endpoints
    • Switched authentication to X-API-Key for all requests
    • Simplified provider and authentication flows; removed legacy vNext paths
    • Added changelog entries for multiple packages
  • Tests

    • Updated tests to reflect new endpoints and engineId behavior

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 5, 2026

📝 Walkthrough

Walkthrough

Migrates vNext into a unified engineId model and switches SDK/CLI to api.lingo.dev: endpoints now use /process/* and /users/me, authentication uses X-API-Key, vNext code paths removed, and config v1.15 adds engineId with auto-migration from vNext.

Changes

Cohort / File(s) Summary
Changelog & Config
.changeset/thirty-pots-judge.md, packages/spec/src/config.ts
Adds changelog; introduces config v1.15 with optional engineId, upgrader that migrates vNextengineId, exports parseI18nConfig and defaultConfig, and updates LATEST_CONFIG_DEFINITION.
CLI Commands
packages/cli/src/cli/cmd/ci/index.ts, packages/cli/src/cli/cmd/i18n.ts, packages/cli/src/cli/cmd/run/setup.ts
Removes vNext branching and config switching; consolidates auth to a single API key check; threads engineId into processor/localizer initialization; simplifies provider detection and parallelism logic.
Localizer Types & Index
packages/cli/src/cli/localizer/_types.ts, packages/cli/src/cli/localizer/index.ts
Removes Lingo.dev vNext from ILocalizer.id; adds optional error to checkAuth result; adds engineId parameter to createLocalizer and removes vNext import/handling.
Lingo.dev Localizer
packages/cli/src/cli/localizer/lingodotdev.ts
Adds optional engineId param, uses settings.auth.apiKey/apiUrl, throws if apiKey missing, adds triggerType and filePath into localize payload, returns processableData for no-op.
Removed vNext Localizer
packages/cli/src/cli/localizer/lingodotdev-vnext.ts
Deletes the entire vNext localizer implementation and its default export.
Processor Adjustments
packages/cli/src/cli/processor/index.ts, packages/cli/src/cli/processor/lingo.ts
Adds optional engineId to processor types and forwards it into engine construction/localize calls.
CLI Utilities & Platform
packages/cli/src/cli/utils/auth.ts, packages/cli/src/cli/utils/settings.ts, packages/cli/src/cli/cmd/ci/platforms/_base.ts, packages/cli/src/cli/cmd/login.ts, packages/cli/src/cli/utils/init-ci-cd.ts
Switches identity call to GET /users/me and header to X-API-Key; adds LINGO_API_KEY/LINGO_API_URL env support; removes vnext auth substructure; updates defaults to https://api.lingo.dev and related help/secret names.
SDK Core
packages/sdk/src/index.ts, packages/sdk/src/utils/observability.ts, packages/sdk/src/utils/observability.spec.ts
Default API → https://api.lingo.dev; unify headers to Content-Type + X-API-Key; use /process/localize, /process/recognize, /users/me; include engineId in request body when provided; remove vNext branching.
Tests & Specs
packages/sdk/src/index.spec.ts, packages/sdk/src/abort-controller.specs.ts
Update tests to pass/expect engineId where applicable and new endpoints (/process/*, /users/me); adjust suite names and request expectations.
Observability / Compiler
packages/compiler/src/utils/observability.ts, packages/new-compiler/src/utils/observability.ts
Switch endpoints to /users/me, method to GET, header to X-API-Key, update default API URL and Content-Type header casing.
Misc
packages/cli/src/cli/localizer/..., packages/cli/src/cli/processor/..., packages/spec/...
Type and signature updates across localizer/processor layers to accept optional engineId; removal of vNext-specific modules and helpers.

Sequence Diagram(s)

sequenceDiagram
  participant CLI as CLI (run/i18n/ci)
  participant Localizer as Localizer/Processor
  participant API as api.lingo.dev
  CLI->>Localizer: createLocalizer(provider, engineId?, apiKey)
  CLI->>Localizer: localize(request with filePath, triggerType, sessionId)
  Localizer->>API: POST /process/localize\nHeaders: X-API-Key: <apiKey>\nBody includes engineId?
  API-->>Localizer: 200 OK (localized data)
  Localizer-->>CLI: processed result
  CLI->>API: GET /users/me\nHeaders: X-API-Key: <apiKey>
  API-->>CLI: 200 OK (identity)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • vrcprl
  • cherkanovart

Poem

🐰 I hopped through code both old and new,
I nudged vNext gently into engineId's view.
X-API-Key now signs each little quest,
api.lingo.dev handles every request.
A joyful thump — migrations done, time to rest!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: vNext migration' directly aligns with the main objective of the PR, which is to remove the vNext/non-vNext split and unify CLI and SDK authentication paths.
Description check ✅ Passed The PR description is well-structured, covering all key sections including a clear summary, comprehensive changes list, completed testing checklist, and proper documentation of the migration.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/vnext-migration

Comment @coderabbitai help to get the list of available commands and usage tips.

@AndreyHirsa AndreyHirsa changed the title Feat/vnext migration feat: vNext migration Mar 5, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
packages/new-compiler/src/utils/observability.ts (1)

103-115: Consider deduplicating tryGetEmail across compiler packages.

This request block is now duplicated with packages/compiler/src/utils/observability.ts; extracting a shared helper would reduce future drift and avoid split fixes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/new-compiler/src/utils/observability.ts` around lines 103 - 115,
Extract the duplicated HTTP user lookup into a shared helper and replace both
copies of tryGetEmail with it: create a new exported function (e.g.,
fetchUserEmail or getUserEmailFromApi) that accepts apiUrl and apiKey, performs
the fetch to `${apiUrl}/users/me` with the same headers and error handling, and
returns the email or null; then update the existing tryGetEmail implementations
to call this shared helper (importing it) and remove the duplicated fetch logic
from both tryGetEmail functions so future changes are centralized.
packages/cli/src/cli/cmd/ci/index.ts (1)

90-92: Update missing-key guidance to include LINGO_API_KEY.

Line 91 currently suggests only LINGODOTDEV_API_KEY, but settings now accept LINGO_API_KEY as well.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/cli/cmd/ci/index.ts` around lines 90 - 92, Update the error
message emitted by the console.error call in the CI command (the console.error
in packages/cli/src/cli/cmd/ci/index.ts) to mention both accepted environment
variable names ("LINGODOTDEV_API_KEY" and "LINGO_API_KEY") along with the
existing guidance to use the --api-key flag; modify the string so it clearly
lists both env vars and the flag.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/cli/src/cli/cmd/ci/index.ts`:
- Around line 89-94: The auth failure currently just logs and returns (see the
check for settings.auth.apiKey and the other early returns), which exits with
code 0; change these early-return branches to terminate with a non-zero exit
code (e.g., call process.exit(1) or throw an error) so CI detects failure—update
the blocks that log via console.error and then return to instead log the error
and immediately call process.exit(1) (or throw) to fail the process.

In `@packages/cli/src/cli/cmd/run/setup.ts`:
- Around line 89-92: The current task's enabled predicate and execution call
assume ctx.localizer.validateSettings exists, which causes a runtime crash for
the "pseudo" localizer; in the async task (task: async (ctx, task) => { ... })
guard the call to ctx.localizer!.validateSettings by checking that ctx.localizer
is present and typeof ctx.localizer.validateSettings === "function" before
invoking it, and treat missing validateSettings as valid (or handle
appropriately) so the validationStatus access and .valid check cannot throw;
update both the enabled/decision logic and the call site to use this predicate
around validateSettings to avoid calling an undefined function.

In `@packages/cli/src/cli/utils/settings.ts`:
- Line 23: The deprecation warning logic needs to treat LINGO_API_KEY as a
migrated key so users with LINGO_API_KEY set (but still having REPLEXICA_API_KEY
present) don't get a false legacy warning; update the deprecation/legacy check
that currently examines env and REPLEXICA_API_KEY to also consider
env.LINGO_API_KEY (and any related conditional that uses env.LINGO_API_KEY ||
...) so the warning is suppressed when LINGO_API_KEY is present, ensuring the
logic that emits the legacy warning (the deprecation check/legacy warning branch
in settings.ts) accounts for the new key.

In `@packages/spec/src/config.ts`:
- Around line 610-624: The schema for v1.15 currently allows legacy vNext fields
so extendConfigDefinition.parse's schema.safeParse succeeds and skips
createUpgrader; update createSchema to explicitly reject vNext (e.g., include
vNext: Z.any().optional().refine(() => false) or mark it as Z.never()) so
configs containing vNext fail validation and are passed into createUpgrader,
ensuring createUpgrader logic (which maps vNext → engineId) runs; modify the
createSchema entry (the createSchema function and its returned object) to
explicitly forbid vNext while keeping engineId optional.

---

Nitpick comments:
In `@packages/cli/src/cli/cmd/ci/index.ts`:
- Around line 90-92: Update the error message emitted by the console.error call
in the CI command (the console.error in packages/cli/src/cli/cmd/ci/index.ts) to
mention both accepted environment variable names ("LINGODOTDEV_API_KEY" and
"LINGO_API_KEY") along with the existing guidance to use the --api-key flag;
modify the string so it clearly lists both env vars and the flag.

In `@packages/new-compiler/src/utils/observability.ts`:
- Around line 103-115: Extract the duplicated HTTP user lookup into a shared
helper and replace both copies of tryGetEmail with it: create a new exported
function (e.g., fetchUserEmail or getUserEmailFromApi) that accepts apiUrl and
apiKey, performs the fetch to `${apiUrl}/users/me` with the same headers and
error handling, and returns the email or null; then update the existing
tryGetEmail implementations to call this shared helper (importing it) and remove
the duplicated fetch logic from both tryGetEmail functions so future changes are
centralized.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bc024c29-d9e1-47cd-b6f3-83c06168ccd8

📥 Commits

Reviewing files that changed from the base of the PR and between c7110ca and 0a38c54.

📒 Files selected for processing (20)
  • .changeset/thirty-pots-judge.md
  • packages/cli/src/cli/cmd/ci/index.ts
  • packages/cli/src/cli/cmd/i18n.ts
  • packages/cli/src/cli/cmd/run/setup.ts
  • packages/cli/src/cli/localizer/_types.ts
  • packages/cli/src/cli/localizer/index.ts
  • packages/cli/src/cli/localizer/lingodotdev-vnext.ts
  • packages/cli/src/cli/localizer/lingodotdev.ts
  • packages/cli/src/cli/processor/index.ts
  • packages/cli/src/cli/processor/lingo.ts
  • packages/cli/src/cli/utils/auth.ts
  • packages/cli/src/cli/utils/settings.ts
  • packages/compiler/src/utils/observability.ts
  • packages/new-compiler/src/utils/observability.ts
  • packages/sdk/src/abort-controller.specs.ts
  • packages/sdk/src/index.spec.ts
  • packages/sdk/src/index.ts
  • packages/sdk/src/utils/observability.spec.ts
  • packages/sdk/src/utils/observability.ts
  • packages/spec/src/config.ts
💤 Files with no reviewable changes (1)
  • packages/cli/src/cli/localizer/lingodotdev-vnext.ts

Comment on lines +89 to +94
if (!settings.auth.apiKey) {
console.error(
"No API key provided. Set LINGODOTDEV_API_KEY environment variable or use --api-key flag.",
);
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Auth failures should fail CI with non-zero exit code.

At Line 93 and Line 104, early return exits successfully. CI can pass even when authentication is missing/invalid.

🛠️ Proposed fix
     if (!settings.auth.apiKey) {
       console.error(
         "No API key provided. Set LINGODOTDEV_API_KEY environment variable or use --api-key flag.",
       );
+      process.exitCode = 1;
       return;
     }

@@
     const auth = await authenticator.whoami();
     if (!auth) {
       console.error("Not authenticated");
+      process.exitCode = 1;
       return;
     }

Also applies to: 101-105

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/cli/cmd/ci/index.ts` around lines 89 - 94, The auth failure
currently just logs and returns (see the check for settings.auth.apiKey and the
other early returns), which exits with code 0; change these early-return
branches to terminate with a non-zero exit code (e.g., call process.exit(1) or
throw an error) so CI detects failure—update the blocks that log via
console.error and then return to instead log the error and immediately call
process.exit(1) (or throw) to fail the process.

Comment on lines +89 to 92
enabled: (ctx) => ctx.localizer?.id !== "Lingo.dev",
task: async (ctx, task) => {
const validationStatus = await ctx.localizer!.validateSettings!();
if (!validationStatus.valid) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Pseudo mode can crash in configuration validation.

At Line 89, the task is enabled for "pseudo", but pseudo localizer does not implement validateSettings, so Line 91 can throw at runtime.

🧩 Proposed fix
       {
         title: "Validating configuration",
-        enabled: (ctx) => ctx.localizer?.id !== "Lingo.dev",
+        enabled: (ctx) =>
+          ctx.localizer?.id !== "Lingo.dev" &&
+          ctx.localizer?.id !== "pseudo" &&
+          !!ctx.localizer?.validateSettings,
         task: async (ctx, task) => {
           const validationStatus = await ctx.localizer!.validateSettings!();
           if (!validationStatus.valid) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
enabled: (ctx) => ctx.localizer?.id !== "Lingo.dev",
task: async (ctx, task) => {
const validationStatus = await ctx.localizer!.validateSettings!();
if (!validationStatus.valid) {
enabled: (ctx) =>
ctx.localizer?.id !== "Lingo.dev" &&
ctx.localizer?.id !== "pseudo" &&
!!ctx.localizer?.validateSettings,
task: async (ctx, task) => {
const validationStatus = await ctx.localizer!.validateSettings!();
if (!validationStatus.valid) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/cli/cmd/run/setup.ts` around lines 89 - 92, The current
task's enabled predicate and execution call assume
ctx.localizer.validateSettings exists, which causes a runtime crash for the
"pseudo" localizer; in the async task (task: async (ctx, task) => { ... }) guard
the call to ctx.localizer!.validateSettings by checking that ctx.localizer is
present and typeof ctx.localizer.validateSettings === "function" before invoking
it, and treat missing validateSettings as valid (or handle appropriately) so the
validationStatus access and .valid check cannot throw; update both the
enabled/decision logic and the call site to use this predicate around
validateSettings to avoid calling an undefined function.

Comment on lines +610 to +624
createSchema: (baseSchema) =>
baseSchema.extend({
engineId: Z.string().optional(),
}),
createDefaultValue: (baseDefaultValue) => ({
...baseDefaultValue,
version: "1.15",
}),
createUpgrader: (oldConfig) => {
const { vNext, ...rest } = oldConfig as any;
return {
...rest,
version: "1.15",
...(vNext && !rest.engineId ? { engineId: vNext } : {}),
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

rg -n -A 20 'extendConfigDefinition.*=.*\(' packages/spec/src/config.ts | head -80

Repository: lingodotdev/lingo.dev

Length of output: 47


🏁 Script executed:

rg -n -B 5 -A 15 'safeParse\(rawConfig\)' packages/spec/src/config.ts

Repository: lingodotdev/lingo.dev

Length of output: 810


🏁 Script executed:

rg -n -B 5 -A 15 'const baseConfig = definition.parse' packages/spec/src/config.ts

Repository: lingodotdev/lingo.dev

Length of output: 653


🏁 Script executed:

rg -n -B 2 -A 2 'configV1_15Definition' packages/spec/src/config.ts

Repository: lingodotdev/lingo.dev

Length of output: 422


🏁 Script executed:

sed -n '605,627p' packages/spec/src/config.ts

Repository: lingodotdev/lingo.dev

Length of output: 676


🏁 Script executed:

rg -n -B 5 -A 10 'configV1_14Definition' packages/spec/src/config.ts | tail -40

Repository: lingodotdev/lingo.dev

Length of output: 1105


🏁 Script executed:

rg -n 'vNext' packages/spec/src/config.ts

Repository: lingodotdev/lingo.dev

Length of output: 349


🏁 Script executed:

sed -n '490,510p' packages/spec/src/config.ts

Repository: lingodotdev/lingo.dev

Length of output: 567


🏁 Script executed:

sed -n '500,580p' packages/spec/src/config.ts

Repository: lingodotdev/lingo.dev

Length of output: 2314


🏁 Script executed:

rg -n -B 10 'createUpgrader:' packages/spec/src/config.ts | grep -A 10 'v1_12\|v1_13\|v1_14'

Repository: lingodotdev/lingo.dev

Length of output: 47


🏁 Script executed:

rg -n 'baseSchema.extend\|baseSchema.omit\|baseSchema.pick' packages/spec/src/config.ts | head -20

Repository: lingodotdev/lingo.dev

Length of output: 47


🏁 Script executed:

sed -n '55,95p' packages/spec/src/config.ts

Repository: lingodotdev/lingo.dev

Length of output: 1234


🏁 Script executed:

rg -n 'const extendConfigDefinition' packages/spec/src/config.ts

Repository: lingodotdev/lingo.dev

Length of output: 100


🏁 Script executed:

sed -n '48,95p' packages/spec/src/config.ts

Repository: lingodotdev/lingo.dev

Length of output: 1521


🏁 Script executed:

rg -n 'vNext.*Z\.never\|baseSchema\.omit.*vNext' packages/spec/src/config.ts

Repository: lingodotdev/lingo.dev

Length of output: 47


🏁 Script executed:

sed -n '587,605p' packages/spec/src/config.ts

Repository: lingodotdev/lingo.dev

Length of output: 490


🏁 Script executed:

cat -n packages/spec/src/config.ts | sed -n '45,100p'

Repository: lingodotdev/lingo.dev

Length of output: 2109


🏁 Script executed:

rg -n 'Z\.never' packages/spec/src/config.ts

Repository: lingodotdev/lingo.dev

Length of output: 47


🏁 Script executed:

# Check if vNext was ever explicitly removed or rejected in schema chain
rg -n 'omit\|pick\|exclude' packages/spec/src/config.ts | grep -i 'vNext\|v1_'

Repository: lingodotdev/lingo.dev

Length of output: 47


🏁 Script executed:

# Check the complete schema inheritance chain more clearly
cat -n packages/spec/src/config.ts | sed -n '495,515p'

Repository: lingodotdev/lingo.dev

Length of output: 709


🏁 Script executed:

# Look for test files that might validate this behavior
find . -name '*.test.ts' -o -name '*.spec.ts' | xargs grep -l 'vNext\|configV1_15\|engineId' 2>/dev/null | head -5

Repository: lingodotdev/lingo.dev

Length of output: 97


🏁 Script executed:

# Check if there's any omit/pick pattern used anywhere to remove fields
rg -n '\.omit\(|\.pick\(' packages/spec/src/config.ts

Repository: lingodotdev/lingo.dev

Length of output: 47


🏁 Script executed:

cat packages/sdk/src/index.spec.ts | grep -A 20 -B 5 'vNext\|engineId' | head -60

Repository: lingodotdev/lingo.dev

Length of output: 2035


🏁 Script executed:

# Verify the actual schema chain - does v1.14 keep vNext visible?
rg -n 'configV1_14Definition\|configV1_13Definition' packages/spec/src/config.ts -A 1 | head -20

Repository: lingodotdev/lingo.dev

Length of output: 47


🏁 Script executed:

# Check if the baseSchema in v1.15 truly carries vNext from prior versions
# by looking at what gets passed to createSchema
cat -n packages/spec/src/config.ts | sed -n '595,625p'

Repository: lingodotdev/lingo.dev

Length of output: 1041


🏁 Script executed:

# Trace schema inheritance - check what baseSchema contains when it reaches v1.15
# Check if there's any test that validates the migration behavior
rg -n 'vNext.*engineId\|migration\|upgrade' packages/sdk/src/index.spec.ts -i

Repository: lingodotdev/lingo.dev

Length of output: 47


🏁 Script executed:

# Verify behavior: does a config with vNext pass v1.15 schema validation?
# Look for any test files in the spec package
find packages/spec -name '*.test.ts' -o -name '*.spec.ts' 2>/dev/null

Repository: lingodotdev/lingo.dev

Length of output: 163


🏁 Script executed:

# Get file size to understand scope
wc -l packages/spec/src/config.ts

Repository: lingodotdev/lingo.dev

Length of output: 96


🏁 Script executed:

# Check config tests for migration behavior
rg -n 'vNext\|engineId\|v1_15' packages/spec/src/config.spec.ts -B 3 -A 10

Repository: lingodotdev/lingo.dev

Length of output: 47


🏁 Script executed:

# Check if there's test about schema validation or migration
cat packages/spec/src/config.spec.ts | head -100

Repository: lingodotdev/lingo.dev

Length of output: 2412


🏁 Script executed:

# Search the entire config.spec.ts for vNext or engineId
grep -n 'vNext\|engineId' packages/spec/src/config.spec.ts

Repository: lingodotdev/lingo.dev

Length of output: 47


🏁 Script executed:

# Get the rest of the config.spec.ts file
wc -l packages/spec/src/config.spec.ts

Repository: lingodotdev/lingo.dev

Length of output: 101


🏁 Script executed:

# Check if there are any integration tests or e2e tests
find . -type f \( -name '*.test.ts' -o -name '*.spec.ts' \) | xargs grep -l 'v1_15\|vNext.*engineId' 2>/dev/null | head -10

Repository: lingodotdev/lingo.dev

Length of output: 47


🏁 Script executed:

# Let me verify the exact logic one more time by checking the full extendConfigDefinition implementation
cat -n packages/spec/src/config.ts | sed -n '45,95p'

Repository: lingodotdev/lingo.dev

Length of output: 1965


🏁 Script executed:

# Check what happens with baseSchema.extend - does it preserve parent fields?
# Try a simple test to understand Zod behavior
python3 << 'EOF'
# Simulating Zod extend behavior
# When you call baseSchema.extend({newField: ...})
# It preserves all parent schema fields AND adds new ones
# It does NOT automatically reject parent fields

# So v1.15's baseSchema (from v1.14) contains vNext (inherited from v1.11)
# When v1.15 extends with {engineId: ...}, vNext is still there and optional
# This means a config with vNext will pass validation

print("Schema inheritance chain:")
print("v1.11: adds vNext as optional")
print("v1.12, v1.13, v1.14: inherit vNext (no changes)")
print("v1.15: extends with engineId as optional")
print("")
print("Result: v1.15 schema accepts vNext (inherited)")
print("If config has vNext but no engineId: passes validation")
print("If validation passes: upgrader never runs")
print("If upgrader never runs: vNext->engineId migration skipped")
EOF

Repository: lingodotdev/lingo.dev

Length of output: 412


vNextengineId auto-migration skipped for configs that pass schema validation.

The extendConfigDefinition.parse function returns immediately when schema.safeParse(rawConfig) succeeds (line 62-63), preventing the upgrader from running. Since v1.15's schema inherits vNext from v1.11 and never explicitly rejects it, a config containing vNext will pass validation and bypass the migration logic entirely.

To force the migration: explicitly reject vNext in v1.15's schema so configs containing it fail validation and are routed through the upgrader.

🔧 Proposed fix
 export const configV1_15Definition = extendConfigDefinition(
   configV1_14Definition,
   {
     createSchema: (baseSchema) =>
       baseSchema.extend({
         engineId: Z.string().optional(),
+        vNext: Z.never(),
       }),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
createSchema: (baseSchema) =>
baseSchema.extend({
engineId: Z.string().optional(),
}),
createDefaultValue: (baseDefaultValue) => ({
...baseDefaultValue,
version: "1.15",
}),
createUpgrader: (oldConfig) => {
const { vNext, ...rest } = oldConfig as any;
return {
...rest,
version: "1.15",
...(vNext && !rest.engineId ? { engineId: vNext } : {}),
};
createSchema: (baseSchema) =>
baseSchema.extend({
engineId: Z.string().optional(),
vNext: Z.never(),
}),
createDefaultValue: (baseDefaultValue) => ({
...baseDefaultValue,
version: "1.15",
}),
createUpgrader: (oldConfig) => {
const { vNext, ...rest } = oldConfig as any;
return {
...rest,
version: "1.15",
...(vNext && !rest.engineId ? { engineId: vNext } : {}),
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/spec/src/config.ts` around lines 610 - 624, The schema for v1.15
currently allows legacy vNext fields so extendConfigDefinition.parse's
schema.safeParse succeeds and skips createUpgrader; update createSchema to
explicitly reject vNext (e.g., include vNext: Z.any().optional().refine(() =>
false) or mark it as Z.never()) so configs containing vNext fail validation and
are passed into createUpgrader, ensuring createUpgrader logic (which maps vNext
→ engineId) runs; modify the createSchema entry (the createSchema function and
its returned object) to explicitly forbid vNext while keeping engineId optional.

@AndreyHirsa AndreyHirsa marked this pull request as draft March 5, 2026 22:20
@AndreyHirsa AndreyHirsa marked this pull request as ready for review March 7, 2026 09:26
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/cli/src/cli/utils/settings.ts (1)

204-209: ⚠️ Potential issue | 🟡 Minor

Info message should also check LINGO_API_KEY.

The condition only checks LINGODOTDEV_API_KEY but LINGO_API_KEY is now the primary environment variable. Users setting LINGO_API_KEY won't see the info message when it overrides systemFile.auth?.apiKey.

🔧 Proposed fix
-  if (env.LINGODOTDEV_API_KEY && systemFile.auth?.apiKey) {
+  if ((env.LINGO_API_KEY || env.LINGODOTDEV_API_KEY) && systemFile.auth?.apiKey) {
     console.info(
       "\x1b[36m%s\x1b[0m",
-      `ℹ️  Using LINGODOTDEV_API_KEY env var instead of credentials from user config`,
+      `ℹ️  Using ${env.LINGO_API_KEY ? 'LINGO_API_KEY' : 'LINGODOTDEV_API_KEY'} env var instead of credentials from user config`,
     );
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/cli/utils/settings.ts` around lines 204 - 209, Update the
conditional that currently checks env.LINGODOTDEV_API_KEY when deciding to log
the info about using an env var over systemFile.auth?.apiKey so it also checks
env.LINGO_API_KEY (the new primary env var); specifically modify the condition
that references env.LINGODOTDEV_API_KEY and the info log text (in the same block
that inspects systemFile.auth?.apiKey) to account for either environment
variable being present so users who set LINGO_API_KEY see the same message.
♻️ Duplicate comments (1)
packages/cli/src/cli/cmd/ci/index.ts (1)

75-91: ⚠️ Potential issue | 🔴 Critical

Auth failures should fail CI with non-zero exit code.

Both early return statements (Lines 79 and 90) exit successfully with code 0. CI pipelines will incorrectly pass when authentication is missing or invalid.

🛠️ Proposed fix
     if (!settings.auth.apiKey) {
       console.error(
         "No API key provided. Set LINGO_API_KEY environment variable or use --api-key flag.",
       );
+      process.exitCode = 1;
       return;
     }

     const authenticator = createAuthenticator({
       apiUrl: settings.auth.apiUrl,
       apiKey: settings.auth.apiKey,
     });

     const auth = await authenticator.whoami();
     if (!auth) {
       console.error("Not authenticated");
+      process.exitCode = 1;
       return;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/cli/cmd/ci/index.ts` around lines 75 - 91, The CI command
currently returns early on auth failures which yields exit code 0; in the auth
checks around settings.auth.apiKey and after awaiting authenticator.whoami(),
replace the plain returns with a non-zero process exit (e.g., call
process.exit(1)) after logging the error so the process fails the CI build;
locate the logic that constructs the authenticator via createAuthenticator and
the auth check using authenticator.whoami() to apply this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/cli/src/cli/utils/init-ci-cd.ts`:
- Line 108: The workflow template currently hard-codes the secret line "api-key:
${{ secrets.LINGO_API_KEY }}", which will break repos that still use a different
secret name; change the writer in init-ci-cd so it preserves existing secret
names or prompts a clear migration warning before overwriting workflows: detect
if a workflow file already exists and either (a) keep its existing secret key
entry instead of replacing it with "secrets.LINGO_API_KEY", or (b) if you must
replace it, emit an explicit interactive/CLI migration warning and require
confirmation; update the code path that emits the template (the code that writes
the "api-key: ${{ secrets.LINGO_API_KEY }}" line) to implement this
preserve-or-warn behavior.

---

Outside diff comments:
In `@packages/cli/src/cli/utils/settings.ts`:
- Around line 204-209: Update the conditional that currently checks
env.LINGODOTDEV_API_KEY when deciding to log the info about using an env var
over systemFile.auth?.apiKey so it also checks env.LINGO_API_KEY (the new
primary env var); specifically modify the condition that references
env.LINGODOTDEV_API_KEY and the info log text (in the same block that inspects
systemFile.auth?.apiKey) to account for either environment variable being
present so users who set LINGO_API_KEY see the same message.

---

Duplicate comments:
In `@packages/cli/src/cli/cmd/ci/index.ts`:
- Around line 75-91: The CI command currently returns early on auth failures
which yields exit code 0; in the auth checks around settings.auth.apiKey and
after awaiting authenticator.whoami(), replace the plain returns with a non-zero
process exit (e.g., call process.exit(1)) after logging the error so the process
fails the CI build; locate the logic that constructs the authenticator via
createAuthenticator and the auth check using authenticator.whoami() to apply
this change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 89f44a17-435e-4701-a359-2c30ba7dfcfa

📥 Commits

Reviewing files that changed from the base of the PR and between 844b36d and fa9fa63.

📒 Files selected for processing (6)
  • packages/cli/src/cli/cmd/ci/index.ts
  • packages/cli/src/cli/cmd/ci/platforms/_base.ts
  • packages/cli/src/cli/cmd/login.ts
  • packages/cli/src/cli/localizer/lingodotdev.ts
  • packages/cli/src/cli/utils/init-ci-cd.ts
  • packages/cli/src/cli/utils/settings.ts

- uses: lingodotdev/lingo.dev@main
with:
api-key: \${{ secrets.LINGODOTDEV_API_KEY }}
api-key: \${{ secrets.LINGO_API_KEY }}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid breaking existing GitHub CI secrets on overwrite.

Line 108 now hard-codes secrets.LINGO_API_KEY. If an existing repo re-runs init-ci-cd and accepts overwrite, the next workflow run will fail unless the maintainer has already renamed the repository secret. Please keep the scaffold backward-compatible here, or emit an explicit migration warning before writing the file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/cli/utils/init-ci-cd.ts` at line 108, The workflow template
currently hard-codes the secret line "api-key: ${{ secrets.LINGO_API_KEY }}",
which will break repos that still use a different secret name; change the writer
in init-ci-cd so it preserves existing secret names or prompts a clear migration
warning before overwriting workflows: detect if a workflow file already exists
and either (a) keep its existing secret key entry instead of replacing it with
"secrets.LINGO_API_KEY", or (b) if you must replace it, emit an explicit
interactive/CLI migration warning and require confirmation; update the code path
that emits the template (the code that writes the "api-key: ${{
secrets.LINGO_API_KEY }}" line) to implement this preserve-or-warn behavior.

@AndreyHirsa AndreyHirsa merged commit 73a8c73 into main Mar 7, 2026
9 checks passed
@AndreyHirsa AndreyHirsa deleted the feat/vnext-migration branch March 7, 2026 11:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants