Skip to content

fix(config): don't migrate a settings.json with a non-numeric version as v1 - #680

Open
eric-engberg wants to merge 1 commit into
sirmalloc:mainfrom
eric-engberg:fix/settings-nonnumeric-version
Open

eric-engberg wants to merge 1 commit into
sirmalloc:mainfrom
eric-engberg:fix/settings-nonnumeric-version

Conversation

@eric-engberg

Copy link
Copy Markdown
Contributor

What

A settings.json whose "version" isn't a number ("4" or null) is now treated as an invalid file, like any other: the status line shows the ⚠ invalid config badge with the default line, the TUI shows its "not in a valid format" banner, and the file is left as it is. Before, ccstatusline ran the v1 migration on it and wrote the result back, which reset most of the user's settings.

Why

loadSettings decides a file is v1 by checking for a version key at all. needsMigration/detectVersion decide by typeof version === 'number' and call anything else v1. The two disagree for a version that's present but not a number. A current config with a quoted version, which a hand edit can produce:

{ "version": "4", "lines": [...], "powerline": { "enabled": true, "theme": "nord-aurora", ... }, "defaultPaddingSide": "both", ... }

skips v1 validation (it has a version key) and then runs the v1→v2 migration anyway (the version isn't a number). That migration builds a fresh config from lines and the nine v1 fields only. The result validates, so it's written over the file:

  • With the new test's fixture (powerline: { enabled: true }, minimalistMode: true), the file main writes back holds only lines with every widget id replaced by a new GUID, "version": 4, and an updatemessage. Powerline and minimalist mode are gone, and so is every other setting the v1 format didn't have.
  • Rendering the demo's Powerline config once on main leaves powerline.enabled: false and no theme, and prints "ccstatusline updated to v2.0.2, 5hr block timer widget added" under the status line, which keeps showing for about a dozen renders.

The load function's own contract says a file that fails validation is never overwritten; this input got around it.

How

needsMigration now returns false when version is present but not a number. Nothing else changes in the loader: the config goes straight to schema validation, which rejects it (version must be a number), and it takes the existing invalid-file path. That path keeps defaults in memory, records the load error for the badge and TUI banner, and doesn't write. Making the version a number again brings the user's settings back as they were.

Unchanged:

  • files with no version key still validate as v1 and migrate;
  • numeric versions below the current one still migrate and are written back once the result validates;
  • numeric versions at or above the current one load as before.

Import uses the same needsMigration, so importing a file with a non-numeric version is now rejected ("Invalid config format: Invalid input: expected number, received string") instead of being quietly reduced to its v1 fields in the preview. detectVersion and migrateConfig are untouched; every caller of migrateConfig either checks needsMigration first or only runs for files without a version key.

Demo

The demo's baseline configs with "version": 4 changed to "version": "4", rendered once from sample data (scripts/payload.example.json). Each still prints the file's version and Powerline flag, renders the status line, then prints them again. stderr is hidden.

Powerline: before

String version in Powerline mode before the fix: the file is rewritten with Powerline off and an update message

Powerline: after

String version in Powerline mode after the fix: invalid config badge, file unchanged

Plain: before

String version in plain mode before the fix: the file is rewritten with an update message

Plain: after

String version in plain mode after the fix: invalid config badge, file unchanged

Testing

  • New tests, all failing on main:
    • config.test.ts: a file with "version": "4" and one with "version": null load as defaults, keep the file byte-for-byte, write no backup, and set the "not in a valid format" load error;
    • config.test.ts: an import with a string version is rejected;
    • migrations.test.ts: needsMigration is false for a string or null version.
  • bun test: 2786 pass, 0 fail. bun run lint passes.
  • Under Node 26.10.0 (Vitest): config.test.ts and migrations.test.ts 150 pass (146 existing, 4 new). The full suite under Node gives 2611 of 2764 passing. The 153 failures are in the same 17 files that fail on main, all from spying on Node built-ins.
  • Built CLI under Bun 1.4.2 and Node 26.10.0:
    • baseline piped renders in plain and Powerline modes agree across runtimes and match main, and the TUI opens and exits cleanly under both;
    • with the string-version configs, main rewrites the file under both runtimes, and this branch renders the badge and leaves it unchanged.
  • TUI launched on the string-version Powerline config under Bun and Node: main rewrites the file on launch. This branch shows the "settings.json is not in a valid format" banner and leaves the file unchanged, the same screen main shows for any other invalid file (e.g. "lines": 42).

… as v1

loadSettings treats a file as v1 only when it has no `version` key, but
needsMigration/detectVersion treat any `version` that isn't a number as
v1. A current config with `"version": "4"` or `"version": null` (after a
hand edit, for example) therefore skipped v1 validation and then ran the
v1->v2 migration anyway. That migration rebuilds the config from `lines`
and the v1 fields only, so the result validated and was written back
over the file: Powerline, defaultPaddingSide, minimalistMode, separators
and every other v2+ setting were dropped, widget ids were regenerated,
and the status line announced "ccstatusline updated to v2.0.2".

needsMigration now reports no migration for a version that is present
but not a number. The config then reaches schema validation, which
rejects it, so it takes the existing invalid-file path: defaults in
memory, the load error recorded for the warning badge and TUI banner,
and the file left untouched. Import validation shares needsMigration, so
an import file with a non-numeric version is now rejected instead of
being reduced to its v1 fields.
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.

1 participant