npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hozo/migration-audit

v0.4.0

Published

Measure how safely Hozo can lower a real React Native application.

Readme

@hozo/migration-audit

Migration analysis for real React Native applications. It runs Hozo's Web and Native lowerers over a checkout without installing anything into that application, respects platform file suffixes, and reports parse failures, diagnostics, React Native JSX left in Web output, and structural wrong-output invariants. It reads the checkout and writes nothing to it unless asked; see What it writes.

npx @hozo/migration-audit .
npx @hozo/migration-audit --root ../my-react-native-app --output hozo-audit.md
npx @hozo/migration-audit . --source app --source components --format json
npx @hozo/migration-audit . --details --output hozo-audit.md
npx @hozo/migration-audit . --css app/globals.css --preflight auto
npx @hozo/migration-audit . --primitive-source @acme/ui
npx @hozo/migration-audit . --source packages/app --include 'packages/**/*.{tsx,jsx,ts}' --exclude '**/*.test.*'
npx @hozo/migration-audit . --native-platform ios
npx @hozo/migration-audit . --font-availability hozo-font-availability.json
npx @hozo/migration-audit . --compare previous-audit.json --output changes.md
npx @hozo/migration-audit . --fail-on error --output hozo-audit.json
npx @hozo/migration-audit . --compare previous-audit.json --fail-on new-errors --output changes.md
npx @hozo/migration-audit . --fix            # print the import moves as a diff
npx @hozo/migration-audit . --fix --write    # apply them

Without --source, the tool scans nonempty src/ and app/ directories (both when present), then falls back to the checkout root when neither contains JS/TS source. Repeat --source to select explicit directories. Overlapping selections are deduplicated. Dependencies and generated directories (node_modules, VCS metadata, .next, .nuxt, .expo, .turbo, .cache, dist, build, coverage, .test-build, artifacts, temp) and symlinked directories are excluded. .tsx, .jsx, .ts, .js, .mts and .mjs enter the authored inventory; declaration files (.d.ts / .d.mts) do not. Repeat --include / --exclude for checkout-relative globs. Includes follow the shared compiler walk's policy: explicit includes override gitignore, but built-in generated/dependency exclusions still apply. --source must remain inside the checkout and cannot select a directory symlink. Empty selections fail with a short message. Explicit includes for a monorepo should be paired with --source . or the appropriate roots. Known declaration/custom-excluded source files are listed separately; built-in excluded directory contents and unsupported extensions are not enumerated, rather than reported as zero.

--fix: the import moves, written to source

With unloweredReactNativeJsx: 'warn' | 'error', the Web build moves the React Native APIs Hozo owns (Platform, Dimensions, Keyboard, StyleSheet and the rest) from react-native to @hozo/rn-compat, invisibly. --fix writes that move into the source, using the compiler's own pass, so it moves exactly the imports the report's "Web React Native import decisions" counts as moved to @hozo/rn-compat. Comments, aliases and type-only imports are kept. A second run changes nothing.

  • Dry run by default. It prints a zero-context unified diff (git apply --unidiff-zero takes it) and a summary. --write applies it, and refuses files Git has no copy of (uncommitted or untracked), or a checkout that is not a Git repository, unless --force is given.
  • Native-only files are left alone. The build makes the move for the Web only. In shared files it changes nothing on Native: @hozo/rn-compat's react-native condition hands back React Native's own objects, and a test fails if a moved API ever stops being one.
  • Components are not moved. The build also moves Pressable and TextInput to @hozo/core, but on Native those are Hozo's own components rather than React Native's, so writing that move into shared source would change Native. View, Text and the rest are lowered, not moved.
  • It says when @hozo/rn-compat is missing from package.json. It does not edit it.

What it writes

This is a contract, not a description of the current code: a change that widens it is a breaking change and needs its own decision.

  • Without --fix --write, nothing in the checkout. The one file an ordinary run writes is the report, and only when --output names a path. It writes there, creating the directory if needed.
  • With --fix --write, only import declarations in the selected source files. It writes the edits the dry run printed and nothing else. That means react-native import specifiers moved to a @hozo/rn-compat import, never a component, and never a Native-only file.
  • Never package.json, a lockfile, configuration, or anything outside the selected sources. A missing @hozo/rn-compat is reported, not installed.
  • Never over work that has no other record. --write refuses when a file it would change has uncommitted changes or is untracked (an ignored file selected by --include counts), and when the checkout is not a Git repository. --force is the only way past that, and it is per run, not remembered.

The Markdown report opens with Next steps: the order to work in, taken from the evidence below it and adding no measurement of its own. Files that fail to parse come first, then React Native JSX left in Web output, errors, a missing @hozo/rn-compat, APIs still on React Native, warnings grouped by code and counted by file, the adapter subsets to check, and, when the build moved any import, the optional --fix above. Each step names its count and up to three files; the full lists stay in the sections it points to.

Interactive terminals receive Markdown; piped stdout stays JSON. --output *.md selects Markdown, other output paths select JSON, and explicit --format json|markdown always wins. Use --help for all options and --expected-commit to make a corpus measurement reproducible. The report deliberately treats its wrong-output count as a lower bound: absence of a diagnostic is not proof that rendered behavior is correct.

The package does not clone repositories. A caller that owns a corpus should acquire and pin it, then pass the checkout to this CLI. Hozo's own pnpm measure:bluesky command is one such runner. It saves both a readable Markdown report and complete JSON evidence under artifacts/measurements/.

JSON schema version 3 retains every finding (message, backend, pipeline stage and location), per-file outcomes, stage timings, tool versions, the actual loaded binding and its SHA-256, checkout dirty status and an authored-source fingerprint. Summary samples remain capped at 12; JSON findings are not capped. --details expands all findings in Markdown. Positions use UTF-16 code units, already converted by NAPI. Positions belonging to rewritten input without an authored source map are explicitly unmapped, not plausible-looking line numbers. Finding fingerprints are content hints, not unique identities or baseline matching guarantees.

Web assessment uses the shared module lowering pipeline, including Canvas and import diagnostics. Native assessment is a compiler-only component/Canvas probe, not full Metro module preparation. It includes the module-level CLASS_NOT_DRAWN_ON_NATIVE warning. That warning names, per file and with the reason, a class React Native cannot draw in a class prop the compiler does not lower, such as one handed to a pattern (handleClassName="before:block" on a BottomSheet). The compiler selects syntax from each filename: .ts/.mts are non-JSX TypeScript, .tsx is TSX, and .js/.jsx/.mjs accept JavaScript with JSX, not TypeScript or Flow. This applies to both authored inventory and imported StyleX definitions. Valid .ts generic arrows are no longer rejected by a TSX-only probe. Parser errors remain SOURCE_SYNTAX_ERROR; invalid TSX is not retried as TS to hide errors. This is not TypeScript type-checking. Each target records integrationEligibility: Web .tsx is semantic-module, other source extensions are runtime-imports-only; Native non-TSX results are compiler-probe-only. No extensions are renamed to claim integration support. Probe-only counts are not backend shape mismatches. scope.tsxFiles retains its literal TSX meaning; authoredFiles is the new denominator, with extension counts and a separate context-module count. Parser-reported syntax errors and tool failures remain visible, with partial diagnostic evidence; the audit does not replace TypeScript or a complete syntax/semantic validation pass. A failed analysis cannot close the direct RN JSX boundary just because observed residue is zero. Static CSS/theme is discovered using the same conventional names as Hozo's integrations, or selected explicitly with --css. Imported CSS is resolved through the shared Tailwind loader. Colors, spacing, paired dark tokens and project keyframes reach both compiler backends. Theme resolution means Hozo's existing token extraction completed, not that every CSS/Tailwind construct is supported or every theme expression converts to Native. Entry and imported CSS are parsed structurally: executable @plugin / @config and remote stylesheet imports are explicitly not assessed, never executed. No bundler/config JavaScript or app scripts are loaded. Safe theme preparation is fresh on each invocation, so edited imports cannot reuse a stale successful result. CSS paths and content hashes are recorded separately from authored files.

No CSS is an absent entry with a visibly defaulted builtin theme. Broken CSS is invalid; executable configuration is unsupported. Both yield partial context, with builtin tokens only as a labelled probe; they cannot close the RN JSX boundary as an authoritative project verdict. An explicitly missing/unreadable CSS entry is an input error. --preflight auto|true|false is a reset assumption supplied to both backends, not proof of the application's bundler configuration. Auto (the default) uses the compiler's Tailwind candidate facts over selected authored JS/TS; the shared conservative token scan can count utility-shaped comments or unused strings. Graph-only sources do not contribute to the selected-scope reset assumption. --primitive-source is repeatable and extends the trusted defaults; discovery never trusts arbitrary component names.

Cross-file StyleX definitions/reexport chains use the compiler's shared module registry in memory, with separate Web/iOS/Android resolver-owned edges, never another utility engine. Only graph-requested context files are read outside the authored selection; their hashes and purpose are retained without inflating files, source bytes or authored signal counts. Static resolution handles relative imports, directory indexes, ESM .js spellings for TS, and exact/single-wildcard paths in root JSONC tsconfig.json with local or installed package extends. The alias fact is scoped to these settings, not all TypeScript/bundler configuration. The shared compiler resolves explicit package subpaths (including omitted .json), a package's static tsconfig field or default tsconfig.json, with lookup bounded by the checkout. Internal package links are accepted only when their physical targets also stay inside it. No install is performed; missing packages/settings are unresolved, not absent/defaulted success. Both selected manifests and configuration files enter configurationInputs with hashes, without entering authored file/byte counts. Base URLs and inherited path origins remain attached to their declaring config; an effective child base URL takes precedence when resolving paths. Settings are read fresh per audit. This follows the static subset of TypeScript config inheritance, not a second application/bundler resolver. The report records checkout-static-json-extends-v1 so previous resolver policies cannot silently provide comparable baseline evidence.

Multiple extends, configuration templates, package exports/custom resolver hooks and bundler aliases remain explicitly unassessed. Nearest installed-but-broken configs are not guessed into an outer package, and lookup never escapes to the audit tool's own dependencies or global installs. No configuration JS, tsconfig plugins, application imports or scripts are executed. Nonselected but admitted sources can supply graph facts (including excluded authored files); this does not turn them into migration targets. Unresolved edges are retained per platform. Default platform preference is .web then unsuffixed for Web, and .ios / .android then .native then unsuffixed for Native. Explicit filenames remain explicit. This static model is not a certification of a particular bundler's resolution; custom module suffix policies are not applied. Shared/native component probes use Android by default; --native-platform ios selects iOS instead. Explicit .ios / .android filenames always use their own graph. Both native graphs are prepared, but a shared file is counted only once, for the selected probe platform.

Production builds, runtime behavior and entry-point graph reachability are not assessed. contextStatus: prepared describes successful theme/reset/static graph preparation, not that all project facts or unresolved graph edges have answers. Unrecorded generated RN expressions and complete member compatibility remain explicit analysis limitations, not requirements for completing the bounded migration-audit work in #790.

Explicit static font registration facts

--font-availability <file.json> reads the serializable registry returned by createFontAvailability() from @hozo/typography/fonts. Export JSON from the same manifest used by the application's integrations, rather than maintaining a separate font inventory:

import { writeFileSync } from 'node:fs'
import { createFontAvailability } from '@hozo/typography/fonts'

// Run this explicitly in your own setup, with your existing font manifest.
writeFileSync('hozo-font-availability.json', JSON.stringify(createFontAvailability(fonts)))

The audit itself never imports that setup or an application font loader. Library callers can instead pass fontAvailability: createFontAvailability(fonts) directly, or fontAvailabilityFile; the two inputs are mutually exclusive. Data is validated and snapshotted once per audit. Missing, malformed or executable explicitly selected files are input errors, not a silently successful default. File paths and physical link targets must stay inside the checkout. Normalized facts appear in projectFacts.fonts; file paths/content hashes appear in fontInputs, outside authored file counts. Duplicate IDs/ambiguous platform names, invalid weights/platforms and contradictory externally managed registrations are rejected rather than guessed.

The canonical compiler analysis uses the existing static font diagnostic, not a second audit font checker. It checks literal generated family/weight/style declarations for managed families on Web and the selected Native platform; explicit .ios/.android filenames win over the default probe platform. Missing platform registrations and variants produce FONT_FAMILY_NOT_REGISTERED / FONT_VARIANT_NOT_REGISTERED warnings with backend and coordinate confidence. Unknown families, external platforms, dynamic declarations and host-owned styles remain unassessed. No input leaves fonts unresolved, even if CSS contains @font-face.

These are supplied registration facts, not proof that font assets exist, are bundled or load on a device. No asset bytes are opened, remote URLs fetched or registration code executed. static-font-availability-v1 records this analysis policy. Font facts/input hashes and the policy participate in baseline comparability; changing/removing them cannot silently resolve warnings. Older reports without this evidence need a fresh baseline against the new policy.

The schema records corpus.sourceDirectories and omits application-specific metrics from authoredSignals. Library callers can supply fileSignals: { metricName: (source, file) => boolean } to measureRealApp; these counts appear separately in corpusSignals and the Markdown report. measureRealApp and runCli are now asynchronous; library callers must await them. Options include css, preflight and primitiveSources (an array of explicitly trusted additions). Library options also include include, exclude (arrays of globs), nativePlatform, fontAvailability and fontAvailabilityFile. The pinned Bluesky runner retains its historical filesUsingAlfAtoms lexical heuristic through this extension. Bluesky's checkout pin, verification command and historical reports are preserved; that heuristic is not presented as a generic detector of an application's design system.

Binding-aware React Native source inventory

reactNativeUsage counts compiler-resolved ESM references, not import spellings or a compatibility score. Each JSON file record retains the complete authored bindings and references with UTF-16 spans. Aliases and lexical shadowing are resolved; explicit type imports, value imports used only as types, unused values, component values, static/dynamic member paths, local exports, direct reexports and side-effect imports stay distinct. Reference counts include opening and closing JSX tag occurrences, not component/call counts. Parse/binding failures and unassessed files stay visible.

CommonJS require, dynamic imports, TS import-equals, indirect wrapper modules and data-flow through subsequent aliases are outside this ESM inventory. Moving an import, resolving its package, removing production RNW dependencies and runtime/member compatibility are separate claims, not a renamed migration-ready score.

Actual Web import rewrite journal

reactNativeImportDecisions summarizes the shared compiler lowerer's actual import-specifier decisions. Complete per-file outcomes are in targets.web.reactNativeImports, joined by bindingIndex to authored source usage. The audit owns no import replacement table and does not infer these decisions by re-reading emitted code. Only completed targets enter the successful headline counts; partial/failed journals remain in JSON with their status.

retained-react-native describes an import declaration, not a remaining runtime reference: JSX lowering may already have removed its uses while leaving the import for the bundler. type-only does not certify erasure. Namespace/default imports and imports with attributes are deliberately retained; policy allow leaves rewriting not assessed. Forwarding/side-effect edges, semantic-reference dispositions, member compatibility, package resolution, production dependency removal and Native import rewriting remain unassessed by this journal. Native analysis is still a compiler-only probe.

Actual Web JSX tag journal

reactNativeReferenceDecisions summarizes exact tag emissions from the Web backend. targets.web.reactNativeReferences retains each authored bindingIndex/referenceIndex outcome in JSON. Import/Canvas edits remap unchanged source runs only; generated text is not guessed back to an authored identifier. Opening/closing names count separately, not as component instances. Void elements can consume authored closing tags.

Preserved tag spelling is not a remaining RN dependency: an import may have moved to Hozo. Non-JSX APIs, factories, type references, prop/handler expressions and tags without an exact backend emission remain not-assessed, not implicitly retained or unsupported. Only completed journals enter successful tag counts; partial/failed evidence stays visible in JSON. Member compatibility, Native module rewriting, production builds and runtime still require separate evidence. The audit does not reimplement backend policy or parse emitted code to invent reference decisions.

Actual Web non-JSX values and review inventory

reactNativeValueDecisions separately summarizes non-JSX runtime references with validated source-run evidence across actual module edits and an actual import-origin decision. Per-file targets.web.reactNativeValues retains authored binding/reference indices, final emitted UTF-16 spans, reasons and explicit scope. unchangedModuleReferences and backendCopiedReferences distinguish untouched module source from actual backend-carried prop/child fragments and DOM style/spread normalizer values. Nested replacements, discarded void children and changed class name fragments cannot acquire copied identity. Emitted onPress/responder handler values receive records too: the function value stays in its authored scope, outside the interactive helper or the sibling disabled-link callback. Dropped handlers, synthesized callback syntax and other expressions without emission records stay unknown, not retained; generated expression scope is copied-runs-only. Type references are excluded separately. Completed-file counts include unknowns; failed/partial/unassessed journals stay in JSON without padding successful headline counts or inventory rows.

The generic inventory groups authored import/member/access and observed origin outcomes, not adapter support guesses. Each row has occurrence counts, distinct-file counts and up to 12 sample files; JSON keeps all individual outcomes regardless of sampling. Retained RN-backed values are a migration review queue, moved values still require member-contract verification, and dynamic/namespace access needs manual review. The audit does not certify member compatibility, package installation, third-party RNW dependence, production dependency removal or runtime.

Reviewed member contracts and next actions

Completed Web value outcomes also carry the compiler's memberContract; the report does not keep its own ownership/support table. Inventory rows retain the status, declaration-subset limitations, next action and implementation/test paths. Markdown displays these separately from import origin. reviewedAdapterReferences, retainedGuidanceReferences and unreviewedMemberReferences partition the same completed-file non-JSX runtime references, not all RN APIs or a compatibility percentage.

The initial catalogue reviews 18 Platform/StyleSheet/Keyboard/Dimensions members and gives next actions for 10 retained RN members. No-op Keyboard notifications, placeholder Platform.Version and object-only StyleSheet assumptions are explicit. Only direct named-import static members with actual final origin evidence are reviewed; aliases work, while dynamic/namespace accesses, whole values, missing entries and untraced expressions stay unassessed. Missing review means unknown, not unsupported. JSON keeps per-reference reviews; the 12-file sample cap does not cap counts.

These are reviewed declarations and migration guidance, not checks of call arguments, writes, data flow, installed adapter versions, Native or runtime behavior. memberCompatibility remains not-assessed. Source paths refer to the compiler's Hozo review, not the audited checkout. Markdown evidence links point to current main, not a frozen installed-tool source revision. Partial/failed journals do not contribute positive reviews. No app code/config is executed.

Baseline comparison

Save a JSON audit, then pass it to --compare. The current audit stays complete and gains a comparison field; Markdown includes the summary and up to 12 records per category, with --details expanding all finding outcomes. Display limits never cap JSON counts. Library callers can use compareReports(current, baseline) without running either application.

Comparison is informational by default. It does not fail a command for findings or claim an improvement percentage. Malformed/unreadable baseline input fails with a short message. Explicit --fail-on policies can turn findings into a CI gate, as described below.

  • Unique compiler subjects match within the same relative file, backend and diagnostic code. Ordinary line movement is continued, not added. Message/severity changes retain both records.
  • Duplicate subjects in edited files, missing authored anchors and possible cross-file moves/clones stay unassessed. Exact duplicate records can continue when source is unchanged. Removed files are inventory changes, not proven diagnostic resolutions.
  • Repository identities, source selection, effective reset/theme/trusted sources, stylesheet, alias configuration and graph-only context input hashes must agree. Different settings or schemas are non-comparable, not lower-error successes. Partial context, failed probes and target/journal contract changes are explicit. Relocated checkouts need the same --repository identity; newly recorded stylesheet paths are checkout-relative like graph/config inputs. Older absolute-path CSS records are not guessed into a new location. No app configuration is executed to reconstruct missing inputs.
  • File-level Web RN boundaries compare the compiler's actual import/tag/value journal category counts, joined to authored usage. Unknowns/types remain visible. These are not matched cross-edit runtime references, member certification or production dependency-removal evidence. Member-review prose/evidence edits alone are not rewrite changes; reviews stay in the audit's separate member guidance inventory.
  • Toolchain/source provenance differences are displayed independently. Two reports cannot attribute a count change to a tool upgrade rather than an app edit. Binding install paths are not tool identity; binary hashes and recorded versions remain evidence.

comparable describes this static observation comparison, not that all application behavior, fonts, indirect RN references, production dependencies or devices were assessed. JSON retains all ambiguous/partial records and before/after boundaries regardless of Markdown sample limits.

Reports record the compiler's compiler-stylex-reference-context-v1 required-context policy and per-target stylexContext evidence. An actual import-binding inventory can prove that a file does not require StyleX context. Consumers instead require conservative reference and reexport candidates, matching source/dependency hashes, target platform and recorded resolver edges. Missing, recovered, stale or contradictory evidence is unassessed, not a successful empty graph. Runtime imports and all unresolved alias/graph observations remain in JSON; this policy does not certify them or suppress setup warnings.

An unresolved alias preset no longer blocks every file if the compiler proves the missing settings unnecessary for that file's static lowering. CSS/theme preparation remains a global requirement, and effective input/settings equality still applies. Edited authored StyleX dependencies leave affected consumers unassessed; unrelated files can still be compared. If any file is unassessed the aggregate comparison is partial, so new-errors stays blocked with newErrors: null. The new policy cannot inherit an old-policy baseline: take a fresh baseline. Legacy and unknown policy reports retain conservative completeness handling. The Markdown required-context table is a static analysis observation, not a compatibility score.

Explicit CI failure policies

--fail-on none|error|new-errors controls diagnostic exits. The default is none.

| Policy | Exit behavior | |---|---| | none | Findings are informational, including authored syntax errors. | | error | Fail for any current error finding across Source, Web or Native. Warnings do not fail. | | new-errors | Require --compare and a comparable result; fail for added errors or a matched finding escalating from non-error to error. Continued errors and error-message-only changes do not fail. |

Input errors and tool/analysis failures are nonzero in every mode. A compiler exception (ANALYSIS_FAILED) or incomplete file analysis cannot be grandfathered by a baseline. Authored syntax rejection (SOURCE_SYNTAX_ERROR) is a diagnostic, not a crashed compiler; it stays informational in none, fails error, and prevents a complete new-errors comparison.

Partial/non-comparable baselines are blocked, not zero new errors. This includes missing project facts, ambiguous/moved subjects, removed files, changed scope/settings and failed probes. For such a project, use error for the current known errors or keep none for observation; do not clear unknowns or weaken the baseline's evidence to get a green result. Even a passed policy is not migration readiness, runtime correctness or a full compatibility assessment.

The CLI writes the complete JSON/Markdown report before returning a policy failure, so CI can upload evidence on failed steps. Invalid arguments/baseline input fail before analysis or output. Exit 0 means the selected policy passed; 1 means a failed/blocked policy or input/tool failure. A failed/blocked policy also emits a brief stderr explanation without polluting JSON stdout.

failurePolicy version 1 records mode, status, exit code, reasons and uncapped finding indices. errorFindings and analysisFailures index current findings; newErrorFindings index the comparison's findings.added or findings.changed array. newErrors: null means not assessed, not zero. Counts describe backend finding occurrences, not distinct source defects. Library callers can use evaluateFailurePolicy(report, mode) after measureRealApp/compareReports. runCli records the policy and returns the report without changing process.exitCode; only the executable applies the exit. This keeps embedded audit calls independent of the caller's process.


Part of Hozo, a universal UI framework for React Native, compiled by Rust, with native semantics on every platform, first-class accessibility, and diagnostics for humans and AI agents alike. Most applications install @hozo/core and one build integration; see Getting started. Source and issues: github.com/iray-tno/hozo.