honua-maplibre migration target

The honua-maplibre codemod target rewrites a curated subset of @arcgis/core constructors directly into @honua/sdk-js/map helpers plus MapLibre GL source/layer definitions, instead of routing them through the Esri-shaped compat shims. It is the codemod option that produces MapLibre-native output for apps that want to leave the ArcGIS JS API behind end-to-end.

This page documents the slice that shipped in PR #208 (commit af1ebee). It does not close issue #205 — open acceptance items are called out under Manual gaps.

How it differs from the other targets

The migration codemod (src/migration/codemod.ts) exposes three targets selected with --target on the CLI:

Target Output shape What it rewrites natively
honua-compat (alias honua) @honua/sdk-esri-compat shims that mimic the ArcGIS JS API surface Every kind in REWRITE_SPECS (SUPPORTED_ARCGIS_MODULE_KIND_BY_PATH) — full 2D constructor surface (layers, views, widgets, controls, tasks, support).
honua-maplibre @honua/sdk-js/map helpers + raw MapLibre style/layer objects The five kinds in HONUA_MAPLIBRE_NATIVE_KINDS: feature-layer, map-image-layer, tile-layer, map, map-view. Everything else still falls through to a manual TODO.
esri-leaflet Mixed esri-leaflet primitives + compat fallback feature-layer, map-image-layer, tile-layer natively; the remaining 2D surface via ESRI_LEAFLET_COMPAT_FALLBACK_KINDS.

honua-compat is the broadest deterministic option — it covers the full REWRITE_SPECS matrix and is the default. honua-maplibre trades surface coverage for a MapLibre-native output: it only emits native helpers for the layer/map/view core, and everything else (widgets, controls, renderers, geometry constructors, tasks) becomes a manual TODO because there is no @honua/sdk-js/map helper for it yet. esri-leaflet sits in between — three native layer kinds with a compat fallback for the rest.

Auto-migrated, assisted, and unsupported kinds

The exact set of kinds the honua-maplibre target rewrites natively is defined in src/migration/codemod.ts as HONUA_MAPLIBRE_NATIVE_KINDS and consumed by TARGET_SUPPORTED_KINDS["honua-maplibre"]:

The helper signatures live in src/map/maplibre-target.ts.

Rendering feature-service layers (custom source → geojson)

createHonuaTileServiceLayer and createHonuaMapServiceLayer emit MapLibre-native raster sources that render as soon as you add them to a map. createHonuaFeatureServiceLayer is different: its source uses the custom honua-feature-service type, which MapLibre's renderer does not understand. Adding that source to a maplibre-gl Map directly is a silent no-op — no error, no features.

To render feature-service layers, run them through the MapLibre runtime adapter exported from @honua/sdk-js/map. It fetches features via the SDK query path and produces a standard geojson source MapLibre renders natively:

import maplibregl from "maplibre-gl";
import { HonuaClient } from "@honua/sdk-js";
import {
  createHonuaFeatureServiceLayer,
  loadHonuaFeatureServiceGeoJson,
} from "@honua/sdk-js/map";

const client = new HonuaClient({ baseUrl: "https://gis.example.com" });
const layer = createHonuaFeatureServiceLayer({ url, outFields: ["*"] });

// Replace the custom honua-feature-service source with a fetched geojson source.
const source = await loadHonuaFeatureServiceGeoJson(client, layer.source.url);
map.addSource(layer.sourceId, source);
map.addLayer(layer.layer);

For a whole HonuaMap, registerHonuaFeatureServiceSources(map, honuaMap, client) walks every honua-feature-service source and live-patches it on the MapLibre map (adding a new geojson source or calling setData on an existing one). Both helpers live in src/map/feature-service-adapter.ts.

Every other constructor kind in REWRITE_SPECS — geometries, symbols, renderers, WebMap, SceneView, GraphicsLayer, GroupLayer, VectorTileLayer, GeoJSONLayer, WMSLayer, WFSLayer, ImageryLayer, FeatureFilter, all widgets (LayerList, Legend, Popup, Search, Sketch, Editor, TimeSlider, …), all controls (Home, Zoom, Compass, ScaleBar, Fullscreen, BasemapToggle, …), Query, OAuthInfo, IdentityManager, esriRequest, esriConfig, reactiveUtils, RouteTask, etc. — is not in HONUA_MAPLIBRE_NATIVE_KINDS. The codemod emits a manual TODO with a reason at each call site and lists the unhandled ArcGIS module under unhandledArcGisModules in the report. The runtime parity matrix (src/migration/runtime-matrix.ts::inferHonuaMapLibreRuntimeStatus) categorizes these surfaces as assisted for honua-maplibre, except for the feature-layer, map-image-layer, and the map-view.navigation-go-to capability which are tagged native.

Use the canonical fixture test/fixtures/esri-maplibre-simple-app/ as a worked example — it imports Map, MapView, FeatureLayer, MapImageLayer, and TileLayer from @arcgis/core and exercises exactly the kinds that honua-maplibre can rewrite natively.

CLI flag and invocations

The codemod is selected with --target honua-maplibre on the codemod, fixtures, and demo subcommands of node dist/src/migration/cli.js. The accepted target tokens are honua (alias of honua-compat), honua-compat, honua-maplibre, and esri-leaflet — anything else is rejected by the parser.

# Dry-run the codemod against the bundled MapLibre fixture
node dist/src/migration/cli.js codemod test/fixtures/esri-maplibre-simple-app \
  --target honua-maplibre \
  --report reports/maplibre-fixture-report.json

# Write the rewrite in place with inline TODOs for manual sites
node dist/src/migration/cli.js codemod test/fixtures/esri-maplibre-simple-app \
  --target honua-maplibre \
  --write --annotate-todos \
  --report reports/maplibre-fixture-report.json

# Run the codemod against the hand-written parcel viewer example
node dist/src/migration/cli.js codemod examples/arcgis-source-app \
  --target honua-maplibre \
  --report reports/arcgis-source-app-maplibre-report.json

# Per-fixture readiness metrics for a single MapLibre fixture, no gating
node dist/src/migration/cli.js fixtures test/fixtures \
  --target honua-maplibre \
  --fixtures esri-maplibre-simple-app \
  --report reports/maplibre-fixture-metrics.json

The shared codemod flags (--write, --annotate-todos, --report <path>, --compat-import-path <pkg>, --fail-on-manual, --fail-on-unhandled, --fail-on-blocked, --max-manual-ratio, --max-manual-intervention-ratio) all apply unchanged. With --target honua-maplibre the codemod only rewrites the five native kinds; manual TODOs and unhandled-module entries are expected to appear for any constructor outside that set, and gating flags like --fail-on-manual will fail closed against apps with widgets, renderers, or non-2D-core surfaces.

For contributor validation, use npm run test:migration:cli. Its prerequisite prepares the SDK once before Vitest starts, and every CLI spec executes the same manifest-owned dist/src/migration/cli.js artifact. The atomic preparation manifest hashes every compiler/control input and the complete dist/ tree, binds workers to one run ID, and revalidates both trees at teardown. A direct Vitest invocation may run source-only tests without a manifest; a test that consumes built output fails with an actionable npm run prepare:test-sdk prerequisite error. Composed CI and publish lanes use their :prepared variants after the single root build.

Migration report fields

The report writer is buildJsMigrationReport (src/migration/report.ts); its JsMigrationReport shape is what gets written to the path passed to --report. The fields you can key off when consuming a honua-maplibre report are:

The parity matrix tooling (matrix / runtime-matrix subcommands) also emits a honuaMapLibre summary block alongside honuaCompat and esriLeaflet with native/assisted/unsupported counts — see summarizeJsParityMatrix and summarizeJsRuntimeParity.

Manual gaps

Issue #205 is a slice ladder, not a single landing. honua-maplibre ships the native-rewrite path for the layer/map/view core; the remaining acceptance items live in docs/migration-punch-list.md and stay open until they ship. The notable ones, framed against the punch list:

For the broader migration story — including the honua-compat parity surface, the codemod gates, and the test corpus — start at docs/migration-punch-list.md and the fixture-only Esri sample corpus in docs/esri-sample-corpus.md.