Packaging and Deployment #

A Budo app is a folder, and the same folder ships everywhere. You develop it with budo run, export it as a static website with budo web-export, and package it for Android with budo android-apk and budo android-aab. Every target reads the same entry file, the same project-relative assets, and the same app.json.

This page walks that path in order: run on desktop, export to web, build an Android debug APK, then build Android releases.

Step 1: Run on desktop #

If you are starting from scratch, budo init writes a starter project: main.js, an app.json (it asks a few questions; press Enter to accept the defaults), and the editor files jsconfig.json, budo.d.ts, and budo-llm.md that give you autocompletion and an API reference.

Terminal
# Create a starter project in my-app/.
budo init my-app

Run the folder while you develop. budo my-app is shorthand for budo run my-app.

Terminal
# Run the app in a desktop window.
budo run my-app
# Restart the app whenever a project file changes.
budo run my-app --watch
# Choose the window size and title.
budo run my-app --width 1280 --height 720 --title "My App"

run also accepts --fullscreen, --no-vsync, and --file-root <path>, which moves the writable files/ folder of sys.files to another directory. Run budo help for the full list.

Step 2: Export to web #

budo web-export turns the app into a self-contained static folder: an index.html, the Budo web runtime, and your project files. Upload that folder to any static host, or embed it in an existing site.

Terminal
# Export to dist/web (the default).
budo web-export my-app
# Export to a folder of your choice.
budo web-export my-app -o dist/my-app-web

To try the web build on your machine, budo web-serve exports the app and serves it at http://localhost:8080.

Terminal
# Serve the app locally.
budo web-serve my-app
# Listen on another port, reachable from other devices on the network.
budo web-serve my-app --listen 0.0.0.0:8084

Always open a web build over HTTP. Browsers apply stricter security rules to pages opened from file://, and the app may fail to load.

The web target runs JavaScript, TypeScript, and Lua apps. A few features are not available in browsers: WebAssembly apps, UDP sockets, network MIDI, and AI inference. HTTP requests also follow the browser's CORS rules, so the server you call must allow your site's origin.

Step 3: Build an Android debug APK #

Budo Pro: Android packaging is a Budo Pro feature. During Budo's public launch, the released Budo binaries include it for free; a later release will make it a paid feature. Budo built from source without the private Android feature pack keeps the commands, which then report that packaging is unavailable.

Android packaging needs a Budo build that includes it (the released binaries do), a Java JDK, and the Android SDK and NDK. Budo looks for the SDK in ANDROID_HOME, then ANDROID_SDK_ROOT, then the usual install locations, and uses JAVA_HOME when it is set. On the first Android build, Budo downloads its Android support files and caches them for the next builds.

Before the first build, give the app an identity in app.json. Every field is optional, but the package name is how Android tells apps apart, so set it before you install the app anywhere.

app.json
{
  "name": "Pulse Orbit",
  "package_name": "com.example.pulseorbit",
  "version": "1.0.0",
  "version_code": 1,
  "icon": "icon.svg",
  "orientation": "portrait"
}

Then build a debug APK. With --install, Budo also installs it on every device connected through adb.

Terminal
# Build a debug APK into dist/.
budo android-apk my-app
# Build it and install it on the connected devices.
budo android-apk my-app --install

Android packages JavaScript, TypeScript, Lua, and native C apps; WebAssembly apps cannot be packaged. On Android, the system Back button quits the app; your app does not receive it as a key.

Android settings in app.json #

The packager reads these app.json fields:

  • name is the title in the launcher. package_name is the Android application ID, such as com.example.pulseorbit; when you leave it out, Budo derives one from name.
  • version (or version_name) is the version users see. version_code is an integer that must increase with every release you publish.
  • icon is a PNG or SVG file in the app folder. Budo generates every launcher icon size from it, so use a square image of at least 512 x 512 pixels.
  • orientation is portrait, landscape, or unspecified. fullscreen (default true) runs the app in immersive mode.
  • permissions lists Android permission names, without the android.permission. prefix. An app that records with sys.audio.openInput needs "RECORD_AUDIO"; see Audio and MIDI.
  • network adds the INTERNET permission for you, and "filesystem": true adds shared-storage access; see Data, Files, and Network.
  • min_sdk and target_sdk set the Android API levels, 26 and 35 by default.
  • package_ignore lists project-relative files or folders to leave out of the package.
app.json
{
  "permissions": ["RECORD_AUDIO"],
  "package_ignore": ["docs", "tests"]
}

Everything in the app folder is bundled with the app, except entries named build, dist, .budo, store_listing, keystore, or icon.png, and anything listed in package_ignore.

Step 4: Build Android releases #

A release build is optimized, and for publishing it must be signed with your release key. Create a keystore with the standard Android tools, then point app.json at it. Passwords never go in app.json: the signing block names the environment variables that hold them.

app.json
{
  "package_name": "com.example.pulseorbit",
  "version": "1.1.0",
  "version_code": 2,
  "signing": {
    "keystore_path": "keystore/release.jks",
    "key_alias": "upload",
    "keystore_password_env": "KEYSTORE_PASSWORD",
    "key_password_env": "KEY_PASSWORD"
  }
}

keystore_path is relative to the app folder, or absolute. A folder named keystore is never bundled into the app, but keep it out of source control as well.

To publish on Google Play, build an Android App Bundle. android-aab builds a release by default, and it refuses to build one without the release key.

Terminal
# Provide the passwords for this shell session.
export KEYSTORE_PASSWORD="your-keystore-password"
export KEY_PASSWORD="your-key-password"
# Build a signed release AAB into dist/, ready for Play Console.
budo android-aab my-app

You can also build a release APK, to test the optimized build on your own devices.

Terminal
# Build a release APK into dist/.
budo android-apk my-app --release

With a signing block and its passwords, the release APK is signed with your release key. Without them, Budo signs it with the Android debug key and says so in its build summary. An APK signed with the debug key is for testing only: never publish it. Each computer has its own debug key, so before installing one on a device that holds the app from another computer, uninstall the old copy.

Set "shrink": true in app.json to shrink release builds with R8.

Packaging options #

android-apk and android-aab accept the same options:

  • --debug and --release choose the build type. APKs default to debug, AABs to release.
  • -o <dir> (or --output <dir>) writes the package to <dir> instead of dist/.
  • --install installs the APK on every connected adb device. It works with android-apk only.
  • --clean discards the app's previous Android build and rebuilds from scratch.
  • --no-build prepares the Android project without building it.

Budo keeps one Android build directory per app so that rebuilds are fast. budo cache android inspect lists them, and budo cache android clean --all (or clean <package>) removes them.

Before you ship #

Test the app on every target you plan to ship. Check that it degrades gracefully when a capability is missing, that every asset path resolves, and that network and filesystem in app.json grant only what the app needs. Check text and touch targets at the real display density, and remember that audio on web starts only after the user interacts with the page.

For Android, install and try the release build on a real device before you upload the AAB. For web, test the exported folder over HTTP, the way your users will load it.