@napi-rs/cli
Version:
Cli tools for napi-rs
1,395 lines (1,339 loc) • 530 kB
JavaScript
import { createRequire } from "node:module";
import { Cli, Command, Option } from "clipanion";
import path, { dirname, isAbsolute, join, parse, resolve } from "node:path";
import * as colors from "colorette";
import { underline, yellow } from "colorette";
import rawDebug from "debug";
import { access, copyFile, mkdir, readFile, readdir, rename, stat, unlink, writeFile } from "node:fs/promises";
import { exec, execSync, spawn, spawnSync } from "node:child_process";
import fs, { existsSync, mkdirSync, promises, rmSync, statSync } from "node:fs";
import { isNil, merge, omit, omitBy, pick, sortBy } from "es-toolkit";
import { createHash } from "node:crypto";
import { homedir } from "node:os";
import { parse as parse$1 } from "semver";
import { dump, load } from "js-yaml";
import * as typanion from "typanion";
import { Octokit } from "@octokit/rest";
import { checkbox, confirm, input, select } from "@inquirer/prompts";
//#region src/def/artifacts.ts
var BaseArtifactsCommand = class extends Command {
static paths = [["artifacts"]];
static usage = Command.Usage({ description: "Copy artifacts from Github Actions into npm packages and ready to publish" });
cwd = Option.String("--cwd", process.cwd(), { description: "The working directory of where napi command will be executed in, all other paths options are relative to this path" });
configPath = Option.String("--config-path,-c", { description: "Path to `napi` config json file" });
packageJsonPath = Option.String("--package-json-path", "package.json", { description: "Path to `package.json`" });
outputDir = Option.String("--output-dir,-o,-d", "./artifacts", { description: "Path to the folder where all built `.node` files put, same as `--output-dir` of build command" });
npmDir = Option.String("--npm-dir", "npm", { description: "Path to the folder where the npm packages put" });
buildOutputDir = Option.String("--build-output-dir", { description: "Path to the build output dir, only needed when targets contains `wasm32-wasi-*`" });
getOptions() {
return {
cwd: this.cwd,
configPath: this.configPath,
packageJsonPath: this.packageJsonPath,
outputDir: this.outputDir,
npmDir: this.npmDir,
buildOutputDir: this.buildOutputDir
};
}
};
function applyDefaultArtifactsOptions(options) {
return {
cwd: process.cwd(),
packageJsonPath: "package.json",
outputDir: "./artifacts",
npmDir: "npm",
...options
};
}
//#endregion
//#region src/utils/log.ts
rawDebug.formatters.i = (v) => {
return colors.green(v);
};
const debugFactory = (namespace) => {
const debug$10 = rawDebug(`napi:${namespace}`);
debug$10.info = (...args) => console.error(colors.black(colors.bgGreen(" INFO ")), ...args);
debug$10.warn = (...args) => console.error(colors.black(colors.bgYellow(" WARNING ")), ...args);
debug$10.error = (...args) => console.error(colors.white(colors.bgRed(" ERROR ")), ...args.map((arg) => arg instanceof Error ? arg.stack ?? arg.message : arg));
return debug$10;
};
const debug$9 = debugFactory("utils");
//#endregion
//#region package.json
var name = "@napi-rs/cli";
var version$1 = "3.4.1";
var description = "Cli tools for napi-rs";
var author = "LongYinan <lynweklm@gmail.com>";
var homepage = "https://github.com/napi-rs/napi-rs";
var license = "MIT";
var type = "module";
var engines = { "node": ">= 16" };
var bin = {
"napi": "./dist/cli.js",
"napi-raw": "./cli.mjs"
};
var main = "./dist/index.cjs";
var module = "./dist/index.js";
var types = "./dist/index.d.ts";
var exports = {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"./package.json": "./package.json"
};
var files = [
"dist",
"src",
"!__tests__"
];
var keywords = [
"cli",
"rust",
"napi",
"n-api",
"node-api",
"node-addon",
"neon"
];
var maintainers = [{
"name": "LongYinan",
"email": "lynweklm@gmail.com",
"homepage": "https://github.com/Brooooooklyn"
}, {
"name": "forehalo",
"homepage": "https://github.com/forehalo"
}];
var repository = {
"type": "git",
"url": "git+https://github.com/napi-rs/napi-rs.git"
};
var publishConfig = {
"registry": "https://registry.npmjs.org/",
"access": "public"
};
var bugs = { "url": "https://github.com/napi-rs/napi-rs/issues" };
var dependencies = {
"@inquirer/prompts": "^7.8.4",
"@napi-rs/cross-toolchain": "^1.0.3",
"@napi-rs/wasm-tools": "^1.0.1",
"@octokit/rest": "^22.0.0",
"clipanion": "^4.0.0-rc.4",
"colorette": "^2.0.20",
"debug": "^4.4.1",
"emnapi": "^1.5.0",
"es-toolkit": "^1.39.10",
"js-yaml": "^4.1.0",
"semver": "^7.7.2",
"typanion": "^3.14.0"
};
var devDependencies = {
"@emnapi/runtime": "^1.5.0",
"@oxc-node/core": "^0.0.32",
"@std/toml": "npm:@jsr/std__toml@^1.0.10",
"@types/debug": "^4.1.12",
"@types/inquirer": "^9.0.9",
"@types/js-yaml": "^4.0.9",
"@types/node": "^24.3.1",
"@types/semver": "^7.7.1",
"ava": "^6.4.1",
"empathic": "^2.0.0",
"env-paths": "^3.0.0",
"prettier": "^3.6.2",
"tsdown": "^0.15.0",
"tslib": "^2.8.1",
"typescript": "^5.9.2"
};
var peerDependencies = { "@emnapi/runtime": "^1.5.0" };
var peerDependenciesMeta = {
"@emnapi/runtime": { "optional": true },
"emnapi": { "optional": true }
};
var funding = {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
};
var scripts = {
"codegen": "node --import @oxc-node/core/register ./codegen/index.ts",
"build": "tsdown",
"test": "node --import @oxc-node/core/register ../node_modules/ava/entrypoints/cli.mjs"
};
var ava = {
"extensions": { "ts": "module" },
"timeout": "2m",
"workerThreads": false,
"files": ["**/__tests__/**/*.spec.ts", "e2e/**/*.spec.ts"]
};
var package_default = {
name,
version: version$1,
description,
author,
homepage,
license,
type,
engines,
bin,
main,
module,
types,
exports,
files,
keywords,
maintainers,
repository,
publishConfig,
bugs,
dependencies,
devDependencies,
peerDependencies,
peerDependenciesMeta,
funding,
scripts,
ava
};
//#endregion
//#region src/utils/misc.ts
const readFileAsync = readFile;
const writeFileAsync = writeFile;
const unlinkAsync = unlink;
const copyFileAsync = copyFile;
const mkdirAsync = mkdir;
const statAsync = stat;
const readdirAsync = readdir;
function fileExists(path$1) {
return access(path$1).then(() => true, () => false);
}
async function dirExistsAsync(path$1) {
try {
return (await statAsync(path$1)).isDirectory();
} catch {
return false;
}
}
function pick$1(o, ...keys) {
return keys.reduce((acc, key) => {
acc[key] = o[key];
return acc;
}, {});
}
async function updatePackageJson(path$1, partial) {
if (!await fileExists(path$1)) {
debug$9(`File not exists ${path$1}`);
return;
}
const old = JSON.parse(await readFileAsync(path$1, "utf8"));
await writeFileAsync(path$1, JSON.stringify({
...old,
...partial
}, null, 2));
}
const CLI_VERSION = package_default.version;
//#endregion
//#region src/utils/target.ts
const SUB_SYSTEMS = new Set(["android", "ohos"]);
const AVAILABLE_TARGETS = [
"aarch64-apple-darwin",
"aarch64-linux-android",
"aarch64-unknown-linux-gnu",
"aarch64-unknown-linux-musl",
"aarch64-unknown-linux-ohos",
"aarch64-pc-windows-msvc",
"x86_64-apple-darwin",
"x86_64-pc-windows-msvc",
"x86_64-pc-windows-gnu",
"x86_64-unknown-linux-gnu",
"x86_64-unknown-linux-musl",
"x86_64-unknown-linux-ohos",
"x86_64-unknown-freebsd",
"i686-pc-windows-msvc",
"armv7-unknown-linux-gnueabihf",
"armv7-unknown-linux-musleabihf",
"armv7-linux-androideabi",
"universal-apple-darwin",
"loongarch64-unknown-linux-gnu",
"riscv64gc-unknown-linux-gnu",
"powerpc64le-unknown-linux-gnu",
"s390x-unknown-linux-gnu",
"wasm32-wasi-preview1-threads",
"wasm32-wasip1-threads"
];
const DEFAULT_TARGETS = [
"x86_64-apple-darwin",
"aarch64-apple-darwin",
"x86_64-pc-windows-msvc",
"x86_64-unknown-linux-gnu"
];
const TARGET_LINKER = {
"aarch64-unknown-linux-musl": "aarch64-linux-musl-gcc",
"loongarch64-unknown-linux-gnu": "loongarch64-linux-gnu-gcc-13",
"riscv64gc-unknown-linux-gnu": "riscv64-linux-gnu-gcc",
"powerpc64le-unknown-linux-gnu": "powerpc64le-linux-gnu-gcc",
"s390x-unknown-linux-gnu": "s390x-linux-gnu-gcc"
};
const CpuToNodeArch = {
x86_64: "x64",
aarch64: "arm64",
i686: "ia32",
armv7: "arm",
loongarch64: "loong64",
riscv64gc: "riscv64",
powerpc64le: "ppc64"
};
const SysToNodePlatform = {
linux: "linux",
freebsd: "freebsd",
darwin: "darwin",
windows: "win32",
ohos: "openharmony"
};
const UniArchsByPlatform = { darwin: ["x64", "arm64"] };
/**
* A triple is a specific format for specifying a target architecture.
* Triples may be referred to as a target triple which is the architecture for the artifact produced, and the host triple which is the architecture that the compiler is running on.
* The general format of the triple is `<arch><sub>-<vendor>-<sys>-<abi>` where:
* - `arch` = The base CPU architecture, for example `x86_64`, `i686`, `arm`, `thumb`, `mips`, etc.
* - `sub` = The CPU sub-architecture, for example `arm` has `v7`, `v7s`, `v5te`, etc.
* - `vendor` = The vendor, for example `unknown`, `apple`, `pc`, `nvidia`, etc.
* - `sys` = The system name, for example `linux`, `windows`, `darwin`, etc. none is typically used for bare-metal without an OS.
* - `abi` = The ABI, for example `gnu`, `android`, `eabi`, etc.
*/
function parseTriple(rawTriple) {
if (rawTriple === "wasm32-wasi" || rawTriple === "wasm32-wasi-preview1-threads" || rawTriple.startsWith("wasm32-wasip")) return {
triple: rawTriple,
platformArchABI: "wasm32-wasi",
platform: "wasi",
arch: "wasm32",
abi: "wasi"
};
const triples = (rawTriple.endsWith("eabi") ? `${rawTriple.slice(0, -4)}-eabi` : rawTriple).split("-");
let cpu;
let sys;
let abi = null;
if (triples.length === 2) [cpu, sys] = triples;
else [cpu, , sys, abi = null] = triples;
if (abi && SUB_SYSTEMS.has(abi)) {
sys = abi;
abi = null;
}
const platform = SysToNodePlatform[sys] ?? sys;
const arch = CpuToNodeArch[cpu] ?? cpu;
return {
triple: rawTriple,
platformArchABI: abi ? `${platform}-${arch}-${abi}` : `${platform}-${arch}`,
platform,
arch,
abi
};
}
function getSystemDefaultTarget() {
const host = execSync(`rustc -vV`, { env: process.env }).toString("utf8").split("\n").find((line) => line.startsWith("host: "));
const triple = host === null || host === void 0 ? void 0 : host.slice(6);
if (!triple) throw new TypeError(`Can not parse target triple from host`);
return parseTriple(triple);
}
function getTargetLinker(target) {
return TARGET_LINKER[target];
}
function targetToEnvVar(target) {
return target.replace(/-/g, "_").toUpperCase();
}
//#endregion
//#region src/utils/version.ts
let NapiVersion = /* @__PURE__ */ function(NapiVersion$1) {
NapiVersion$1[NapiVersion$1["Napi1"] = 1] = "Napi1";
NapiVersion$1[NapiVersion$1["Napi2"] = 2] = "Napi2";
NapiVersion$1[NapiVersion$1["Napi3"] = 3] = "Napi3";
NapiVersion$1[NapiVersion$1["Napi4"] = 4] = "Napi4";
NapiVersion$1[NapiVersion$1["Napi5"] = 5] = "Napi5";
NapiVersion$1[NapiVersion$1["Napi6"] = 6] = "Napi6";
NapiVersion$1[NapiVersion$1["Napi7"] = 7] = "Napi7";
NapiVersion$1[NapiVersion$1["Napi8"] = 8] = "Napi8";
NapiVersion$1[NapiVersion$1["Napi9"] = 9] = "Napi9";
return NapiVersion$1;
}({});
const NAPI_VERSION_MATRIX = new Map([
[NapiVersion.Napi1, "8.6.0 | 9.0.0 | 10.0.0"],
[NapiVersion.Napi2, "8.10.0 | 9.3.0 | 10.0.0"],
[NapiVersion.Napi3, "6.14.2 | 8.11.2 | 9.11.0 | 10.0.0"],
[NapiVersion.Napi4, "10.16.0 | 11.8.0 | 12.0.0"],
[NapiVersion.Napi5, "10.17.0 | 12.11.0 | 13.0.0"],
[NapiVersion.Napi6, "10.20.0 | 12.17.0 | 14.0.0"],
[NapiVersion.Napi7, "10.23.0 | 12.19.0 | 14.12.0 | 15.0.0"],
[NapiVersion.Napi8, "12.22.0 | 14.17.0 | 15.12.0 | 16.0.0"],
[NapiVersion.Napi9, "18.17.0 | 20.3.0 | 21.1.0"]
]);
function parseNodeVersion(v) {
const matches = v.match(/v?([0-9]+)\.([0-9]+)\.([0-9]+)/i);
if (!matches) throw new Error("Unknown node version number: " + v);
const [, major, minor, patch] = matches;
return {
major: parseInt(major),
minor: parseInt(minor),
patch: parseInt(patch)
};
}
function requiredNodeVersions(napiVersion) {
const requirement = NAPI_VERSION_MATRIX.get(napiVersion);
if (!requirement) return [parseNodeVersion("10.0.0")];
return requirement.split("|").map(parseNodeVersion);
}
function toEngineRequirement(versions) {
const requirements = [];
versions.forEach((v, i) => {
let req = "";
if (i !== 0) {
const lastVersion = versions[i - 1];
req += `< ${lastVersion.major + 1}`;
}
req += `${i === 0 ? "" : " || "}>= ${v.major}.${v.minor}.${v.patch}`;
requirements.push(req);
});
return requirements.join(" ");
}
function napiEngineRequirement(napiVersion) {
return toEngineRequirement(requiredNodeVersions(napiVersion));
}
//#endregion
//#region src/utils/metadata.ts
async function parseMetadata(manifestPath) {
if (!fs.existsSync(manifestPath)) throw new Error(`No crate found in manifest: ${manifestPath}`);
const childProcess = spawn("cargo", [
"metadata",
"--manifest-path",
manifestPath,
"--format-version",
"1"
], { stdio: "pipe" });
let stdout = "";
let stderr = "";
let status = 0;
childProcess.stdout.on("data", (data) => {
stdout += data;
});
childProcess.stderr.on("data", (data) => {
stderr += data;
});
await new Promise((resolve$1) => {
childProcess.on("close", (code) => {
status = code ?? 0;
resolve$1();
});
});
if (status !== 0) {
const simpleMessage = `cargo metadata exited with code ${status}`;
throw new Error(`${simpleMessage} and error message:\n\n${stderr}`, { cause: new Error(simpleMessage) });
}
try {
return JSON.parse(stdout);
} catch (e) {
throw new Error("Failed to parse cargo metadata JSON", { cause: e });
}
}
//#endregion
//#region src/utils/config.ts
async function readNapiConfig(path$1, configPath) {
if (configPath && !await fileExists(configPath)) throw new Error(`NAPI-RS config not found at ${configPath}`);
if (!await fileExists(path$1)) throw new Error(`package.json not found at ${path$1}`);
const content = await readFileAsync(path$1, "utf8");
let pkgJson;
try {
pkgJson = JSON.parse(content);
} catch (e) {
throw new Error(`Failed to parse package.json at ${path$1}`, { cause: e });
}
let separatedConfig;
if (configPath) {
const configContent = await readFileAsync(configPath, "utf8");
try {
separatedConfig = JSON.parse(configContent);
} catch (e) {
throw new Error(`Failed to parse NAPI-RS config at ${configPath}`, { cause: e });
}
}
const userNapiConfig = pkgJson.napi ?? {};
if (pkgJson.napi && separatedConfig) {
const pkgJsonPath = underline(path$1);
const configPathUnderline = underline(configPath);
console.warn(yellow(`Both napi field in ${pkgJsonPath} and [NAPI-RS config](${configPathUnderline}) file are found, the NAPI-RS config file will be used.`));
Object.assign(userNapiConfig, separatedConfig);
}
const napiConfig = merge({
binaryName: "index",
packageName: pkgJson.name,
targets: [],
packageJson: pkgJson,
npmClient: "npm"
}, omit(userNapiConfig, ["targets"]));
let targets = userNapiConfig.targets ?? [];
if (userNapiConfig === null || userNapiConfig === void 0 ? void 0 : userNapiConfig.name) {
console.warn(yellow(`[DEPRECATED] napi.name is deprecated, use napi.binaryName instead.`));
napiConfig.binaryName = userNapiConfig.name;
}
if (!targets.length) {
var _userNapiConfig$tripl, _userNapiConfig$tripl2;
let deprecatedWarned = false;
const warning = yellow(`[DEPRECATED] napi.triples is deprecated, use napi.targets instead.`);
if ((_userNapiConfig$tripl = userNapiConfig.triples) === null || _userNapiConfig$tripl === void 0 ? void 0 : _userNapiConfig$tripl.defaults) {
deprecatedWarned = true;
console.warn(warning);
targets = targets.concat(DEFAULT_TARGETS);
}
if ((_userNapiConfig$tripl2 = userNapiConfig.triples) === null || _userNapiConfig$tripl2 === void 0 || (_userNapiConfig$tripl2 = _userNapiConfig$tripl2.additional) === null || _userNapiConfig$tripl2 === void 0 ? void 0 : _userNapiConfig$tripl2.length) {
targets = targets.concat(userNapiConfig.triples.additional);
if (!deprecatedWarned) console.warn(warning);
}
}
if (new Set(targets).size !== targets.length) {
const duplicateTarget = targets.find((target, index) => targets.indexOf(target) !== index);
throw new Error(`Duplicate targets are not allowed: ${duplicateTarget}`);
}
napiConfig.targets = targets.map(parseTriple);
return napiConfig;
}
//#endregion
//#region src/utils/cargo.ts
function tryInstallCargoBinary(name$1, bin$1) {
if (detectCargoBinary(bin$1)) {
debug$9("Cargo binary already installed: %s", name$1);
return;
}
try {
debug$9("Installing cargo binary: %s", name$1);
execSync(`cargo install ${name$1}`, { stdio: "inherit" });
} catch (e) {
throw new Error(`Failed to install cargo binary: ${name$1}`, { cause: e });
}
}
function detectCargoBinary(bin$1) {
debug$9("Detecting cargo binary: %s", bin$1);
try {
execSync(`cargo help ${bin$1}`, { stdio: "ignore" });
debug$9("Cargo binary detected: %s", bin$1);
return true;
} catch {
debug$9("Cargo binary not detected: %s", bin$1);
return false;
}
}
//#endregion
//#region src/utils/typegen.ts
const TOP_LEVEL_NAMESPACE = "__TOP_LEVEL_MODULE__";
const DEFAULT_TYPE_DEF_HEADER = `/* auto-generated by NAPI-RS */
/* eslint-disable */
`;
var TypeDefKind = /* @__PURE__ */ function(TypeDefKind$1) {
TypeDefKind$1["Const"] = "const";
TypeDefKind$1["Enum"] = "enum";
TypeDefKind$1["StringEnum"] = "string_enum";
TypeDefKind$1["Interface"] = "interface";
TypeDefKind$1["Type"] = "type";
TypeDefKind$1["Fn"] = "fn";
TypeDefKind$1["Struct"] = "struct";
TypeDefKind$1["Extends"] = "extends";
TypeDefKind$1["Impl"] = "impl";
return TypeDefKind$1;
}(TypeDefKind || {});
function prettyPrint(line, constEnum, ident, ambient = false) {
let s = line.js_doc ?? "";
switch (line.kind) {
case TypeDefKind.Interface:
s += `export interface ${line.name} {\n${line.def}\n}`;
break;
case TypeDefKind.Type:
s += `export type ${line.name} = \n${line.def}`;
break;
case TypeDefKind.Enum:
const enumName = constEnum ? "const enum" : "enum";
s += `${exportDeclare(ambient)} ${enumName} ${line.name} {\n${line.def}\n}`;
break;
case TypeDefKind.StringEnum:
if (constEnum) s += `${exportDeclare(ambient)} const enum ${line.name} {\n${line.def}\n}`;
else s += `export type ${line.name} = ${line.def.replaceAll(/.*=/g, "").replaceAll(",", "|")};`;
break;
case TypeDefKind.Struct:
const extendsDef = line.extends ? ` extends ${line.extends}` : "";
if (line.extends) {
const genericMatch = line.extends.match(/Iterator<(.+)>$/);
if (genericMatch) {
const [T, TResult, TNext] = genericMatch[1].split(",").map((p) => p.trim());
line.def = line.def + `\nnext(value?: ${TNext}): IteratorResult<${T}, ${TResult}>`;
}
}
s += `${exportDeclare(ambient)} class ${line.name}${extendsDef} {\n${line.def}\n}`;
if (line.original_name && line.original_name !== line.name) s += `\nexport type ${line.original_name} = ${line.name}`;
break;
case TypeDefKind.Fn:
s += `${exportDeclare(ambient)} ${line.def}`;
break;
default: s += line.def;
}
return correctStringIdent(s, ident);
}
function exportDeclare(ambient) {
if (ambient) return "export";
return "export declare";
}
async function processTypeDef(intermediateTypeFile, constEnum) {
const exports$1 = [];
const defs = await readIntermediateTypeFile(intermediateTypeFile);
const groupedDefs = preprocessTypeDef(defs);
return {
dts: sortBy(Array.from(groupedDefs), [([namespace]) => namespace]).map(([namespace, defs$1]) => {
if (namespace === TOP_LEVEL_NAMESPACE) return defs$1.map((def) => {
switch (def.kind) {
case TypeDefKind.Const:
case TypeDefKind.Enum:
case TypeDefKind.StringEnum:
case TypeDefKind.Fn:
case TypeDefKind.Struct:
exports$1.push(def.name);
if (def.original_name && def.original_name !== def.name) exports$1.push(def.original_name);
break;
default: break;
}
return prettyPrint(def, constEnum, 0);
}).join("\n\n");
else {
exports$1.push(namespace);
let declaration = "";
declaration += `export declare namespace ${namespace} {\n`;
for (const def of defs$1) declaration += prettyPrint(def, constEnum, 2, true) + "\n";
declaration += "}";
return declaration;
}
}).join("\n\n") + "\n",
exports: exports$1
};
}
async function readIntermediateTypeFile(file) {
return (await readFileAsync(file, "utf8")).split("\n").filter(Boolean).map((line) => {
line = line.trim();
const parsed = JSON.parse(line);
if (parsed.js_doc) parsed.js_doc = parsed.js_doc.replace(/\\n/g, "\n");
if (parsed.def) parsed.def = parsed.def.replace(/\\n/g, "\n");
return parsed;
}).sort((a, b) => {
if (a.kind === TypeDefKind.Struct) {
if (b.kind === TypeDefKind.Struct) return a.name.localeCompare(b.name);
return -1;
} else if (b.kind === TypeDefKind.Struct) return 1;
else return a.name.localeCompare(b.name);
});
}
function preprocessTypeDef(defs) {
const namespaceGrouped = /* @__PURE__ */ new Map();
const classDefs = /* @__PURE__ */ new Map();
for (const def of defs) {
const namespace = def.js_mod ?? TOP_LEVEL_NAMESPACE;
if (!namespaceGrouped.has(namespace)) namespaceGrouped.set(namespace, []);
const group = namespaceGrouped.get(namespace);
if (def.kind === TypeDefKind.Struct) {
group.push(def);
classDefs.set(def.name, def);
} else if (def.kind === TypeDefKind.Extends) {
const classDef = classDefs.get(def.name);
if (classDef) classDef.extends = def.def;
} else if (def.kind === TypeDefKind.Impl) {
const classDef = classDefs.get(def.name);
if (classDef) {
if (classDef.def) classDef.def += "\n";
classDef.def += def.def;
if (classDef.def) classDef.def = classDef.def.replace(/\\n/g, "\n");
}
} else group.push(def);
}
return namespaceGrouped;
}
function correctStringIdent(src, ident) {
let bracketDepth = 0;
return src.split("\n").map((line) => {
line = line.trim();
if (line === "") return "";
const isInMultilineComment = line.startsWith("*");
const isClosingBracket = line.endsWith("}");
const isOpeningBracket = line.endsWith("{");
const isTypeDeclaration = line.endsWith("=");
const isTypeVariant = line.startsWith("|");
let rightIndent = ident;
if ((isOpeningBracket || isTypeDeclaration) && !isInMultilineComment) {
bracketDepth += 1;
rightIndent += (bracketDepth - 1) * 2;
} else {
if (isClosingBracket && bracketDepth > 0 && !isInMultilineComment && !isTypeVariant) bracketDepth -= 1;
rightIndent += bracketDepth * 2;
}
if (isInMultilineComment) rightIndent += 1;
return `${" ".repeat(rightIndent)}${line}`;
}).join("\n");
}
//#endregion
//#region src/utils/read-config.ts
async function readConfig(options) {
const resolvePath = (...paths) => resolve(options.cwd, ...paths);
return await readNapiConfig(resolvePath(options.packageJsonPath ?? "package.json"), options.configPath ? resolvePath(options.configPath) : void 0);
}
//#endregion
//#region src/api/artifacts.ts
const debug$8 = debugFactory("artifacts");
async function collectArtifacts(userOptions) {
const options = applyDefaultArtifactsOptions(userOptions);
const resolvePath = (...paths) => resolve(options.cwd, ...paths);
const packageJsonPath = resolvePath(options.packageJsonPath);
const { targets, binaryName, packageName } = await readNapiConfig(packageJsonPath, options.configPath ? resolvePath(options.configPath) : void 0);
const distDirs = targets.map((platform) => join(options.cwd, options.npmDir, platform.platformArchABI));
const universalSourceBins = new Set(targets.filter((platform) => platform.arch === "universal").flatMap((p) => {
var _UniArchsByPlatform$p;
return (_UniArchsByPlatform$p = UniArchsByPlatform[p.platform]) === null || _UniArchsByPlatform$p === void 0 ? void 0 : _UniArchsByPlatform$p.map((a) => `${p.platform}-${a}`);
}).filter(Boolean));
await collectNodeBinaries(join(options.cwd, options.outputDir)).then((output) => Promise.all(output.map(async (filePath) => {
debug$8.info(`Read [${colors.yellowBright(filePath)}]`);
const sourceContent = await readFileAsync(filePath);
const parsedName = parse(filePath);
const terms = parsedName.name.split(".");
const platformArchABI = terms.pop();
const _binaryName = terms.join(".");
if (_binaryName !== binaryName) {
debug$8.warn(`[${_binaryName}] is not matched with [${binaryName}], skip`);
return;
}
const dir$1 = distDirs.find((dir$2) => dir$2.includes(platformArchABI));
if (!dir$1 && universalSourceBins.has(platformArchABI)) {
debug$8.warn(`[${platformArchABI}] has no dist dir but it is source bin for universal arch, skip`);
return;
}
if (!dir$1) throw new Error(`No dist dir found for ${filePath}`);
const distFilePath = join(dir$1, parsedName.base);
debug$8.info(`Write file content to [${colors.yellowBright(distFilePath)}]`);
await writeFileAsync(distFilePath, sourceContent);
const distFilePathLocal = join(parse(packageJsonPath).dir, parsedName.base);
debug$8.info(`Write file content to [${colors.yellowBright(distFilePathLocal)}]`);
await writeFileAsync(distFilePathLocal, sourceContent);
})));
const wasiTarget = targets.find((t) => t.platform === "wasi");
if (wasiTarget) {
const wasiDir = join(options.cwd, options.npmDir, wasiTarget.platformArchABI);
const cjsFile = join(options.buildOutputDir ?? options.cwd, `${binaryName}.wasi.cjs`);
const workerFile = join(options.buildOutputDir ?? options.cwd, `wasi-worker.mjs`);
const browserEntry = join(options.buildOutputDir ?? options.cwd, `${binaryName}.wasi-browser.js`);
const browserWorkerFile = join(options.buildOutputDir ?? options.cwd, `wasi-worker-browser.mjs`);
debug$8.info(`Move wasi binding file [${colors.yellowBright(cjsFile)}] to [${colors.yellowBright(wasiDir)}]`);
await writeFileAsync(join(wasiDir, `${binaryName}.wasi.cjs`), await readFileAsync(cjsFile));
debug$8.info(`Move wasi worker file [${colors.yellowBright(workerFile)}] to [${colors.yellowBright(wasiDir)}]`);
await writeFileAsync(join(wasiDir, `wasi-worker.mjs`), await readFileAsync(workerFile));
debug$8.info(`Move wasi browser entry file [${colors.yellowBright(browserEntry)}] to [${colors.yellowBright(wasiDir)}]`);
await writeFileAsync(join(wasiDir, `${binaryName}.wasi-browser.js`), (await readFileAsync(browserEntry, "utf8")).replace(`new URL('./wasi-worker-browser.mjs', import.meta.url)`, `new URL('${packageName}-wasm32-wasi/wasi-worker-browser.mjs', import.meta.url)`));
debug$8.info(`Move wasi browser worker file [${colors.yellowBright(browserWorkerFile)}] to [${colors.yellowBright(wasiDir)}]`);
await writeFileAsync(join(wasiDir, `wasi-worker-browser.mjs`), await readFileAsync(browserWorkerFile));
}
}
async function collectNodeBinaries(root) {
const files$1 = await readdirAsync(root, { withFileTypes: true });
const nodeBinaries = files$1.filter((file) => file.isFile() && (file.name.endsWith(".node") || file.name.endsWith(".wasm"))).map((file) => join(root, file.name));
const dirs = files$1.filter((file) => file.isDirectory());
for (const dir$1 of dirs) if (dir$1.name !== "node_modules") nodeBinaries.push(...await collectNodeBinaries(join(root, dir$1.name)));
return nodeBinaries;
}
//#endregion
//#region src/api/templates/js-binding.ts
function createCjsBinding(localName, pkgName, idents, packageVersion) {
return `${bindingHeader}
${createCommonBinding(localName, pkgName, packageVersion)}
module.exports = nativeBinding
${idents.map((ident) => `module.exports.${ident} = nativeBinding.${ident}`).join("\n")}
`;
}
function createEsmBinding(localName, pkgName, idents, packageVersion) {
return `${bindingHeader}
import { createRequire } from 'node:module'
const require = createRequire(import.meta.url)
const __dirname = new URL('.', import.meta.url).pathname
${createCommonBinding(localName, pkgName, packageVersion)}
const { ${idents.join(", ")} } = nativeBinding
${idents.map((ident) => `export { ${ident} }`).join("\n")}
`;
}
const bindingHeader = `// prettier-ignore
/* eslint-disable */
// @ts-nocheck
/* auto-generated by NAPI-RS */
`;
function createCommonBinding(localName, pkgName, packageVersion) {
function requireTuple(tuple, identSize = 8) {
const identLow = " ".repeat(identSize - 2);
const ident = " ".repeat(identSize);
const versionCheck = packageVersion ? `
${identLow}try {
${ident}const binding = require('${pkgName}-${tuple}')
${ident}const bindingPackageVersion = require('${pkgName}-${tuple}/package.json').version
${ident}if (bindingPackageVersion !== '${packageVersion}' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
${ident} throw new Error(\`Native binding package version mismatch, expected ${packageVersion} but got \${bindingPackageVersion}. You can reinstall dependencies to fix this issue.\`)
${ident}}
${ident}return binding
${identLow}} catch (e) {
${ident}loadErrors.push(e)
${identLow}}` : `
${identLow}try {
${ident}return require('${pkgName}-${tuple}')
${identLow}} catch (e) {
${ident}loadErrors.push(e)
${identLow}}`;
return `try {
${ident}return require('./${localName}.${tuple}.node')
${identLow}} catch (e) {
${ident}loadErrors.push(e)
${identLow}}${versionCheck}`;
}
return `const { readFileSync } = require('node:fs')
let nativeBinding = null
const loadErrors = []
const isMusl = () => {
let musl = false
if (process.platform === 'linux') {
musl = isMuslFromFilesystem()
if (musl === null) {
musl = isMuslFromReport()
}
if (musl === null) {
musl = isMuslFromChildProcess()
}
}
return musl
}
const isFileMusl = (f) => f.includes('libc.musl-') || f.includes('ld-musl-')
const isMuslFromFilesystem = () => {
try {
return readFileSync('/usr/bin/ldd', 'utf-8').includes('musl')
} catch {
return null
}
}
const isMuslFromReport = () => {
let report = null
if (typeof process.report?.getReport === 'function') {
process.report.excludeNetwork = true
report = process.report.getReport()
}
if (!report) {
return null
}
if (report.header && report.header.glibcVersionRuntime) {
return false
}
if (Array.isArray(report.sharedObjects)) {
if (report.sharedObjects.some(isFileMusl)) {
return true
}
}
return false
}
const isMuslFromChildProcess = () => {
try {
return require('child_process').execSync('ldd --version', { encoding: 'utf8' }).includes('musl')
} catch (e) {
// If we reach this case, we don't know if the system is musl or not, so is better to just fallback to false
return false
}
}
function requireNative() {
if (process.env.NAPI_RS_NATIVE_LIBRARY_PATH) {
try {
return require(process.env.NAPI_RS_NATIVE_LIBRARY_PATH);
} catch (err) {
loadErrors.push(err)
}
} else if (process.platform === 'android') {
if (process.arch === 'arm64') {
${requireTuple("android-arm64")}
} else if (process.arch === 'arm') {
${requireTuple("android-arm-eabi")}
} else {
loadErrors.push(new Error(\`Unsupported architecture on Android \${process.arch}\`))
}
} else if (process.platform === 'win32') {
if (process.arch === 'x64') {
if (process.config?.variables?.shlib_suffix === 'dll.a' || process.config?.variables?.node_target_type === 'shared_library') {
${requireTuple("win32-x64-gnu")}
} else {
${requireTuple("win32-x64-msvc")}
}
} else if (process.arch === 'ia32') {
${requireTuple("win32-ia32-msvc")}
} else if (process.arch === 'arm64') {
${requireTuple("win32-arm64-msvc")}
} else {
loadErrors.push(new Error(\`Unsupported architecture on Windows: \${process.arch}\`))
}
} else if (process.platform === 'darwin') {
${requireTuple("darwin-universal", 6)}
if (process.arch === 'x64') {
${requireTuple("darwin-x64")}
} else if (process.arch === 'arm64') {
${requireTuple("darwin-arm64")}
} else {
loadErrors.push(new Error(\`Unsupported architecture on macOS: \${process.arch}\`))
}
} else if (process.platform === 'freebsd') {
if (process.arch === 'x64') {
${requireTuple("freebsd-x64")}
} else if (process.arch === 'arm64') {
${requireTuple("freebsd-arm64")}
} else {
loadErrors.push(new Error(\`Unsupported architecture on FreeBSD: \${process.arch}\`))
}
} else if (process.platform === 'linux') {
if (process.arch === 'x64') {
if (isMusl()) {
${requireTuple("linux-x64-musl", 10)}
} else {
${requireTuple("linux-x64-gnu", 10)}
}
} else if (process.arch === 'arm64') {
if (isMusl()) {
${requireTuple("linux-arm64-musl", 10)}
} else {
${requireTuple("linux-arm64-gnu", 10)}
}
} else if (process.arch === 'arm') {
if (isMusl()) {
${requireTuple("linux-arm-musleabihf", 10)}
} else {
${requireTuple("linux-arm-gnueabihf", 10)}
}
} else if (process.arch === 'loong64') {
if (isMusl()) {
${requireTuple("linux-loong64-musl", 10)}
} else {
${requireTuple("linux-loong64-gnu", 10)}
}
} else if (process.arch === 'riscv64') {
if (isMusl()) {
${requireTuple("linux-riscv64-musl", 10)}
} else {
${requireTuple("linux-riscv64-gnu", 10)}
}
} else if (process.arch === 'ppc64') {
${requireTuple("linux-ppc64-gnu")}
} else if (process.arch === 's390x') {
${requireTuple("linux-s390x-gnu")}
} else {
loadErrors.push(new Error(\`Unsupported architecture on Linux: \${process.arch}\`))
}
} else if (process.platform === 'openharmony') {
if (process.arch === 'arm64') {
${requireTuple("openharmony-arm64")}
} else if (process.arch === 'x64') {
${requireTuple("openharmony-x64")}
} else if (process.arch === 'arm') {
${requireTuple("openharmony-arm")}
} else {
loadErrors.push(new Error(\`Unsupported architecture on OpenHarmony: \${process.arch}\`))
}
} else {
loadErrors.push(new Error(\`Unsupported OS: \${process.platform}, architecture: \${process.arch}\`))
}
}
nativeBinding = requireNative()
if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) {
let wasiBinding = null
let wasiBindingError = null
try {
wasiBinding = require('./${localName}.wasi.cjs')
nativeBinding = wasiBinding
} catch (err) {
if (process.env.NAPI_RS_FORCE_WASI) {
wasiBindingError = err
}
}
if (!nativeBinding) {
try {
wasiBinding = require('${pkgName}-wasm32-wasi')
nativeBinding = wasiBinding
} catch (err) {
if (process.env.NAPI_RS_FORCE_WASI) {
wasiBindingError.cause = err
loadErrors.push(err)
}
}
}
if (process.env.NAPI_RS_FORCE_WASI === 'error' && !wasiBinding) {
const error = new Error('WASI binding not found and NAPI_RS_FORCE_WASI is set to error')
error.cause = wasiBindingError
throw error
}
}
if (!nativeBinding) {
if (loadErrors.length > 0) {
throw new Error(
\`Cannot find native binding. \` +
\`npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). \` +
'Please try \`npm i\` again after removing both package-lock.json and node_modules directory.',
{
cause: loadErrors.reduce((err, cur) => {
cur.cause = err
return cur
}),
},
)
}
throw new Error(\`Failed to load native binding\`)
}
`;
}
//#endregion
//#region src/api/templates/load-wasi-template.ts
const createWasiBrowserBinding = (wasiFilename, initialMemory = 4e3, maximumMemory = 65536, fs$1 = false, asyncInit = false, buffer = false) => {
return `import {
createOnMessage as __wasmCreateOnMessageForFsProxy,
getDefaultContext as __emnapiGetDefaultContext,
${asyncInit ? `instantiateNapiModule as __emnapiInstantiateNapiModule` : `instantiateNapiModuleSync as __emnapiInstantiateNapiModuleSync`},
WASI as __WASI,
} from '@napi-rs/wasm-runtime'
${fs$1 ? buffer ? `import { memfs, Buffer } from '@napi-rs/wasm-runtime/fs'` : `import { memfs } from '@napi-rs/wasm-runtime/fs'` : ""}
${buffer && !fs$1 ? `import { Buffer } from 'buffer'` : ""}
${fs$1 ? `
export const { fs: __fs, vol: __volume } = memfs()
const __wasi = new __WASI({
version: 'preview1',
fs: __fs,
preopens: {
'/': '/',
},
})` : `
const __wasi = new __WASI({
version: 'preview1',
})`}
const __wasmUrl = new URL('./${wasiFilename}.wasm', import.meta.url).href
const __emnapiContext = __emnapiGetDefaultContext()
${buffer ? "__emnapiContext.feature.Buffer = Buffer" : ""}
const __sharedMemory = new WebAssembly.Memory({
initial: ${initialMemory},
maximum: ${maximumMemory},
shared: true,
})
const __wasmFile = await fetch(__wasmUrl).then((res) => res.arrayBuffer())
const {
instance: __napiInstance,
module: __wasiModule,
napiModule: __napiModule,
} = ${asyncInit ? `await __emnapiInstantiateNapiModule` : `__emnapiInstantiateNapiModuleSync`}(__wasmFile, {
context: __emnapiContext,
asyncWorkPoolSize: 4,
wasi: __wasi,
onCreateWorker() {
const worker = new Worker(new URL('./wasi-worker-browser.mjs', import.meta.url), {
type: 'module',
})
${fs$1 ? ` worker.addEventListener('message', __wasmCreateOnMessageForFsProxy(__fs))\n` : ""}
return worker
},
overwriteImports(importObject) {
importObject.env = {
...importObject.env,
...importObject.napi,
...importObject.emnapi,
memory: __sharedMemory,
}
return importObject
},
beforeInit({ instance }) {
for (const name of Object.keys(instance.exports)) {
if (name.startsWith('__napi_register__')) {
instance.exports[name]()
}
}
},
})
`;
};
const createWasiBinding = (wasmFileName, packageName, initialMemory = 4e3, maximumMemory = 65536) => `/* eslint-disable */
/* prettier-ignore */
/* auto-generated by NAPI-RS */
const __nodeFs = require('node:fs')
const __nodePath = require('node:path')
const { WASI: __nodeWASI } = require('node:wasi')
const { Worker } = require('node:worker_threads')
const {
createOnMessage: __wasmCreateOnMessageForFsProxy,
getDefaultContext: __emnapiGetDefaultContext,
instantiateNapiModuleSync: __emnapiInstantiateNapiModuleSync,
} = require('@napi-rs/wasm-runtime')
const __rootDir = __nodePath.parse(process.cwd()).root
const __wasi = new __nodeWASI({
version: 'preview1',
env: process.env,
preopens: {
[__rootDir]: __rootDir,
}
})
const __emnapiContext = __emnapiGetDefaultContext()
const __sharedMemory = new WebAssembly.Memory({
initial: ${initialMemory},
maximum: ${maximumMemory},
shared: true,
})
let __wasmFilePath = __nodePath.join(__dirname, '${wasmFileName}.wasm')
const __wasmDebugFilePath = __nodePath.join(__dirname, '${wasmFileName}.debug.wasm')
if (__nodeFs.existsSync(__wasmDebugFilePath)) {
__wasmFilePath = __wasmDebugFilePath
} else if (!__nodeFs.existsSync(__wasmFilePath)) {
try {
__wasmFilePath = __nodePath.resolve('${packageName}-wasm32-wasi')
} catch {
throw new Error('Cannot find ${wasmFileName}.wasm file, and ${packageName}-wasm32-wasi package is not installed.')
}
}
const { instance: __napiInstance, module: __wasiModule, napiModule: __napiModule } = __emnapiInstantiateNapiModuleSync(__nodeFs.readFileSync(__wasmFilePath), {
context: __emnapiContext,
asyncWorkPoolSize: (function() {
const threadsSizeFromEnv = Number(process.env.NAPI_RS_ASYNC_WORK_POOL_SIZE ?? process.env.UV_THREADPOOL_SIZE)
// NaN > 0 is false
if (threadsSizeFromEnv > 0) {
return threadsSizeFromEnv
} else {
return 4
}
})(),
reuseWorker: true,
wasi: __wasi,
onCreateWorker() {
const worker = new Worker(__nodePath.join(__dirname, 'wasi-worker.mjs'), {
env: process.env,
})
worker.onmessage = ({ data }) => {
__wasmCreateOnMessageForFsProxy(__nodeFs)(data)
}
// The main thread of Node.js waits for all the active handles before exiting.
// But Rust threads are never waited without \`thread::join\`.
// So here we hack the code of Node.js to prevent the workers from being referenced (active).
// According to https://github.com/nodejs/node/blob/19e0d472728c79d418b74bddff588bea70a403d0/lib/internal/worker.js#L415,
// a worker is consist of two handles: kPublicPort and kHandle.
{
const kPublicPort = Object.getOwnPropertySymbols(worker).find(s =>
s.toString().includes("kPublicPort")
);
if (kPublicPort) {
worker[kPublicPort].ref = () => {};
}
const kHandle = Object.getOwnPropertySymbols(worker).find(s =>
s.toString().includes("kHandle")
);
if (kHandle) {
worker[kHandle].ref = () => {};
}
worker.unref();
}
return worker
},
overwriteImports(importObject) {
importObject.env = {
...importObject.env,
...importObject.napi,
...importObject.emnapi,
memory: __sharedMemory,
}
return importObject
},
beforeInit({ instance }) {
for (const name of Object.keys(instance.exports)) {
if (name.startsWith('__napi_register__')) {
instance.exports[name]()
}
}
},
})
`;
//#endregion
//#region src/api/templates/wasi-worker-template.ts
const WASI_WORKER_TEMPLATE = `import fs from "node:fs";
import { createRequire } from "node:module";
import { parse } from "node:path";
import { WASI } from "node:wasi";
import { parentPort, Worker } from "node:worker_threads";
const require = createRequire(import.meta.url);
const { instantiateNapiModuleSync, MessageHandler, getDefaultContext } = require("@napi-rs/wasm-runtime");
if (parentPort) {
parentPort.on("message", (data) => {
globalThis.onmessage({ data });
});
}
Object.assign(globalThis, {
self: globalThis,
require,
Worker,
importScripts: function (f) {
;(0, eval)(fs.readFileSync(f, "utf8") + "//# sourceURL=" + f);
},
postMessage: function (msg) {
if (parentPort) {
parentPort.postMessage(msg);
}
},
});
const emnapiContext = getDefaultContext();
const __rootDir = parse(process.cwd()).root;
const handler = new MessageHandler({
onLoad({ wasmModule, wasmMemory }) {
const wasi = new WASI({
version: 'preview1',
env: process.env,
preopens: {
[__rootDir]: __rootDir,
},
});
return instantiateNapiModuleSync(wasmModule, {
childThread: true,
wasi,
context: emnapiContext,
overwriteImports(importObject) {
importObject.env = {
...importObject.env,
...importObject.napi,
...importObject.emnapi,
memory: wasmMemory
};
},
});
},
});
globalThis.onmessage = function (e) {
handler.handle(e);
};
`;
const createWasiBrowserWorkerBinding = (fs$1) => {
return `${fs$1 ? `import { instantiateNapiModuleSync, MessageHandler, WASI, createFsProxy } from '@napi-rs/wasm-runtime'
import { memfsExported as __memfsExported } from '@napi-rs/wasm-runtime/fs'
const fs = createFsProxy(__memfsExported)` : `import { instantiateNapiModuleSync, MessageHandler, WASI } from '@napi-rs/wasm-runtime'`}
const handler = new MessageHandler({
onLoad({ wasmModule, wasmMemory }) {
${fs$1 ? `const wasi = new WASI({
fs,
preopens: {
'/': '/',
},
print: function () {
// eslint-disable-next-line no-console
console.log.apply(console, arguments)
},
printErr: function() {
// eslint-disable-next-line no-console
console.error.apply(console, arguments)
},
})` : `const wasi = new WASI({
print: function () {
// eslint-disable-next-line no-console
console.log.apply(console, arguments)
},
printErr: function() {
// eslint-disable-next-line no-console
console.error.apply(console, arguments)
},
})`}
return instantiateNapiModuleSync(wasmModule, {
childThread: true,
wasi,
overwriteImports(importObject) {
importObject.env = {
...importObject.env,
...importObject.napi,
...importObject.emnapi,
memory: wasmMemory,
}
},
})
},
})
globalThis.onmessage = function (e) {
handler.handle(e)
}
`;
};
//#endregion
//#region src/api/build.ts
const debug$7 = debugFactory("build");
const require = createRequire(import.meta.url);
async function buildProject(rawOptions) {
debug$7("napi build command receive options: %O", rawOptions);
const options = {
dtsCache: true,
...rawOptions,
cwd: rawOptions.cwd ?? process.cwd()
};
const resolvePath = (...paths) => resolve(options.cwd, ...paths);
const manifestPath = resolvePath(options.manifestPath ?? "Cargo.toml");
const metadata = await parseMetadata(manifestPath);
const crate = metadata.packages.find((p) => {
if (options.package) return p.name === options.package;
else return p.manifest_path === manifestPath;
});
if (!crate) throw new Error("Unable to find crate to build. It seems you are trying to build a crate in a workspace, try using `--package` option to specify the package to build.");
const config = await readNapiConfig(resolvePath(options.packageJsonPath ?? "package.json"), options.configPath ? resolvePath(options.configPath) : void 0);
return new Builder(metadata, crate, config, options).build();
}
var Builder = class {
args = [];
envs = {};
outputs = [];
target;
crateDir;
outputDir;
targetDir;
enableTypeDef = false;
constructor(metadata, crate, config, options) {
this.metadata = metadata;
this.crate = crate;
this.config = config;
this.options = options;
this.target = options.target ? parseTriple(options.target) : process.env.CARGO_BUILD_TARGET ? parseTriple(process.env.CARGO_BUILD_TARGET) : getSystemDefaultTarget();
this.crateDir = parse(crate.manifest_path).dir;
this.outputDir = resolve(this.options.cwd, options.outputDir ?? this.crateDir);
this.targetDir = options.targetDir ?? process.env.CARGO_BUILD_TARGET_DIR ?? metadata.target_directory;
this.enableTypeDef = this.crate.dependencies.some((dep) => dep.name === "napi-derive" && (dep.uses_default_features || dep.features.includes("type-def")));
if (!this.enableTypeDef) {
const requirementWarning = "`napi-derive` crate is not used or `type-def` feature is not enabled for `napi-derive` crate";
debug$7.warn(`${requirementWarning}. Will skip binding generation for \`.node\`, \`.wasi\` and \`.d.ts\` files.`);
if (this.options.dts || this.options.dtsHeader || this.config.dtsHeader || this.config.dtsHeaderFile) debug$7.warn(`${requirementWarning}. \`dts\` related options are enabled but will be ignored.`);
}
}
get cdyLibName() {
var _this$crate$targets$f;
return (_this$crate$targets$f = this.crate.targets.find((t) => t.crate_types.includes("cdylib"))) === null || _this$crate$targets$f === void 0 ? void 0 : _this$crate$targets$f.name;
}
get binName() {
var _this$crate$targets$f2;
return this.options.bin ?? (this.cdyLibName ? null : (_this$crate$targets$f2 = this.crate.targets.find((t) => t.crate_types.includes("bin"))) === null || _this$crate$targets$f2 === void 0 ? void 0 : _this$crate$targets$f2.name);
}
build() {
if (!this.cdyLibName) {
const warning = "Missing `crate-type = [\"cdylib\"]` in [lib] config. The build result will not be available as node addon.";
if (this.binName) debug$7.warn(warning);
else throw new Error(warning);
}
return this.pickBinary().setPackage().setFeatures().setTarget().pickCrossToolchain().setEnvs().setBypassArgs().exec();
}
pickCrossToolchain() {
if (!this.options.useNapiCross) return this;
if (this.options.useCross) debug$7.warn("You are trying to use both `--cross` and `--use-napi-cross` options, `--use-cross` will be ignored.");
if (this.options.crossCompile) debug$7.warn("You are trying to use both `--cross-compile` and `--use-napi-cross` options, `--cross-compile` will be ignored.");
try {
var _process$env$TARGET_C, _process$env$CC, _process$env$CXX, _process$env$TARGET_C2;
const { version: version$2, download } = require("@napi-rs/cross-toolchain");
const alias = { "s390x-unknown-linux-gnu": "s390x-ibm-linux-gnu" };
const toolchainPath = join(homedir(), ".napi-rs", "cross-toolchain", version$2, this.target.triple);
mkdirSync(toolchainPath, { recursive: true });
if (existsSync(join(toolchainPath, "package.json"))) debug$7(`Toolchain ${toolchainPath} exists, skip extracting`);
else download(process.arch, this.target.triple).unpack(toolchainPath);
const upperCaseTarget = targetToEnvVar(this.target.triple);
const crossTargetName = alias[this.target.triple] ?? this.target.triple;
const linkerEnv = `CARGO_TARGET_${upperCaseTarget}_LINKER`;
this.setEnvIfNotExists(linkerEnv, join(toolchainPath, "bin", `${crossTargetName}-gcc`));
this.setEnvIfNotExists("TARGET_SYSROOT", join(toolchainPath, crossTargetName, "sysroot"));
this.setEnvIfNotExists("TARGET_AR", join(toolchainPath, "bin", `${crossTargetName}-ar`));
this.setEnvIfNotExists("TARGET_RANLIB", join(toolchainPath, "bin", `${crossTargetName}-ranlib`));
this.setEnvIfNotExists("TARGET_READELF", join(toolchainPath, "bin", `${crossTargetName}-readelf`));
this.setEnvIfNotExists("TARGET_C_INCLUDE_PATH", join(toolchainPath, crossTargetName, "sysroot", "usr", "include/"));
this.setEnvIfNotExists("TARGET_CC", join(toolchainPath, "bin", `${crossTargetName}-gcc`));
this.setEnvIfNotExists("TARGET_CXX", join(toolchainPath, "bin", `${crossTargetName}-g++`));
this.setEnvIfNotExists("BINDGEN_EXTRA_CLANG_ARGS", `--sysroot=${this.envs.TARGET_SYSROOT}}`);
if (((_process$env$TARGET_C = process.env.TARGET_CC) === null || _process$env$TARGET_C === void 0 ? void 0 : _process$env$TARGET_C.startsWith("clang")) || ((_process$env$CC = process.env.CC) === null || _process$env$CC === void 0 ? void 0 : _process$env$CC.startsWith("clang")) && !process.env.TARGET_CC) {
const TARG