@better-builds/nodec
Version:
The unofficial node-application-to-standalone-executable compiler, complete with ESBuild support.
106 lines (105 loc) • 4.71 kB
JavaScript
import os from 'node:os';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import fs from 'fs-extra';
import createCLI from 'yargs';
import { hideBin } from 'yargs/helpers';
import { bundleEntrypoint } from './bundleEntrypoint.js';
import { compileBinary } from './compileBinary.js';
import { defaultNodeVersion, supportedOs } from './constants.js';
import { downloadNode } from './downloadNode.js';
import { NodecFolders } from './folders.js';
import { guardGoToolchain } from './guardGoToolchain.js';
import { TargetFormat } from './types.js';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
/**
* Cleans up the download folder after nodec executes,
* regardless of whether it was a success or a failure
*/
function cleanup(noCleanup) {
if (noCleanup) {
return console.info('you opted for --noCleanup so all artifacts have been left in the following temp folder:', NodecFolders.root);
}
const foldersToRemove = [NodecFolders.extracted];
for (const folder of foldersToRemove)
fs.removeSync(folder);
}
/**
* Gets the default compilation OS + ARCH target,
* if the user didn't provide one
*/
function getDefaultTarget() {
return `${os.type() === 'Linux' ? 'linux' : os.type() === 'Darwin' ? 'macos' : 'win'}-${os.arch() === 'arm64' ? 'arm64' : 'x64'}`;
}
/**
* Executes the Nodec program
*/
async function nodec() {
const yargs = createCLI(hideBin(process.argv));
const { version } = JSON.parse(await fs.readFile(path.join(__dirname, '../package.json'), 'utf-8'));
const { entry, format, name, noCleanup, nodeFlags, nodeVersion, outDir, target } = await yargs
.scriptName('nodec')
.version(version)
.option('entry', {
demandOption: true,
description: 'the entrypoint to your JavaScript or TypeScript application',
type: 'string',
})
.option('format', {
choices: Object.values(TargetFormat),
default: 'esm',
description: 'which module format your JavaScript and / or TypeScript code will be compiled to',
type: 'string',
})
.option('name', {
default: 'my-app',
description: 'the final outputted filename that represents your compiled application',
type: 'string',
})
.option('noCleanup', {
default: false,
description: 'if true, will leave all of the downloaded, bundled and compresses assets in a temporary folder on your machine, so you can inspect the state of them',
type: 'boolean',
})
.option('nodeFlags', {
default: [],
description: 'one or more node.js flags to automatically set when executing your compiled application (like --experimental-require-module, --experimental-default-type and others). for a list of available flags, please refer to the official node.js documentation: https://nodejs.org/docs/latest/api',
type: 'array',
})
.option('nodeVersion', {
default: defaultNodeVersion,
description: 'defines the version of NodeJS that will be used when compiling your standalone executable. Must be an explicit version. Semver ranges are not supported.',
type: 'string',
})
.option('outDir', {
default: process.cwd(),
description: 'if set, uses this dir as the location where your binaries will be placed after compilation. Defaults to your CWD.',
type: 'string',
})
.option('target', {
choices: Array.from(supportedOs),
default: [getDefaultTarget()],
description: 'one or more os+arch compilation targets',
type: 'array',
})
.help().argv;
try {
if (!target.every(t => supportedOs.has(t))) {
throw new Error(`you have provided one or more compilation targets that are not supported. ${Array.from(supportedOs).join(',')} are supported`);
}
await guardGoToolchain();
const downloadLocations = await Promise.all(target.map(async (osname) => downloadNode(nodeVersion, osname)));
const bundleLocations = await Promise.all(downloadLocations.map(async (nodePath) => bundleEntrypoint(entry, nodePath, nodeVersion, format)));
const renderedLocations = await Promise.all(target.map(async (target, i) => compileBinary(bundleLocations[i], downloadLocations[i], nodeFlags.map(arg => String(arg)), target, name, outDir)));
console.info('rendered the following binaries:');
console.info(renderedLocations.join(os.EOL));
cleanup(noCleanup);
}
catch (error) {
cleanup(noCleanup);
console.error(error.message || error);
process.exit(1);
}
}
nodec();