-
Notifications
You must be signed in to change notification settings - Fork 0
Getting Started
This page walks through adding one V2 module to an existing Supernote plugin, choosing an implementation environment, writing a function, generating the bindings, and calling it from JavaScript. The CLI calls each managed module a feature.
It assumes that the plugin already builds and runs. If it does not, fix the ordinary plugin build first so generator problems are not mixed together with plugin setup problems.
Add creates a local package and connects it to the parent plugin. It can change:
local_modules/<package-name>/
package.json
package-lock.json or yarn.lock
android/settings.gradle or android/settings.gradle.kts
android/app/build.gradle or android/app/build.gradle.kts
android/.supernote-module/
There is no Add dry run. Before starting, commit your current work, save a patch, or otherwise make sure you can tell your existing changes apart from the generator's changes.
You do not need a perfectly clean repository. You just need a safe way to review and recover your own work.
Python 3.9 or newer is required.
On Linux or macOS:
python3 -m pip install --upgrade supernote-module-generatorOn Windows, a common form is:
py -m pip install --upgrade supernote-module-generatorConfirm that the command is available:
supernote-module --versionFor the current stable V2 startup behavior, that should report 2.0.3 or a
later compatible release.
If the package installs but the command is not found, see Troubleshooting.
Run the generator from the root of the existing plugin, not from android/, a
source folder, or the generator repository.
The directory must contain at least:
PluginConfig.json
package.json
android/
The generator does not search parent directories for a plugin. A quick check on Linux, macOS, Git Bash, or WSL is:
pwd
ls PluginConfig.json package.json androidIn PowerShell:
Get-Location
Get-Item PluginConfig.json, package.json, androidDoctor checks the JavaScript and Android tools needed by the shared V2 runtime:
supernote-module doctorThis includes Java, Gradle, the Android SDK, Kotlin/KSP, CMake, the NDK, C23, C++23, and the local JSI build inputs.
Doctor checks the complete toolchain because one module can use the native environment, the JVM environment, or both. It does not use ADB and cannot prove that PluginHost on a target device will load and run the result.
Java 17 is the recommended Gradle JVM. V2 accepts Java 17 through 23; Java 17
and Java 21 were exercised in the platform builds. If several JDKs are
installed, make sure java on PATH, JAVA_HOME, and any
org.gradle.java.home setting all select the JDK you intend Gradle to use.
If Android Studio builds the plugin but Doctor cannot find the SDK, see Doctor cannot find Android tools.
The generator and plugin build were checked on macOS ARM64, Linux Mint x86_64, and Windows 11 x86_64. These are host-side build results; they do not replace testing the finished plugin on its intended Supernote and PluginHost version.
Android C and C++ code is compiled by Android NDK Clang on every host. An
ordinary Windows plugin build does not require MSVC or GCC. On Windows, keep
the plugin in a reasonably short path such as C:\src\my-plugin. The generator
keeps its own runtime paths short, but React Native and third-party CMake builds
can still create long intermediate paths.
For the normal interactive workflow, run:
supernote-moduleThe main menu contains:
Add feature
Update feature
Validate feature
Remove feature
Doctor
Help
Exit
Choose Add feature.
In a capable terminal, use the arrow keys and Enter. Press Esc to go back, or
Ctrl+C to interrupt. In --plain mode, menus become numbered lists and accept
:back or :cancel.
Add first asks which example source you want:
[ ] C/C++ (native)
[ ] Kotlin/Java (JVM)
You can select either one or both. These are two environments inside the same module, not two module types.
- Choose C/C++ for native libraries, low-level processing, or C++ work.
- Choose Kotlin/Java for Android APIs, JVM libraries, or Kotlin coroutines.
- Choose both when the same module needs both kinds of work.
This is only scaffolding. A module created with one starter can gain the other environment later without conversion.
The rest of the guided flow asks for values such as:
| Question | Example | What it controls |
|---|---|---|
| Package name | document |
Folder under local_modules/, dependency name, and import string |
| Description | Leave empty | Optional package description |
| JavaScript name | Document |
Generated module name inside the plugin runtime |
| Android namespace | com.example.document |
Kotlin/Java namespace and generated Android paths |
| Package version | 0.1.0 |
Version of this local module package |
| Install now | Yes | Runs npm or Yarn to refresh the local dependency |
Add starts after the last valid answer. It does not show another confirmation screen.
The same choices are available without opening the main menu:
supernote-module add document --starter cpp --yes
supernote-module add document --starter kotlin --yes
supernote-module add document --starter cpp --starter kotlin --yes--yes accepts documented defaults; it does not turn on every optional or
destructive action. For the complete Add options, see Using the CLI.
After Add finishes, inspect the plugin before writing more code:
git status --short
git diffThe generated module package normally looks like this:
local_modules/document/
├── .supernote-module.json
├── README.md
├── index.js
├── index.d.ts
├── package.json
└── android/src/main/
├── cpp/ C and C++ implementation
└── java/com/example/document/ Kotlin and Java implementation
Only the source roots you selected need to exist initially.
The plugin also gets one shared generated runtime below:
android/.supernote-module/v2-runtime/
That runtime belongs to the whole plugin. Adding three modules does not create three worker pools or three copies of the runtime.
Normal source is ignored unless you deliberately mark a declaration for the generator.
Choose one of the following examples. You do not need to write both.
For the native environment, open android/src/main/cpp/feature.cpp and write:
#include <cstdint>
// @SupernotePluginExport
std::int32_t pageCount() {
return 42;
}For the JVM environment, open the generated FeatureApi.kt and write:
package com.example.document
import supernote.generated.annotations.SupernotePluginExport
@SupernotePluginExport
fun pageCount(): Int = 42Do not mark both examples with the same public name in one module. V2 rejects cross-language duplicate API names instead of guessing which implementation you meant.
Anything without SupernotePluginExport or SupernotePluginInternal remains
ordinary implementation code, even when it is public in its own language.
After changing marked source, run:
supernote-module update document --yes
supernote-module validate document --build --verboseThe Android build lets KSP inspect Kotlin/Java declarations, scans C++ markers,
merges both environments into one module API, regenerates index.d.ts, and
compiles the native/JVM routes.
validate without --build checks structure and integration only. That is
useful for a quick check, but it is not enough after changing marked source.
Inspect local_modules/document/index.d.ts for the exact generated API.
Import the generated package normally:
import document from 'document';
function onReadPageCount() {
const pages: number = document.pageCount();
// Use pages here.
}The generated package can be imported while the JavaScript bundle is loading. It resolves the active native feature when your code uses the API. Keep actual native calls in the plugin's normal component, effect, or event flow rather than evaluating them as module-level constants before the plugin starts.
The implementation language does not change the JavaScript call. A C++
pageCount and a Kotlin pageCount produce the same public shape.
An explicitly async declaration returns a Promise instead:
const bytes: Uint8Array = await document.loadPage(3);Read the language guide before adding async work or persistent objects:
Suppose you started in the native environment and later need Android Context.
Add Kotlin or Java under the module's JVM source root, use the generated
annotations, then update and build again. There is no “convert to JVM” command.
The reverse works as well: a JVM-started module can gain .c and .cpp files
under its native source root.
For a module that deliberately connects native C++ to Kotlin or Java, see Using Both Environments.