Skip to content

Stack: kml-to-mif + schema-driven UI + docs refresh + CLI - #5

Merged
fedyunin merged 4 commits into
mainfrom
feat/cli
May 19, 2026
Merged

Stack: kml-to-mif + schema-driven UI + docs refresh + CLI#5
fedyunin merged 4 commits into
mainfrom
feat/cli

Conversation

@fedyunin

@fedyunin fedyunin commented May 19, 2026

Copy link
Copy Markdown
Owner

Summary

Bundled merge of the kml-to-mif converter, schema-driven UI dispatch, docs refresh, and CLI on top of the registry caved-in by #1. Four logical PRs (originally #2 #3 #4 #5) collapsed into one merge after their stacked bases got removed during #1's merge.

Commits in this PR (each one was a separate PR review-wise before the stack broke):

What lands

kml-to-mif converter — KML/KMZ → MapInfo MIF/MID, preserving per-feature colors via styleUrl → StyleMap → Style resolution, with Name / Description / StyleId / Folder attribute columns. Byte-identical to the reference Python script on a real 1854-feature dataset.

Schema-driven UI — renderer reads the converter registry on startup and builds its dropdown + options form from each converter's options[] schema. Adding a converter requires no UI code.

Settings v2 + migration — per-converter option storage, legacy flat keys auto-migrated from 1.0.x users on first load.

mifkit CLImifkit list, mifkit help <id>, mifkit convert <id> <inputs...> --output=<dir> [--flag=value]. Same registry, same validation, same run() as the GUI. stdout = output paths, stderr = logs. Bin registered in package.json, also bundled into the electron-builder installer.

Docs — README has Converter table, CLI section, "Adding a new converter" guide. PROJECT_CONTEXT is a full rewrite for the toolkit shape (three layers, contract, settings, quality bar).

Tests

102 passing locally (npm test):

  • registry + validation (5)
  • converter dispatch (4)
  • color / zip / mid / mif / csv / excel / encoding (existing 41)
  • kml-to-mif schema + nested folders + flat + KMZ + UTF-8 charset (5)
  • settings v1 → v2 migration (6)
  • runConversion integration (7)
  • CLI parseArgs (5)
  • CLI coerceOptions (7)
  • CLI runCli end-to-end (9)

Smoke

  • npm test is green
  • ./bin/mifkit.js convert kml-to-mif "Караганда 108.kmz" --output=/tmp/out --flat processed all 1854 features into 18 files, exit 0
  • byte-for-byte diff vs the original Python reference output → identical

🤖 Generated with Claude Code

fedyunin and others added 4 commits May 19, 2026 23:59
Adds the second converter through the registry contract introduced in PR1.
Produces byte-identical output to the reference Python script
(kml_to_mif.py) on a real 1854-feature dataset (Karaganda 108 KMZ).

What it does
- Resolves per-feature colors by following styleUrl -> StyleMap(normal)
  -> Style chains. KML AABBGGRR -> MapInfo decimal RGB int.
- Preserves <name> (label), <description>, and the resolved StyleId as
  MID attribute columns. Adds a Folder column with the full slash-joined
  KML folder path so the hierarchy is queryable in MapInfo SQL.
- Lays files out either as nested directories matching the KML Folder
  tree (default) or one flat directory with `parent__child` prefixes
  (`flat: true`) so they can be merged without TAB name collisions.
- Charset option: WindowsCyrillic (cp1251, default) for Russian/Kazakh
  data, or Neutral (UTF-8) for MapInfo Pro 15.2+.
- Supports KML and KMZ. KMZ is unzipped in-process via a minimal pure-JS
  ZIP reader (stored + deflate) — no native deps.
- Skips placemarks without geometry; reports per-file errors in stats.

New modules
- src/core/common/color.js       KML AABBGGRR <-> MapInfo int <-> #RRGGBB
- src/core/common/zip.js         minimal ZIP reader (zlib only)
- src/core/converters/kml-to-mif/
    parseKml.js                  KML parsing (fast-xml-parser) +
                                 Style/StyleMap collection + resolution
    writeMif.js                  MIF/MID writer with CRLF normalization
                                 and Unicode-safe filename sanitization
    index.js                     Converter definition + run()
- test/fixtures/sample.kml       polygons + line + point + StyleMap

New dependency: fast-xml-parser (zero runtime deps, MIT, popular)

Tests: 75 passing (62 from PR1 + 13 new)
- color: AABBGGRR / BBGGRR / fallback / hex round-trip
- zip: deflate + stored extraction, rejects non-ZIP buffers
- kml-to-mif: schema, full folder dispatch, flat layout, KMZ archive,
  Neutral charset, resolved Pen/Brush/Symbol values per feature

Verified end-to-end: `diff -r` of the original Python output against
the JS output on the 1854-feature Karaganda 108 KMZ shows no
differences across all 18 produced files.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
UI and worker now talk to the registry instead of the legacy convert.js
engine directly. Adding a converter is one folder under
src/core/converters/ — the desktop app auto-renders its options panel
from the option schema; no UI changes required to surface it.

Renderer
- New "Converter" card with a dropdown of registered converters and a
  per-converter description line.
- "Options" card rendered dynamically from the selected converter's
  options[] schema (boolean -> checkbox, enum -> select, number ->
  number input, string -> text). Saved per-converter so switching
  direction keeps each one's last values.
- Drop-zone and file picker now filter by the current converter's
  inputs.extensions (e.g. .mif/.mid for mif-to-xlsx, .kml/.kmz for
  kml-to-mif).
- Removed all converter-specific HTML/JS for the Excel exporter; the
  same form generator handles both shipped converters.

Worker / IPC
- worker.js dispatches converters.get(id).run({inputs, output, options},
  ctx) and runs converters.validateOptions() before invoking run.
- main.js: new IPC channel converters:list returns a serializable lean
  view of the registry (id, name, description, io extensions, options).
  selectFiles accepts the current converter's allowed extensions.
- preload.js exposes listConverters() and the extension-aware
  selectFiles(options).
- convert:start payload moved from the legacy {inputMode, inputFolder,
  selectedFiles, recursive, paintRows, ...} bag to the new
  {converterId, inputs, output, options} contract.

Settings
- New format v2: shared top-level (language, inputMode, inputFolder,
  outputFolder, selectedFiles), plus converterId and converterOptions
  keyed by converter id.
- migrate() lifts legacy flat option keys (paintRows, skipBlack,
  combineIntoOneWorkbook, etc.) into converterOptions['mif-to-xlsx']
  so existing users see identical behavior on first launch.
- saveSettings stamps the version on every write so future migrations
  can branch on it.

i18n
- New shared keys: section.converter, field.converter,
  hint.optionsEmpty. Subtitle and drop hint reworded to be
  format-agnostic. English / Russian / Kazakh updated.
- Per-option labels currently come from the schema (English). Future
  PR can add optional i18nKey on options without touching the
  renderer.

Tests: 81 passing (75 + 6 settings migration). Renderer is browser-only
JS so it is not unit-tested here; verified via node --check.

Manual smoke (run by maintainer): `npm run dev`, pick "MapInfo -> Excel/CSV",
confirm the same form as 1.0.4 renders and conversion behavior is
unchanged; switch to "KML/KMZ -> MapInfo MIF/MID", confirm the new
options (flat, charset, recursive) render and a real KMZ converts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both docs now reflect the post-rebrand reality: a registry of
converters, a schema-driven UI, and kml-to-mif shipped.

README
- Converter table: mark mif-to-xlsx and kml-to-mif as shipped; keep
  the planned ones for direction.
- Replace the single-feature blurb with two feature sections (one per
  shipped converter) covering what each actually does.
- Add an "Adding a new converter" section with a runnable skeleton so
  contributors can land a new format without reading the whole core.
- Trim and reorder project structure to match the current src/core/
  layout, including src/core/common/ and src/core/converters/.

PROJECT_CONTEXT
- Full rewrite. Old doc only described the original MIF -> XLSX
  desktop app; new doc describes the toolkit, the three-layer
  architecture (core / shell / planned CLI), the Converter contract,
  and how the GUI talks to the engine.
- Tables: shipped converters (with implementation notes), planned
  converters (with rationale).
- Input format assumptions split between read (MIF, MID, KML/KMZ) and
  write (MIF/MID from kml-to-mif), with the byte-level details that
  matter for compatibility (CRLF, charset, Pen/Brush/Symbol).
- Settings v2 shape documented + migration policy.
- Quality bar and non-goals updated for the toolkit scope.
- New "Definition of done for a new converter PR" and "Guidance for
  future AI assistants" sections.

No code touched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds bin/mifkit — a CLI front-end over the converter registry. The GUI
and CLI now share one engine: same options schema, same validation,
same run() function. Adding a converter still requires no CLI work;
it appears automatically in both surfaces.

Commands

  mifkit                              top-level help
  mifkit --version                    package version
  mifkit list                         list registered converters
  mifkit help <converter>             show one converter's schema
  mifkit convert <converter> <inputs...> --output=<dir> [options]

Conventions

  --key=value      string/enum/number options
  --key            boolean option set to true
  --no-key         boolean option set to false
  --output, -o     required for convert
  exit 0           success
  exit 1           run errors (per-file)
  exit 2           usage errors (unknown flag, missing --output, etc.)

Output produced by `convert` goes to stdout (one path per line) and
log/progress messages go to stderr, so output paths can be piped to
xargs / another tool without parsing logs.

New files

  bin/mifkit.js                       Shebang entry, delegates to runCli
  src/cli/index.js                    runCli(argv, io) — pure dispatch
                                      so tests can drive without spawn
  src/cli/parseArgs.js                argv -> { command, positional, flags }
  src/cli/coerceOptions.js            schema-driven flag coercion +
                                      unknown-option detection
  src/cli/format.js                   help / list / converter-help text

package.json
  - new "bin": { "mifkit": "bin/mifkit.js" }
  - electron-builder "files" now includes bin/** so the CLI lands in
    the desktop installer's resources too

Tests: 102 passing (81 + 21 CLI):
  - parseArgs (5): command/positional/flags, --no-*, embedded =, -h/-v
  - coerceOptions (7): booleans (string aliases), numbers, strings,
    enum strings, missing-value error, unknown-option error, reserved
    flag pass-through
  - runCli end-to-end (9): top help, --version, list, help <id>, help
    of unknown id, convert without args, convert without --output,
    convert with unknown option, full kml-to-mif on a fixture writes
    expected output filenames

Manual smoke verified on the real Karaganda 108.kmz:

  ./bin/mifkit.js convert kml-to-mif "Караганда 108.kmz" \
      --output=/tmp/out --flat
  # processes 1854 features in 9 layers, exit 0

Docs

README gains a "CLI" section with all conventions and an updated
project structure including bin/ and src/cli/. PROJECT_CONTEXT moves
the CLI from "planned" to "shipped" and includes the new files in
the layout block.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@fedyunin
fedyunin changed the base branch from feat/docs-refresh to main May 19, 2026 19:00
@fedyunin fedyunin changed the title feat: mifkit CLI built on the same registry as the GUI Stack: kml-to-mif + schema-driven UI + docs refresh + CLI May 19, 2026
@fedyunin
fedyunin merged commit cd34e8f into main May 19, 2026
@fedyunin
fedyunin deleted the feat/cli branch May 19, 2026 19:01
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