HakobuHakobu

Migration from pkg

Migrate your project from @yao-pkg/pkg to Hakobu.

Hakobu is a fork of @yao-pkg/pkg (v6.14.1) rebuilt for Node 24 with native ESM support. Most pkg projects can migrate with minimal changes.

Migration Steps

Install Hakobu

Replace @yao-pkg/pkg with @hakobu/hakobu:

npm uninstall @yao-pkg/pkg
npm install -g @hakobu/hakobu

Or as a dev dependency:

npm install @hakobu/hakobu --save-dev

Update your package.json config

Hakobu reads the "hakobu" field in package.json. The legacy "pkg" field is still accepted with a migration warning, so you can migrate incrementally.

Before (pkg):

package.json
{
  "pkg": {
    "scripts": ["lib/**/*.js"],
    "assets": ["views/**", "public/**"],
    "targets": ["node18-linux-x64", "node18-win-x64"],
    "outputPath": "./dist"
  }
}

After (hakobu):

package.json
{
  "hakobu": {
    "assets": ["lib/**/*.js", "views/**", "public/**"],
    "target": "node24-linux-x64",
    "output": "./dist/my-app"
  }
}

You can leave the "pkg" field in place while migrating. Hakobu reads it and emits guidance about what to change. If both "hakobu" and "pkg" fields exist, "hakobu" takes priority and "pkg" is ignored.

Update CLI commands

Replace pkg with hakobu in your build scripts:

package.json
{
  "scripts": {
    "build:exe": "hakobu . --output ./dist/my-app"
  }
}

Update target strings

Change Node version targets from older versions to node24:

- hakobu . --target node18-linux-x64
+ hakobu . --target node24-linux-x64

Test the packaged binary

Run the output executable and verify your application works correctly:

./dist/my-app

Config Field Mapping

pkg optionHakobu equivalentNotes
pkg.scriptshakobu.assetsMerged into assets list. In pkg, "scripts" were extra JS files to compile; Hakobu includes all reachable files automatically.
pkg.assetshakobu.assetsDirect mapping.
pkg.targetshakobu.targetHakobu builds one target at a time, or use comma-separated multi-target.
pkg.outputPathhakobu.outputDirect mapping.
pkg.patchesNot supportedHakobu does not support source patching.
pkg.dictionaryNot supportedHakobu resolves dependencies directly.
pkg.deployFileshakobu.assets or hakobu.externalsUse assets for files to embed, externals for files kept outside.
pkg.ignoreNot neededHakobu only includes files reachable from the entry point.

CLI Flag Mapping

pkg flagHakobu flagNotes
-t, --targets--targetSingular. Comma-separated multi-target or all for all published targets.
-o, --output--outputSame.
-d, --debug--debug / -dSame. Shows detailed packaging diagnostics.
-c, --configNot supportedUse "hakobu" field in package.json.
--out-path--outputAccepted with warning. Use --output with a full path.
--no-bytecodeNot neededHakobu always packages source (no bytecode mode by default).
--compress--compressSame. Supports Brotli and GZip.
--publicNot neededHakobu always includes source.
--seaNot supportedHakobu uses its own snapshot format.
--options--optionsSame. Comma-separated V8 flags baked into the executable.
--build / -b--build / -bSame. Forces local build of base binary instead of downloading.
--no-native-buildNot neededHakobu does not prebuild native addons.
--no-dictNot supportedDictionaries are not used.

Unsupported flags cause a clear error with an explanation. For example, --sea produces: Error! --sea is not supported. Hakobu uses its own snapshot format, not Node SEA.

Key Behavioral Differences

Node Version

  • pkg: Supported Node 8--22 with V8 bytecode compilation.
  • Hakobu: Targets Node 24 only. Base binaries are Node 24.14.0.

Module Format

  • pkg: CJS-first. ESM was experimental and required workarounds.
  • Hakobu: Native ESM by default. "type": "module" in package.json works out of the box. CJS also works. Mixed ESM/CJS with createRequire() works.

Multi-Target Builds

  • pkg: pkg -t node18-linux,node18-win,node18-macos . builds three executables in one command.
  • Hakobu: Same syntax supported with comma-separated targets or the all shorthand:
hakobu . --target node24-linux-x64,node24-win-x64,node24-macos-arm64 --output dist/
hakobu . --target all --output dist/

Hakobu performs shared analysis and parallel base binary fetching for efficiency.

No Bytecode Compilation (by default)

  • pkg: Could compile JS to V8 bytecode (default behavior).
  • Hakobu: Packages source by default. This simplifies the pipeline and avoids V8 version mismatch issues. The executable still contains a snapshot filesystem -- source is not visible to end users without effort. Bytecode can be opted into with --bytecode.

Cache Location

  • pkg: ~/.pkg-cache/
  • Hakobu: ~/.hakobu/cache/

These do not conflict. You can have both installed side by side.

Binary Naming

  • pkg: Base binaries named fetched-v{nodeVersion}-{platform}-{arch}
  • Hakobu: Base binaries named hakobu-base-v{nodeVersion}-{platform}-{arch}

Bundle Mode (new)

Hakobu adds optional bundle mode (--bundle) for TypeScript and monorepo projects. This has no pkg equivalent -- pkg required pre-compiled JavaScript.

hakobu ./my-ts-app --bundle --output ./dist/app

Diagnostic Commands (new)

Hakobu adds commands that pkg did not have:

hakobu targets                    # Show available targets and cache status
hakobu inspect ./my-app           # Analyze project without packaging
hakobu doctor ./my-app            # Check if project is ready to package

What Should Work Without Changes

  • Simple CJS projects with require() and module.exports
  • JSON file loading via require('./data.json')
  • Native addon loading (.node files)
  • child_process.fork() and child_process.spawn()
  • worker_threads with workerData
  • process.execPath pointing to the packaged binary
  • __dirname and __filename in CJS modules

What Needs Attention

  • ESM entries: If your package.json has "type": "module", Hakobu handles it natively. No changes needed, but this is different from pkg's CJS-only model.
  • process.execPath with -e: Spawning process.execPath -e "code" does not work in packaged mode (same as pkg). Use script files instead.
  • Bytecode protection: If you relied on bytecode to obscure source, Hakobu does not offer this by default. Source is embedded in the snapshot.

On this page