You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+36-3Lines changed: 36 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -5,6 +5,42 @@ All notable changes to this project will be documented in this file.
5
5
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
8
+
## [Unreleased]
9
+
10
+
### Added
11
+
12
+
-**write**: `deepl write --to <language>` is now accepted as a long-only alias of `--lang`. The alias exists so users can reach for `--to` uniformly across `deepl translate` and `deepl write` — the single most common vocabulary split flagged in cross-command usage. `--lang` / `-l` remain fully supported; nothing deprecated. The short form `-t` is intentionally **not** bound on `write` (it would collide with `deepl translate -t, --to`). Passing both `--to` and `--lang` with different values exits with a `ValidationError`; passing the same value works fine.
13
+
-**docs**: `docs/API.md` gained a one-paragraph callout distinguishing `deepl sync --locale` (filter over locales already configured in `.deepl-sync.yaml#target_locales`) from `deepl translate --to` (invocation-time target-language specifier). The split is semantic — sync owns its locale mapping via config; translate does not — and documenting the distinction is the right fix rather than forcing one to compromise for surface symmetry.
14
+
-**sync**: new `sync.limits.max_source_files` config field. Caps how many source files a single bucket's `include` glob may match before the bucket is skipped with a warning. Default `10000`, hard ceiling `1000000`. Guards against a misconfigured `**/*.json` that accidentally picks up a vendored subtree. Sibling fields `max_entries_per_file` / `max_file_bytes` / `max_depth` gate individual files; this one gates the whole bucket because processing the first N of an oversized glob would silently drop the rest. Narrow the pattern or raise the cap in `.deepl-sync.yaml`.
15
+
16
+
### Changed
17
+
18
+
-**sync exit codes**: `deepl sync` partial-failure (one or more locales failed while others succeeded) now exits **12** instead of 1. Exit 1 now means strictly "unclassified CLI failure." A prior version aliased `ExitCode.PartialFailure` to `GeneralError` (both `1`), which prevented CI scripts from telling a partial sync outcome from a CLI crash. With this change, CI can safely branch on `$? -eq 12` and retry only the failed locales via `deepl sync --locale <failed,comma,separated>`. The paired typed error class `SyncPartialFailureError` (exit 12, envelope `code: "SyncPartialFailure"`) is added to `src/utils/errors.ts`, mirroring `SyncDriftError` (10) and `SyncConflictError` (11). Migration: any CI script that branched on `$? -eq 1` to detect partial sync failure should switch to `$? -eq 12`; a generic `$? -ne 0` check continues to work unchanged.
19
+
-**sync drift exit**: `deepl sync --frozen` now exits *soft* (sets `process.exitCode = 10` and returns from the action handler) instead of calling `process.exit(10)` directly. Observable exit code is unchanged at 10; the internal change lets in-flight writes, auto-commit steps, and any `--watch` event loop drain cleanly before the process exits. `docs/API.md` has promised this shape since 1.1.0 but the implementation drifted to a hard exit — now aligned.
20
+
-**tests**: The shared `tests/setup.ts``afterEach` hook now asserts that every `nock` interceptor registered during a test actually fired. An unasserted mock (registered scope with no matching request) now throws with the pending interceptor list, surfacing silent test gaps where the SUT never exercised the mocked network call. No test changes were required — the existing suite (49 integration files, 766 tests, plus unit + E2E = 4501 tests) already had clean hygiene. Negative-path tests that intentionally register non-firing interceptors can opt out by calling `nock.cleanAll()` from their own `afterEach` before the shared hook runs.
21
+
-**cache**: SQLite cache DB now carries a schema version via `PRAGMA user_version`. Fresh DBs are stamped at version 1; pre-versioned DBs (created before this field existed) report 0 and are upgrade-stamped in place — no data migration, no user-visible change. Opening a DB whose version is **newer** than the CLI supports now fails with a `ConfigError` rather than risking data loss.
22
+
-**cache**: Corrupted cache databases are now backed up aside as `cache.db.corrupt-<timestamp>` (plus any `-wal` / `-shm` sidecars) instead of being unlinked. Users keep their 30-day cache contents and a forensic artifact for post-mortem; the CLI creates a fresh DB alongside and continues. `Logger.warn` names the backup path.
23
+
-**http**: Retry backoff now uses **full jitter** (AWS-recommended variant): the delay for attempt `n` is a uniform random value in `[0, min(INIT * 2^n, MAX)]` rather than the fixed `min(INIT * 2^n, MAX)`. Concurrent clients (e.g., parallel sync buckets) that all hit 429 at the same moment no longer form a thundering herd on the retry. The `Retry-After` header path is unchanged — server-specified delays are honored verbatim.
24
+
-**http**: Retries now emit a `Logger.verbose` line per retry decision naming the attempt number, delay, and reason (429 with Retry-After, 429 with jitter backoff, or generic network error). Previously retries were silent; a user seeing elevated latency had no visibility into whether the CLI was backing off or stuck.
25
+
26
+
### Fixed
27
+
28
+
-**docs**: `docs/API.md` and `docs/SYNC.md` now document the `--format FORMAT` option on `deepl sync export` (previously undocumented even though the flag was registered in `src/cli/commands/sync/register-sync-export.ts`). Clarified that on `sync export` the format choice affects only the error envelope on stderr; the success output is always XLIFF 1.2.
29
+
-**docs**: `docs/API.md` corrected the note on the `audit` subcommand rename — the previous wording said "Prior to the 1.0.0 release, this subcommand was named `glossary-report`", which implied 1.0.0 users had access to it. The prototype name `glossary-report` never shipped in any tagged release; now worded consistently with the 1.1.0 CHANGELOG entry.
30
+
-**write**: `deepl write --interactive` now fails fast with a `ValidationError` when stdin is not a TTY (e.g., a CI job that passes `--interactive` without `--no-input`). Previously the process would hang indefinitely on an `@inquirer/prompts``select` call that a non-TTY stream can never answer.
31
+
-**translate**: `deepl translate --format table` now falls back to plain `[lang] text` output with a `WARN` line on stderr when stdout is not a TTY. Screen readers and log scrapers no longer have to parse `cli-table3`'s Unicode box-drawing characters; pipe `--format table > out.txt` produces parseable plain text instead of Unicode noise.
32
+
-**output**: Spinners (`ora`) are now gated on `process.stderr.isTTY` at the `Logger.shouldShowSpinner()` chokepoint. CI and piped-stderr contexts no longer risk ANSI escape leaks into log files regardless of which callsite instantiates the spinner.
33
+
-**color**: `NO_COLOR` is now explicitly honored in the CLI bootstrap by setting `chalk.level = 0` when the env var is present. `chalk` already auto-detects `NO_COLOR`, but the explicit hook keeps the two color-detection paths in the codebase (`chalk` and `isColorEnabled()` in `utils/formatters.ts`) unambiguously in sync if `chalk`'s auto-detection ever changes or is mocked in tests.
34
+
-**sync init**: a bare `process.exit(7)` literal in `register-sync-init.ts`'s JSON-output path now goes through `ExitCode.ConfigError` so future exit-code renumbering can't desync the hard-coded value from the rest of the codebase.
35
+
-**xliff**: the chained-`.replace()` XML entity decoder double-decoded entities. `&lt;` (the literal 5-character string "`<`") was silently collapsed to "`<`" because the decoder ran `&` → `&` on the first pass and then `<` → `<` on the second. Replaced with a single-pass regex that handles the five named entities plus decimal (`&#NN;`) and hex (`&#xNN;`) numeric character references, retiring both bugs in one stroke. Round-tripping literal entities through translation now preserves them byte-for-byte.
36
+
-**xliff**: CDATA sections inside `<source>` / `<target>` elements were silently malformed on round-trip — `<` / `>` bytes inside a CDATA body round-tripped asymmetrically through the escape pass. The parser now rejects CDATA inside translatable elements with a `ValidationError` at extract time, matching the allowlist posture of the Laravel PHP parser's heredoc / interpolation rejection. CDATA in non-translatable positions (e.g., `<note>`) is still accepted.
37
+
38
+
### Security
39
+
40
+
-**sync**: `.deepl-sync.yaml` and auto-detect-path reads (`package.json`, the first-match i18n file for key counting) now route through `safeReadFileSync`, which rejects symbolic links with a `ValidationError`. A hostile repo could previously ship a `.deepl-sync.yaml` symlinked to `~/.ssh/id_rsa` (or another dotfile outside the project root) and surface the target's contents in YAML parser errors on stderr. Affected sites: `src/sync/sync-config.ts:566`, `src/sync/sync-init.ts:239`, `src/sync/sync-init.ts:271`. Runtime file reads during `deepl sync` itself are unchanged — the bucket walker already refuses symlinks via `fast-glob`'s `followSymbolicLinks: false`.
41
+
-**http**: When `HTTP_PROXY` / `HTTPS_PROXY` is configured with an `http://` proxy and the target endpoint is `https://`, the CLI now emits a startup warning naming the proxy host and noting the TLS-termination MITM risk. TLS is still tunneled end-to-end via CONNECT, so this is a visibility fix rather than a behavior change — a malicious proxy that terminates TLS with a trusted cert would see the Authorization header, and the user should be aware. Users with legitimate corporate http-only proxies see the warning but the connection proceeds (no forced abort; the CLI can't tell corporate infra apart from attacker infra).
42
+
-**log redaction**: `Logger`'s sanitizer now redacts `X-Api-Key` and `X-Auth-Token` headers, plus `?api_key=` / `?apikey=` query parameters. Previously only `DeepL-Auth-Key`, `Authorization: ApiKey|Bearer`, `?token=` / `&token=`, and the `DEEPL_API_KEY` / `TMS_API_KEY` / `TMS_TOKEN` env-var exact values were covered. axios error dumps that include `config.headers` on TMS-style third-party backends (e.g., Phrase, Lokalise, custom REST endpoints) no longer leak these credentials via verbose logs.
43
+
8
44
## [1.1.0] - 2026-04-23
9
45
10
46
### Added
@@ -77,9 +113,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
113
78
114
### Changed
79
115
80
-
- Voice API no longer hardcodes the Pro endpoint; it follows the same endpoint resolution as all other commands
81
-
-`auth set-key` and `init` now validate entered keys against the correct endpoint based on key suffix
82
-
- Standard DeepL URLs (`api.deepl.com`, `api-free.deepl.com`) in saved config no longer override key-based auto-detection
83
116
-**`deepl sync` cost estimates now labeled as Pro tier**: text-mode output appends `(Pro tier estimate)` to all cost lines (both dry-run and post-sync). The `--format json` output carries `rateAssumption: "pro"`. `docs/SYNC.md` now documents the Pro-rate assumption ($25/1M chars) and points users to their account page to determine the applicable rate for their tier.
84
117
-**`deepl sync --format json` output contract stabilized**: the success JSON payload is now a curated `SyncJsonOutput` shape (`ok`, `totalKeys`, `translated`, `skipped`, `failed`, `targetLocaleCount`, `estimatedCharacters`, `estimatedCost?`, `rateAssumption: "pro"`, `dryRun`, `perLocale[]`) instead of a raw internal spread. The public shape is documented in docs/API.md and guaranteed stable across 1.x.
85
118
-**`deepl sync init` no-detection exit**: when the auto-detector finds no recognized i18n files, the command now exits 7 (`ConfigError`) instead of 0, and prints an actionable remediation hint listing all four required flags (`--source-locale`, `--target-locales`, `--file-format`, `--path`). In `--format json` mode the canonical error envelope (`{ok:false, error:{code:"ConfigError",...}, exitCode:7}`) is emitted to stderr. Scripts that previously relied on exit 0 in empty projects must be updated to handle exit 7.
0 commit comments