Detecting assets in source code
When pkg encounters path.join(__dirname, '../path/to/asset'), it automatically packages the referenced file as an asset — no manual assets entry needed. This static analysis avoids forcing every project to configure its assets explicitly.
The supported pattern
path.join(__dirname, 'views/greeting.html'); // ✅ detected
path.join(__dirname, 'migrations/001.sql'); // ✅ detected
path.join(__dirname, '../data/cities.json'); // ✅ detectedTwo arguments only
path.join must have exactly two arguments, and the second must be a string literal. Anything else — three args, a variable, a template literal with expressions — is not detected.
Not detected
const view = 'greeting.html';
path.join(__dirname, 'views', view); // ❌ three arguments
path.join(__dirname, `views/${name}.html`); // ❌ expression inside template
const p = 'views/greeting.html';
path.join(__dirname, p); // ❌ variable, not literalFor these, list the file(s) manually in the assets glob — see Configuration → Assets.
Why this matters
The static walker rewrites detected calls so that at runtime, the resolved path points at /snapshot/... and the asset is served from the virtual filesystem. If the call isn't detected, the asset isn't bundled — and your packaged binary will throw ENOENT at runtime.
How to find missing assets
- Build with
--debug:shpkg --debug index.js - Run the binary with
DEBUG_PKG=1:shDEBUG_PKG=1 ./app-linux - Look at the snapshot tree in the output — is your asset there? If not, either fix the call-site to match the supported pattern or add the asset to the
assetsglob.
See Debug virtual FS for the full debug workflow.
Escape hatch — dynamic assets
If your asset paths are genuinely dynamic (e.g. user-selected theme, loaded language pack), glob the whole directory in package.json:
{
"pkg": {
"assets": ["themes/**/*", "i18n/**/*.json"]
}
}This packages everything matching the glob, regardless of whether any specific path.join(...) call is statically analysable.
