Packaging apps

A ScriptWeaver app is just JavaScript. To ship it, bundle the entry script and any assets into a single .zip — the Player runs it directly:

scriptweaver myapp.zip

A single .js file works too (for a one-file app); the .zip form is for anything with more than one file.

Bundle layout

The Player looks for main.js at the root of the zip and runs it. Put your modules and assets alongside it:

myapp.zip
├── main.js          ← entry point (runs first)
├── greet.js         ← import { … } from './greet.js'
├── logo.png         ← an asset
└── help/            ← optional: your app's F1 manual
    └── index.md

Relative imports between bundled modules work as usual. Create the zip with any tool:

zip -r myapp.zip main.js greet.js logo.png help/

Bundling a manual (F1)

Add a help/ folder with an index.md and the Player opens it when the user presses F1 — your app ships with its own browsable manual, rendered by the same Markdown viewer as these docs. Use relative links and images between help pages; they resolve inside the folder. docs/ works too, but help/ wins when both are present (keep developer docs in docs/, the user manual in help/). Preview it without running the app via scriptweaver --help myapp.zip. See System & files → Help for the details.

Reading bundled assets

Files inside the zip are mounted in a virtual filesystem at //zipfs:/app.

Images and themes accept a //zipfs: path directly, so bundled images just work:

const logo = new Photo({ file: '//zipfs:/app/logo.png' });
app.Label({ image: logo }).pack.configure();

Text and data files read through sw.fs: its read operations understand //zipfs: paths, so a bundled asset is a single call.

const config = JSON.parse(sw.fs.readFile('//zipfs:/app/data.json'));

sw.fs.exists, list, and stat accept //zipfs: paths too (writeFile and friends target the real disk only). Bundled images and themes need nothing special beyond the //zipfs:/app/… path, as shown above.

Distributing

Your users need the Player plus your .zip. Two common options:

Either way there's no installer and no separate runtime to set up — the Player is a single self-contained binary.

Sealing: ship the program, not the source

A .zip (or loose .js) is readable — anyone can unzip it and read your code. To ship an app compiled, not human-readable, seal it to bytecode. This is compilation, not encryption: no DRM, phone-home, or telemetry — the open-source Player must still be able to run it.

Compile to a .swx bundle:

scriptweaver pack myapp/ -o myapp.swx   # myapp/ holds main.js + modules + assets
scriptweaver myapp.swx                   # run it

Every .js becomes stripped QuickJS bytecode; other files travel as plain assets (readable, like a .zip), so unzip/strings on a .swx shows no source. A .swx needs a Player whose QuickJS matches — a clear "repack" message tells you when it doesn't.

Bake a single self-contained executable — the friendly default, one file to hand to a non-technical user:

scriptweaver bake myapp/ -o myapp        # or: bake myapp.swx -o myapp
./myapp                                    # no separate Player needed

bake staples the sealed app onto a copy of the Player, so the matching runtime travels inside the artifact: copy it anywhere and it runs. (On macOS, re-sign the result with codesign — appending a payload invalidates the signature.)

Confidentiality here is obscurity-grade by necessity: bytecode stops you from gifting your source, it is not a vault. eval() / runtime-generated code is not sealed (sealing covers the shipped module graph).

Provenance & licensing (official builds)

Officially distributed ScriptWeaver artifacts carry an ed25519 signature the Player verifies against an embedded public key, surfaced to JS as read-only honesty signals:

sw.bundle.verified; // true for an untampered, officially signed build
sw.bundle.signedBy; // signer label, or null
sw.license.verify(token); // true for a valid signed license token
sw.license.read(token); // the validated payload ({ sub, product?, note?, entitledThrough?, seats? }), or null

These are advisory — the open Player could be rebuilt without them — so they are a provenance hint and a light license honor-system, never a hard lock.

Next