@kafitra/lynx-autolink
v0.1.1
Published
Auto-linking library for Lynx Native Modules — scans node_modules, generates Java registry, injects Gradle wiring
Maintainers
Readme
@kafitra/lynx-autolink
Core auto-linking library for Lynx Native Modules on Android.
Used internally by@kafitra/lynx-cli— you rarely need to call this directly.Repository: github.com/kafitramarna/kafitra-lynx-native
Overview
@kafitra/lynx-autolink scans node_modules for Lynx Native Module packages (identified by a lynx.module.json metadata file), then generates all the Android plumbing needed to wire them up:
| What it does | Output |
| ------------------------------------- | --------------------------------------------------- |
| Scans node_modules (hoisted + nested) | List of LynxModuleMetadata |
| Validates each lynx.module.json | Throws descriptive error on bad schema |
| Generates LynxAutolinkRegistry.java | Drop-in registry class, call registerAll() |
| Patches settings.gradle | Idempotent include ':module-name' block |
| Patches app/build.gradle | Idempotent implementation project(':module-name') |
Installation
pnpm add -D @kafitra/lynx-autolink
# or
npm install --save-dev @kafitra/lynx-autolinkAPI Reference
scanModules(projectRoot: string): LynxModuleMetadata[]
Walks all node_modules directories reachable from projectRoot (handles hoisted monorepos).
Returns an array of validated LynxModuleMetadata objects — one per package that has a lynx.module.json.
import { scanModules } from "@kafitra/lynx-autolink";
const modules = scanModules(process.cwd());
// [{ name: "LynxDeviceInfo", android: { moduleClass: "...", sourceDir: "android" }, ... }]writeJavaRegistry(androidAppDir, javaPackage, modules)
Generates LynxAutolinkRegistry.java into <androidAppDir>/src/main/java/<javaPackage>/.
The file is always overwritten so it stays in sync with installed packages.
import { writeJavaRegistry } from "@kafitra/lynx-autolink";
writeJavaRegistry("./android/app", "com.example.app", modules);Generated file looks like:
package com.example.app;
import com.lynx.tasm.LynxEnv;
import com.example.mymodule.MyLynxModule;
public class LynxAutolinkRegistry {
public static void registerAll() {
LynxEnv.inst().registerModule("MyModule", MyLynxModule.class);
}
}Then call it from your Application class:
@Override
protected void initLynxEnv() {
LynxEnv.inst().init(this, null, null, null);
LynxAutolinkRegistry.registerAll(); // ← auto-linked modules
}injectSettings(settingsFile, modules)
Idempotently inserts Gradle project includes into settings.gradle, wrapped in markers:
// lynx-autolink-start
include ':lynx-device-info'
project(':lynx-device-info').projectDir = new File(rootDir, '../../../packages/lynx-device-info/android')
// lynx-autolink-endSafe to run multiple times — existing block is replaced, not duplicated.
injectBuildGradle(buildFile, modules)
Idempotently adds implementation project(':...') lines to the first dependencies {} block in app/build.gradle.
readApplicationId(buildGradlePath): string | null
Reads the applicationId from an app/build.gradle file using a regex.
Used by the CLI to infer the Java package for the registry class.
injectManifestPermissions(manifestFile, modules)
Collects all permissions entries from the provided modules and idempotently injects
<uses-permission android:name="..."/> elements into AndroidManifest.xml.
Uses <!-- lynx-autolink-permissions-start --> / <!-- lynx-autolink-permissions-end --> markers.
If the markers are absent, the block is inserted immediately before the <application> tag.
Re-running the function replaces the existing block — no duplication.
import { injectManifestPermissions } from "@kafitra/lynx-autolink";
injectManifestPermissions("./android/app/src/main/AndroidManifest.xml", modules);Result in AndroidManifest.xml:
<uses-permission android:name="android.permission.INTERNET" />
<!-- lynx-autolink-permissions-start -->
<uses-permission android:name="android.permission.CAMERA" />
<!-- lynx-autolink-permissions-end -->validateMetadata(raw, source): LynxModuleMetadata
Validates a parsed lynx.module.json object. Throws a descriptive error if required fields are missing or malformed.
Enforcements include:
android.componentTagis required whenandroid.componentClassis provided.- At least one of
moduleClassorcomponentClassmust be present. - All class names must match the fully-qualified Java class name pattern.
lynx.module.json Schema
Place this file at the root of your Lynx Native Module npm package:
{
"name": "LynxDeviceInfo",
"android": {
"moduleClass": "com.kafitra.lynxdeviceinfo.LynxDeviceInfoModule",
"sourceDir": "android",
"gradleProjectName": "lynx-device-info"
}
}| Field | Type | Required | Description |
| --------------------------- | ---------- | ------------------------ | ---------------------------------------------------------------------------------- |
| name | string | ✅ | Module name registered with LynxEnv (e.g. "LynxDeviceInfo") |
| android.moduleClass | string | ✅¹ | Fully-qualified Java class name of the LynxModule implementation |
| android.componentClass | string | ✅¹ | Fully-qualified Java class name of the LynxUI custom element implementation |
| android.componentTag | string | ✅ when componentClass | JSX element tag registered with Lynx (e.g. "camera") |
| android.sourceDir | string | ✅ | Relative path to the Android library directory inside the package |
| android.gradleProjectName | string | ❌ | Override for the Gradle project name. Defaults to the kebab-cased npm package name |
| android.permissions | string[] | ❌ | Android permissions required at runtime (e.g. ["android.permission.CAMERA"]). Auto-injected into AndroidManifest.xml by lynx link. |
¹ At least one of
moduleClassorcomponentClassmust be provided.
Types
export interface LynxModuleAndroidConfig {
/** LynxModule class name (for native modules) */
moduleClass?: string;
/** LynxUI class name (for native UI custom elements) */
componentClass?: string;
/** JSX element tag — required when componentClass is provided */
componentTag?: string;
sourceDir: string;
gradleProjectName?: string;
/** Android permissions required at runtime. Injected into AndroidManifest.xml by `lynx link`. */
permissions?: string[];
}
export interface LynxModuleMetadata {
name: string;
android: LynxModuleAndroidConfig;
/** Populated by scanner — not present in lynx.module.json */
packageDir?: string;
packageName?: string;
}Native UI Components
In addition to native modules (moduleClass), you can register native UI custom elements
(componentClass + componentTag). The generated LynxAutolinkRegistry.java will expose
an addUIBehaviorsTo(builder) method alongside registerAll().
Example lynx.module.json for a native UI component:
{
"name": "LynxCamera",
"android": {
"componentClass": "com.kafitra.lynxcamera.LynxCameraView",
"componentTag": "camera",
"sourceDir": "android"
}
}In your Activity:
LynxAutolinkRegistry.registerAll(); // native modules
LynxAutolinkRegistry.addUIBehaviorsTo(builder); // native UI componentsCreating a Lynx Native Module
To make your package auto-linkable, add lynx.module.json to it and include it in files[]:
// package.json
{
"name": "@your-scope/lynx-my-module",
"files": ["dist", "android", "ios", "lynx.module.json"]
}Then implement a standard Lynx LynxModule in android/src/main/java/....
License
MIT
