Skip to content

Managing Modules

s edited this page Aug 17, 2026 · 11 revisions

Managing Modules

This Wiki calls each local generated package a module. The CLI calls the same independently managed package a feature: it is the part you add, update, validate, and remove.

A module has a native C/C++ environment, a Kotlin/Java JVM environment, or both. The plugin-wide runtime is shared, but each module remains independently manageable.

Know which files are yours

Your implementation normally lives below:

local_modules/<package>/android/src/main/cpp/
local_modules/<package>/android/src/main/java/

These are user-owned source roots. Update preserves files you add there and starter files you intentionally change or delete.

The generator owns the surrounding connection code, including:

  • .supernote-module.json;
  • package entry points;
  • index.d.ts;
  • generated JSI and JNI adapters;
  • KSP manifests and adapter output;
  • generated CMake and Gradle integration;
  • feature/package README files; and
  • android/.supernote-module/v2-runtime/.

Do not put durable implementation logic in a generated adapter, declaration, manifest, build file, or runtime source. Put it in the C/C++ or Kotlin/Java source root and use a Supernote marker when the generator needs to know about it.

The generated README inside a feature records the ownership boundary for the generator version that created it.

Before Update or Remove

Update and Remove are transactional, but normal source-control habits still matter.

Before a large change:

git status --short
git diff

Then commit, stash, save a patch, or otherwise make sure you can identify your existing work.

You do not need to erase unrelated changes before using the generator. Do not run a broad Git clean or reset merely to make the plugin look tidy.

A safe Update workflow

After upgrading the generator or changing feature integration:

supernote-module update document

The interactive command shows what it will replace, preserve, and change in the parent plugin. Review that summary before accepting it.

For automation:

supernote-module update document --yes --plain

After Update:

git status --short
git diff
supernote-module validate document --build --verbose

Inspect index.d.ts whenever the public API changed.

Feature versions and plugin versions

The feature package version and the complete plugin version are separate.

The feature's --package-version value belongs to the generated local package. It does not control how PluginHost identifies an update to the complete .snplg plugin.

Before packaging an installable plugin update, increase versionCode and versionName in the plugin root's PluginConfig.json, for example:

{
  "versionCode": "2",
  "versionName": "0.0.2"
}

versionCode is the installation/update version. versionName is the version shown to people. Reinstalling changed code with the same plugin ID and the same root plugin version may leave the previously installed bundle active. Running supernote-module update does not increase these root plugin values for you.

What Update preserves

Update preserves the C/C++ and Kotlin/Java implementation roots, including:

  • functions and classes you wrote;
  • ordinary unmarked helpers;
  • marked public or internal declarations;
  • extra .c, .cpp, .h, .hpp, .kt, and .java files;
  • starter files you modified; and
  • starter files you intentionally removed.

It regenerates the files that connect that implementation to the shared plugin runtime.

What Update does not do

Update is not a source converter or feature-renaming command.

It does not:

  • translate C++ into Kotlin or Kotlin into C++;
  • infer a permanent backend;
  • expose unmarked public language members;
  • rename the npm package, JavaScript feature name, or Android namespace;
  • add V1 compatibility behavior; or
  • preserve manual edits made inside generator-owned output.

When an identity really needs to change, create a new feature with the intended name, move the user-owned implementation deliberately, verify it, and then remove the old feature.

Add another environment later

Starter selection controls only the first example files.

A module created with:

supernote-module add document --starter cpp --yes

can later gain Kotlin or Java under its JVM source root. A module started in the JVM environment can later gain .c and .cpp files under its native source root.

There is no conversion step and no metadata switch. Add the source, mark only the declarations that need generated routing, then run:

supernote-module update document --yes
supernote-module validate document --build --verbose

See Using Both Environments when native C++ needs to call Kotlin or Java through generated internal routing.

Dependency refresh

Add writes a local file: dependency into the parent package.json. Update and Remove refresh npm or Yarn only when the package metadata or local link needs it.

--skip-install skips the package-manager command. It does not undo the package.json change.

If you skip installation, refresh dependencies yourself before expecting node_modules/<feature> to exist:

npm install

or:

yarn install

If both lockfiles exist, the CLI asks you to resolve the ambiguity or pass --package-manager explicitly.

Validate one feature

For a quick structure and integration check:

supernote-module validate document

After changing marked declarations, use the build path:

supernote-module validate document --build --verbose

The build is where KSP sees Kotlin/Java source and the common generator rebuilds the final TypeScript and native routes.

A passing local build proves generation and compilation in that environment. It does not prove that the target PluginHost can load the library or survive a same-process plugin reload.

Validate every feature

supernote-module validate --all

For machine-readable CI output:

supernote-module validate --all --json

--all reports all feature failures before returning failure instead of stopping after the first one.

Remove one feature

Interactive removal asks you to type the exact package name:

supernote-module remove document

For automation, make the target explicit:

supernote-module remove document --yes --plain

Remove updates the parent dependency and rebuilds the shared runtime registry without the selected feature.

The transaction keeps implementation source recoverable until the parent changes, dependency refresh, and final checks have succeeded.

Remove every managed feature

Interactive removal of everything requires typing:

REMOVE ALL

Start it with:

supernote-module remove --all

For automation:

supernote-module remove --all --yes --plain

--yes is accepted only when the target is already unambiguous. It never turns a missing target into “all features.”

Build files are preserved by default

Remove leaves build output alone, even when --yes is present.

To remove the three documented plugin build directories as part of the same operation, opt in explicitly:

supernote-module remove document --delete-build-files --yes

The cleanup scope is exactly:

build/
android/build/
android/app/build/

It does not delete global Gradle caches, arbitrary CMake directories, Android SDK files, or user-owned source outside the selected feature.

Interrupted operations and recovery

Before a mutating operation changes the plugin, it stages data and records a transaction journal. If the process is interrupted, the next command attempts recovery before starting new work.

Exit code 3 means recovery still needs attention.

Do not delete the journal or staging directory blindly. They may contain the only remaining copy of source that was temporarily detached during the transaction.

Start with:

supernote-module doctor --verbose
git status --short
git diff

Then follow the exact recovery instructions printed by the CLI. Preserve the plugin and transaction files before attempting a manual repair.

Custom build or generated-code changes

If a feature needs custom Gradle, CMake, JNI, JSI, or KSP behavior that the generator does not expose, do not quietly patch a generated file and assume Update will preserve it.

First ask whether the customization can live in user-owned source or an ordinary library dependency. If it truly requires a generator change, treat it as a generator capability and test it there.

V1 projects

V2 does not include a V1 converter, migration analyzer, compatibility mode, or source-rewriting tool. V1 was an implementation baseline, not a supported input format.

If an experimental V1 project needs V2, create V2 features and move the implementation source deliberately.

Clone this wiki locally