Pure planners for the tier-3 build-time file merges — migrations, fonts, and
images — that native_build copies into the host app at build.
Unlike the runtime manifest (behavioral data read on device), these are
physical files: a plugin's migration .exs, font, and image files are
meaningless as build-machine paths on device, so they're copied into the host
bundle at build time. This module computes what gets copied where (pure +
unit-tested); native_build does the I/O (listing dirs, copying, patching
Info.plist).
Summary
Functions
The Android res/font/ resource name for a font file: lowercase, the
extension dropped, and any character outside [a-z0-9_] replaced with _
(Android resource-name rules). "Georgia.ttf" → "georgia",
"Inter-Regular.ttf" → "inter_regular". The renderer normalises the font:
prop the same way to find the resource. A leading non-letter is prefixed with
f_ so the name is a valid resource identifier.
The on-device bundle path a plugin://<plugin>/<file> reference resolves to.
Plugin images are copied here at build time; the core plugin:// resolver
maps to the same convention. Returns a path relative to the app bundle root.
Merges font basenames into an iOS Info.plist XML string under UIAppFonts,
creating the array if absent and de-duplicating existing entries. Pure string
transform over the plist's XML (the same approach as the plist-keys merge).
Plans the migration copies: maps each plugin migration source file to a
destination under the host's migrations_dir, prefixed with the plugin's
repo_namespace so files from different vendors don't collide.
Namespaces a migration filename with the plugin's repo_namespace, inserting
it into the name part after the <version>_ prefix so Ecto can still parse
the leading-integer version (20260101000000_create.exs →
20260101000000_kv_create.exs). Files without a numeric version prefix fall
back to a plain prefix.
Extracts the current UIAppFonts entries from an Info.plist (or []).
Plans the Android res/font/ copies: each distinct source maps to
<android_font_resource_name>.<ext>. Two distinct sources normalising to the
same resource name (e.g. Inter-Regular.ttf and Inter_Regular.ttf both →
inter_regular.ttf) would silently overwrite each other, so that is surfaced
as an error.
Plans the iOS font-bundle copies: each distinct source font copies to the .app
root under its basename (and is listed in UIAppFonts by basename). Two distinct
sources sharing a basename (e.g. two plugins both shipping Icons.ttf) would
silently overwrite each other in the bundle and collapse to a single UIAppFonts
entry, so that is surfaced as an error instead of a silent loss.
Functions
The Android res/font/ resource name for a font file: lowercase, the
extension dropped, and any character outside [a-z0-9_] replaced with _
(Android resource-name rules). "Georgia.ttf" → "georgia",
"Inter-Regular.ttf" → "inter_regular". The renderer normalises the font:
prop the same way to find the resource. A leading non-letter is prefixed with
f_ so the name is a valid resource identifier.
The on-device bundle path a plugin://<plugin>/<file> reference resolves to.
Plugin images are copied here at build time; the core plugin:// resolver
maps to the same convention. Returns a path relative to the app bundle root.
Merges font basenames into an iOS Info.plist XML string under UIAppFonts,
creating the array if absent and de-duplicating existing entries. Pure string
transform over the plist's XML (the same approach as the plist-keys merge).
@spec migration_copies([%{repo_namespace: String.t(), files: [Path.t()]}], Path.t()) :: [ {Path.t(), Path.t()} ]
Plans the migration copies: maps each plugin migration source file to a
destination under the host's migrations_dir, prefixed with the plugin's
repo_namespace so files from different vendors don't collide.
Takes [%{repo_namespace, files: [src_path]}] (the caller lists each plugin's
migration dir) and the host migrations dir; returns [{src, dest}].
Namespaces a migration filename with the plugin's repo_namespace, inserting
it into the name part after the <version>_ prefix so Ecto can still parse
the leading-integer version (20260101000000_create.exs →
20260101000000_kv_create.exs). Files without a numeric version prefix fall
back to a plain prefix.
The namespace is always inserted — there is no "already namespaced?" guard. Migration sources are always the plugin author's raw files (never our output), so re-run idempotency comes from the deterministic source→dest mapping, not from inspecting the name. A guard that skipped namespacing when the description happened to begin with the namespace text would silently drop the namespace and cause cross-vendor collisions, so we don't do it.
Extracts the current UIAppFonts entries from an Info.plist (or []).
@spec plan_android_font_copies([Path.t()]) :: {:ok, [{Path.t(), String.t()}]} | {:error, {:font_resource_collision, String.t(), [Path.t()]}}
Plans the Android res/font/ copies: each distinct source maps to
<android_font_resource_name>.<ext>. Two distinct sources normalising to the
same resource name (e.g. Inter-Regular.ttf and Inter_Regular.ttf both →
inter_regular.ttf) would silently overwrite each other, so that is surfaced
as an error.
Returns {:ok, [{src, res_filename}]} or
{:error, {:font_resource_collision, res_filename, [src, ...]}}.
@spec plan_ios_font_bundle([Path.t()]) :: {:ok, [{Path.t(), String.t()}]} | {:error, {:font_basename_collision, String.t(), [Path.t()]}}
Plans the iOS font-bundle copies: each distinct source font copies to the .app
root under its basename (and is listed in UIAppFonts by basename). Two distinct
sources sharing a basename (e.g. two plugins both shipping Icons.ttf) would
silently overwrite each other in the bundle and collapse to a single UIAppFonts
entry, so that is surfaced as an error instead of a silent loss.
Returns {:ok, [{src, dest_basename}]} or
{:error, {:font_basename_collision, basename, [src, ...]}}.