Skip to content

Instantly share code, notes, and snippets.

@FlamingTempura
Created December 22, 2019 23:17
Show Gist options
  • Save FlamingTempura/0ddfd3f48576da23fa1226f6f6a76105 to your computer and use it in GitHub Desktop.
Save FlamingTempura/0ddfd3f48576da23fa1226f6f6a76105 to your computer and use it in GitHub Desktop.
Making a binary from Node.js scripts
const { spawn } = require('child_process');
const path = require('path');
const fs = require('fs');
const ROOT_DIR = __dirname;
const OUTPUT_DIR = path.join(__dirname, 'bin'); // where the binary should end up
const OUTPUT = path.join(OUTPUT_DIR, 'my_program'); // name of the binary
const BUILD_DIR = path.join(__dirname, 'build'); // location for temporary build files
const MAIN = 'your-code.js'; // the entrypoint to your code
const INCLUDE = [
process.execPath, // copy node binary
'package.json',
'node_modules',
MAIN,
'something.jpg', // files/directories you wish to include
'something-else.js',
'some-directory'
];
const PAYLOAD_TAR_GZ = path.join(ROOT_DIR, 'payload.tar.gz');
const SH_TEMPLATE = [
'#!/bin/sh',
'export TMPDIR=`mktemp -d /tmp/nodescript.XXXXXX`', // create temp install dir
'function __cleanup () {',
'rm -rf $TMPDIR', // when finish, remove temp install dir
'}',
'trap __cleanup EXIT',
'ARCHIVE=`awk \'/^__ARCHIVE_BELOW__/ {print NR + 1; exit 0; }\' $0`',// get line of archive start
'tail -n+$ARCHIVE $0 | tar xz -C $TMPDIR', // extact archive to temp install dir
//'cd $TMPDIR', // go to temp install dir
`$TMPDIR/node $TMPDIR/${MAIN}`, // run the program
'__cleanup', // when finish, remove temp install dir
'exit 0', // exit before getting to the included file
'__ARCHIVE_BELOW__' // insert marker
].join('\n');
const exec = async (cmd, args, cwd = ROOT_DIR) => new Promise((resolve, reject) => {
console.log(['>', cmd, ...args].join(' '));
const p = spawn(cmd, [...args], { stdio: 'inherit', cwd });
p.on('error', e => reject(e));
p.on('close', code => resolve(code));
});
const build = async () => {
await exec('rm', ['-rf', BUILD_DIR]);
await exec('mkdir', ['-p', BUILD_DIR]);
await exec('mkdir', ['-p', OUTPUT_DIR]);
await exec('cp', ['-r', ...INCLUDE, BUILD_DIR]); // copy all needed files to the build directory
await exec('npm', ['prune', '--production'], BUILD_DIR); // remove unneeded modules (e.g. devDependencies)
await exec('tar', [ // create the tar.gz which will be appended to the binary
'-c', // create
'-z', // gzip
'-C', BUILD_DIR, // working directory
'-f', PAYLOAD_TAR_GZ, // output
'--overwrite', // don't ask before overwrite
'.' // contents
]);
await new Promise(resolve => { // write the executable
const ws = fs.createWriteStream(OUTPUT, { encoding: 'ascii' });
ws.write(SH_TEMPLATE + '\n');
fs.createReadStream(PAYLOAD_TAR_GZ).pipe(ws);
ws.on('close', resolve);
});
await exec('chmod', ['+x', OUTPUT]); // make it executable
await exec('rm', ['-rf', BUILD_DIR, PAYLOAD_TAR_GZ]); // remove build files
};
build();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment