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:
- Install the Player once, then share the
.zip. The Player is free to redistribute (MPL-2.0), so you can host it yourself. - Ship them together — the Player binary and your
.zipin one download.
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.