@napi-rs/cli
Version:
Cli tools for napi-rs
1,430 lines (1,372 loc) • 512 kB
JavaScript
#!/usr/bin/env node
import { createRequire } from "node:module";
import { Cli, Command, Option } from "clipanion";
import path, { join, parse, resolve } from "node:path";
import * as colors from "colorette";
import { underline, yellow } from "colorette";
import rawDebug from "debug";
import fs, { copyFile, existsSync, mkdir, mkdirSync, promises, readFile, readdir, rmSync, stat, statSync, unlink, writeFile } from "node:fs";
import { promisify } from "node:util";
import { exec, execSync, spawn, spawnSync } from "node:child_process";
import { isNil, merge, omit, omitBy, pick, sortBy } from "lodash-es";
import { createHash } from "node:crypto";
import { homedir } from "node:os";
import { parse as parse$1 } from "semver";
import { checkbox, confirm, input, select } from "@inquirer/prompts";
import { dump, load } from "js-yaml";
import * as typanion from "typanion";
import { rename } from "node:fs/promises";
import { findUp } from "find-up";
import { Octokit } from "@octokit/rest";
//#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.0.4";
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 exports = {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/index.cjs"
}
},
"./package.json": {
"import": "./package.json",
"require": "./package.json"
}
};
var files = ["dist", "src"];
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.4.0",
"@napi-rs/cross-toolchain": "^1.0.0",
"@napi-rs/wasm-tools": "^1.0.0",
"@octokit/rest": "^22.0.0",
"clipanion": "^4.0.0-rc.4",
"colorette": "^2.0.20",
"debug": "^4.4.0",
"emnapi": "^1.4.0",
"find-up": "^7.0.0",
"js-yaml": "^4.1.0",
"lodash-es": "^4.17.21",
"semver": "^7.7.1",
"typanion": "^3.14.0"
};
var devDependencies = {
"@emnapi/core": "^1.4.0",
"@emnapi/runtime": "^1.4.0",
"@oxc-node/core": "^0.0.30",
"@std/toml": "npm:@jsr/std__toml@^1.0.8",
"@types/debug": "^4.1.12",
"@types/inquirer": "^9.0.7",
"@types/js-yaml": "^4.0.9",
"@types/lodash-es": "^4.17.12",
"@types/node": "^22.13.16",
"@types/semver": "^7.7.0",
"ava": "^6.2.0",
"env-paths": "^3.0.0",
"prettier": "^3.5.3",
"rolldown": "latest",
"tslib": "^2.8.1",
"typescript": "^5.8.2"
};
var peerDependencies = {
"@emnapi/runtime": "^1.1.0",
"emnapi": "^1.1.0"
};
var peerDependenciesMeta = {
"@emnapi/runtime": { "optional": true },
"emnapi": { "optional": true }
};
var funding = {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
};
var scripts = {
"clean": "node --import @oxc-node/core/register ./clean.ts",
"codegen": "node --import @oxc-node/core/register ./codegen/index.ts",
"build": "tsc && yarn clean && rolldown -c rolldown.config.js",
"test": "node --import @oxc-node/core/register ../node_modules/ava/entrypoints/cli.mjs"
};
var ava = {
"extensions": { "ts": "module" },
"timeout": "1m",
"files": ["**/__tests__/**/*.spec.ts", "e2e/**/*.spec.ts"]
};
var gitHead = "521b5774aa89f1a7c807a6ddba509ce43973f624";
var package_default = {
name,
version: version$1,
description,
author,
homepage,
license,
type,
engines,
bin,
main,
module,
exports,
files,
keywords,
maintainers,
repository,
publishConfig,
bugs,
dependencies,
devDependencies,
peerDependencies,
peerDependenciesMeta,
funding,
scripts,
ava,
gitHead
};
//#endregion
//#region src/utils/misc.ts
const readFileAsync = promisify(readFile);
const writeFileAsync = promisify(writeFile);
const unlinkAsync = promisify(unlink);
const copyFileAsync = promisify(copyFile);
const mkdirAsync = promisify(mkdir);
const statAsync = promisify(stat);
const readdirAsync = promisify(readdir);
async function fileExists(path$1) {
const exists = await statAsync(path$1).then(() => true).catch(() => false);
return exists;
}
async function dirExistsAsync(path$1) {
try {
const stats = await statAsync(path$1);
return stats.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) {
const exists = await fileExists(path$1);
if (!exists) {
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 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-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",
"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",
"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",
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 triple = rawTriple.endsWith("eabi") ? `${rawTriple.slice(0, -4)}-eabi` : rawTriple;
const triples = triple.split("-");
let cpu;
let sys;
let abi = null;
if (triples.length === 2) [cpu, sys] = triples;
else [cpu, , sys, abi = null] = triples;
const platform = SysToNodePlatform[sys] ?? sys;
const arch = CpuToNodeArch[cpu] ?? cpu;
if (rawTriple.includes("ohos")) return {
triple: rawTriple,
platformArchABI: `linux-${arch}-ohos`,
platform: "openharmony",
arch,
abi: null
};
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?.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;
let error = null;
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 (error) throw new Error("cargo metadata failed to run", { cause: error });
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?.name) {
console.warn(yellow(`[DEPRECATED] napi.name is deprecated, use napi.binaryName instead.`));
napiConfig.binaryName = userNapiConfig.name;
}
if (!targets.length) {
let deprecatedWarned = false;
const warning = yellow(`[DEPRECATED] napi.triples is deprecated, use napi.targets instead.`);
if (userNapiConfig.triples?.defaults) {
deprecatedWarned = true;
console.warn(warning);
targets = targets.concat(DEFAULT_TARGETS);
}
if (userNapiConfig.triples?.additional?.length) {
targets = targets.concat(userNapiConfig.triples.additional);
if (!deprecatedWarned) console.warn(warning);
}
}
const uniqueTargets = new Set(targets);
if (uniqueTargets.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["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:
s += `${exportDeclare(ambient)} class ${line.name} {\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);
const 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";
return {
dts,
exports: exports$1
};
}
async function readIntermediateTypeFile(file) {
const content = await readFileAsync(file, "utf8");
const defs = content.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;
});
return defs.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.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;
const result = 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;
const s = `${" ".repeat(rightIndent)}${line}`;
return s;
}).join("\n");
return result;
}
//#endregion
//#region src/utils/read-config.ts
async function readConfig(options) {
const resolvePath = (...paths) => resolve(options.cwd, ...paths);
const config = await readNapiConfig(resolvePath(options.configPath ?? options.packageJsonPath ?? "package.json"), options.configPath ? resolvePath(options.configPath) : void 0);
return config;
}
//#endregion
//#region src/api/artifacts.ts
const debug$8 = debugFactory("artifacts");
async function collectArtifacts(userOptions) {
const options = applyDefaultArtifactsOptions(userOptions);
const packageJsonPath = join(options.cwd, options.packageJsonPath);
const { targets, binaryName, packageName } = await readNapiConfig(packageJsonPath);
const distDirs = targets.map((platform) => join(options.cwd, options.npmDir, platform.platformArchABI));
const universalSourceBins = new Set(targets.filter((platform) => platform.arch === "universal").flatMap((p) => UniArchsByPlatform[p.platform]?.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 = distDirs.find((dir$1) => dir$1.includes(platformArchABI));
if (!dir && universalSourceBins.has(platformArchABI)) {
debug$8.warn(`[${platformArchABI}] has no dist dir but it is source bin for universal arch, skip`);
return;
}
if (!dir) throw new Error(`No dist dir found for ${filePath}`);
const distFilePath = join(dir, 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 of dirs) if (dir.name !== "node_modules") nodeBinaries.push(...await collectNodeBinaries(join(root, dir.name)));
return nodeBinaries;
}
//#endregion
//#region src/commands/artifacts.ts
var ArtifactsCommand = class extends BaseArtifactsCommand {
static usage = Command.Usage({
description: "Copy artifacts from Github Actions into specified dir",
examples: [["$0 artifacts --output-dir ./artifacts --dist ./npm", `Copy [binaryName].[platform].node under current dir(.) into packages under npm dir.
e.g: index.linux-x64-gnu.node --> ./npm/linux-x64-gnu/index.linux-x64-gnu.node`]]
});
static paths = [["artifacts"]];
async execute() {
await collectArtifacts(this.getOptions());
}
};
//#endregion
//#region src/api/templates/js-binding.ts
function createCjsBinding(localName, pkgName, idents) {
return `${bindingHeader}
const { createRequire } = require('node:module')
require = createRequire(__filename)
${createCommonBinding(localName, pkgName)}
module.exports = nativeBinding
${idents.map((ident) => `module.exports.${ident} = nativeBinding.${ident}`).join("\n")}
`;
}
function createEsmBinding(localName, pkgName, idents) {
return `${bindingHeader}
import { createRequire } from 'node:module'
const require = createRequire(import.meta.url)
const __dirname = new URL('.', import.meta.url).pathname
${createCommonBinding(localName, pkgName)}
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) {
function requireTuple(tuple, identSize = 8) {
const identLow = " ".repeat(identSize - 2);
const ident = " ".repeat(identSize);
return `try {
${ident}return require('./${localName}.${tuple}.node')
${identLow}} catch (e) {
${ident}loadErrors.push(e)
${identLow}}
${identLow}try {
${ident}return require('${pkgName}-${tuple}')
${identLow}} catch (e) {
${ident}loadErrors.push(e)
${identLow}}`;
}
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 {
nativeBinding = 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') {
${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 === '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("linux-arm64-ohos")}
} else if (process.arch === 'x64') {
${requireTuple("linux-x64-ohos")}
} else if (process.arch === 'arm') {
${requireTuple("linux-arm-ohos")}
} 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) {
try {
nativeBinding = require('./${localName}.wasi.cjs')
} catch (err) {
if (process.env.NAPI_RS_FORCE_WASI) {
loadErrors.push(err)
}
}
if (!nativeBinding) {
try {
nativeBinding = require('${pkgName}-wasm32-wasi')
} catch (err) {
if (process.env.NAPI_RS_FORCE_WASI) {
loadErrors.push(err)
}
}
}
}
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 }
)
}
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) => {
const fsImport = fs$1 ? buffer ? `import { memfs, Buffer } from '@napi-rs/wasm-runtime/fs'` : `import { memfs } from '@napi-rs/wasm-runtime/fs'` : "";
const bufferImport = buffer && !fs$1 ? `import { Buffer } from 'buffer'` : "";
const wasiCreation = 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 workerFsHandler = fs$1 ? ` worker.addEventListener('message', __wasmCreateOnMessageForFsProxy(__fs))\n` : "";
const emnapiInjectBuffer = buffer ? "__emnapiContext.feature.Buffer = Buffer" : "";
const emnapiInstantiateImport = asyncInit ? `instantiateNapiModule as __emnapiInstantiateNapiModule` : `instantiateNapiModuleSync as __emnapiInstantiateNapiModuleSync`;
const emnapiInstantiateCall = asyncInit ? `await __emnapiInstantiateNapiModule` : `__emnapiInstantiateNapiModuleSync`;
return `import {
createOnMessage as __wasmCreateOnMessageForFsProxy,
getDefaultContext as __emnapiGetDefaultContext,
${emnapiInstantiateImport},
WASI as __WASI,
} from '@napi-rs/wasm-runtime'
${fsImport}
${bufferImport}
${wasiCreation}
const __wasmUrl = new URL('./${wasiFilename}.wasm', import.meta.url).href
const __emnapiContext = __emnapiGetDefaultContext()
${emnapiInjectBuffer}
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,
} = ${emnapiInstantiateCall}(__wasmFile, {
context: __emnapiContext,
asyncWorkPoolSize: 4,
wasi: __wasi,
onCreateWorker() {
const worker = new Worker(new URL('./wasi-worker-browser.mjs', import.meta.url), {
type: 'module',
})
${workerFsHandler}
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) => {
const fsImport = 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 wasiCreation = 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 `${fsImport}
const handler = new MessageHandler({
onLoad({ wasmModule, wasmMemory }) {
${wasiCreation}
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.configPath ?? options.packageJsonPath ?? "package.json"), options.configPath ? resolvePath(options.configPath) : void 0);
const builder = new Builder(metadata, crate, config, options);
return builder.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() {
return this.crate.targets.find((t) => t.crate_types.includes("cdylib"))?.name;
}
get binName() {
return this.options.bin ?? (this.cdyLibName ? null : this.crate.targets.find((t) => t.crate_types.includes("bin"))?.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 {
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 {
const tarArchive = download(process.arch, this.target.triple);
tarArchive.unpack(toolchainPath);
}
const upperCaseTarget = targetToEnvVar(this.target.triple);
const crossTargetName = alias[this.target.triple] ?? this.target.triple;
const linkerEnv = `CARGO_TARGET_${upperCaseTarget}_LINKER`;
this.envs[linkerEnv] = join(toolchainPath, "bin", `${crossTargetName}-gcc`);
if (!process.env.TARGET_SYSROOT) this.envs[`TARGET_SYSROOT`] = join(toolchainPath, crossTargetName, "sysroot");
if (!process.env.TARGET_AR) this.envs[`TARGET_AR`] = join(toolchainPath, "bin", `${crossTargetName}-ar`);
if (!process.env.TARGET_RANLIB) this.envs[`TARGET_RANLIB`] = join(toolchainPath, "bin", `${crossTargetName}-ranlib`);
if (!process.env.TARGET_READELF) this.envs[`TARGET_READELF`] = join(toolchainPath, "bin", `${crossTargetName}-readelf`);
if (!process.env.TARGET_C_INCLUDE_PATH) this.envs[`TARGET_C_INCLUDE_PATH`] = join(toolchainPath, crossTargetName, "sysroot", "usr", "include/");
if (!process.env.TARGET_CC) this.envs[`TARGET_CC`] = join(toolchainPath, "bin", `${crossTargetName}-gcc`);
if (!process.env.TARGET_CXX) this.envs[`TARGET_CXX`] = join(toolchainPath, "bin", `${crossTargetName}-g++`);
if (process.env.TARGET_CC?.startsWith("clang") || process.env.CC?.startsWith("clang") && !process.env.TARGET_CC) {
const TARGET_CFLAGS = process.env.TARGET_CFLAGS ?? "";
this.envs.TARGET_CFLAGS = `--sysroot=${this.envs.TARGET_SYSROOT} --gcc-toolchain=${toolchainPath} ${TARGET_CFLAGS}`;
}
if (process.env.CXX?.startsWith("clang++") && !process.env.TARGET_CXX || process.env.TARGET_CXX?.startsWith("clang++")) {
const TARGET_CXXFLAGS = process.env.TARGET_CXXFLAGS ?? "";
this.envs.TARGET_CXXFLAGS = `--sysroot=${this.envs.TARGET_SYSROOT} --gcc-toolchain=${toolchainPath} ${TARGET_CXXFLAGS}`;
}
this.envs.PATH = this.envs.PATH ? `${toolchainPath}/bin:${this.envs.PATH}:${process.env.PATH}` : `${toolchainPath}/bin:${process.env.PATH}`;
} catch (e) {
debug$7.warn("Pick cross toolchain failed", e);
}
return this;
}
exec() {
debug$7(`Start building crate: ${this.crate.name}`);
debug$7(" %i", `cargo ${this.args.join(" ")}`);
const controller = new AbortController();
const watch = this.options.watch;
const buildTask = new Promise((resolve$1, reject) => {
if (this.options.useCross && this.options.crossCompile) throw new Error("`--use-cross` and `--cross-compile` can not be used together");
const command = process.env.CARGO ?? (this.options.useCross ? "cross" : "cargo");
const buildProcess = spawn(command, this.args, {
env: {
...process.env,
...this.envs
},
stdio: watch ? [
"inherit",
"inherit",
"pipe"
] : "inherit",
cwd: this.options.cwd,
signal: controller.signal
});
buildProcess.once("exit", (code) => {
if (code === 0) {
debug$7("%i", `Build crate ${this.crate.name} successfully!`);
r