@napi-rs/cli
Version:
Cli tools for napi-rs
1,312 lines (1,272 loc) • 595 kB
JavaScript
#!/usr/bin/env node
import { createRequire } from "node:module";
import { Cli, Command, Option } from "clipanion";
import path, { basename, dirname, isAbsolute, join, parse, resolve } from "node:path";
import * as colors from "colorette";
import { underline, yellow } from "colorette";
import { createDebug } from "obug";
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 { Comparator, Range, minVersion, subset } 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
const debugFactory = (namespace) => {
const debug = createDebug(`napi:${namespace}`, { formatters: { i(v) {
return colors.green(v);
} } });
debug.info = (...args) => console.error(colors.black(colors.bgGreen(" INFO ")), ...args);
debug.warn = (...args) => console.error(colors.black(colors.bgYellow(" WARNING ")), ...args);
debug.error = (...args) => console.error(colors.white(colors.bgRed(" ERROR ")), ...args.map((arg) => arg instanceof Error ? arg.stack ?? arg.message : arg));
return debug;
};
const debug$10 = debugFactory("utils");
//#endregion
//#region package.json
var version$1 = "3.7.4";
//#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) {
return access(path).then(() => true, () => false);
}
async function dirExistsAsync(path) {
try {
return (await statAsync(path)).isDirectory();
} catch {
return false;
}
}
function pick$1(o, ...keys) {
return keys.reduce((acc, key) => {
acc[key] = o[key];
return acc;
}, {});
}
function isPlainObject(value) {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
function mergePackageJson(old, partial) {
const merged = { ...old };
for (const [key, value] of Object.entries(partial)) if (isPlainObject(merged[key]) && isPlainObject(value)) merged[key] = mergePackageJson(merged[key], value);
else merged[key] = value;
return merged;
}
async function updatePackageJson(path, partial) {
if (!await fileExists(path)) {
debug$10(`File not exists ${path}`);
return;
}
const old = JSON.parse(await readFileAsync(path, "utf8"));
await writeFileAsync(path, JSON.stringify(mergePackageJson(old, partial), null, 2));
}
const CLI_VERSION = version$1;
//#endregion
//#region src/utils/target.ts
const SUB_SYSTEMS = /* @__PURE__ */ 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) {
NapiVersion[NapiVersion["Napi1"] = 1] = "Napi1";
NapiVersion[NapiVersion["Napi2"] = 2] = "Napi2";
NapiVersion[NapiVersion["Napi3"] = 3] = "Napi3";
NapiVersion[NapiVersion["Napi4"] = 4] = "Napi4";
NapiVersion[NapiVersion["Napi5"] = 5] = "Napi5";
NapiVersion[NapiVersion["Napi6"] = 6] = "Napi6";
NapiVersion[NapiVersion["Napi7"] = 7] = "Napi7";
NapiVersion[NapiVersion["Napi8"] = 8] = "Napi8";
NapiVersion[NapiVersion["Napi9"] = 9] = "Napi9";
NapiVersion[NapiVersion["Napi10"] = 10] = "Napi10";
return NapiVersion;
}({});
const NAPI_VERSION_MATRIX = /* @__PURE__ */ new Map([
[1, "8.6.0 | 9.0.0 | 10.0.0"],
[2, "8.10.0 | 9.3.0 | 10.0.0"],
[3, "6.14.2 | 8.11.2 | 9.11.0 | 10.0.0"],
[4, "10.16.0 | 11.8.0 | 12.0.0"],
[5, "10.17.0 | 12.11.0 | 13.0.0"],
[6, "10.20.0 | 12.17.0 | 14.0.0"],
[7, "10.23.0 | 12.19.0 | 14.12.0 | 15.0.0"],
[8, "12.22.0 | 14.17.0 | 15.12.0 | 16.0.0"],
[9, "18.17.0 | 20.3.0 | 21.1.0"],
[10, "22.14.0 | 23.6.0"]
]);
const SUPPORTED_NAPI_VERSIONS = Object.values(NapiVersion).filter((v) => typeof v === "number");
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
const debug$9 = debugFactory("metadata");
function getNapiDeriveDependentCrates(metadata) {
var _metadata$resolve;
const resolveNodes = (_metadata$resolve = metadata.resolve) === null || _metadata$resolve === void 0 ? void 0 : _metadata$resolve.nodes;
if (!resolveNodes) return metadata.packages.filter((crate) => crate.dependencies.some((d) => d.name === "napi-derive"));
const napiDerivePackageIds = new Set(metadata.packages.filter((p) => p.name === "napi-derive").map((p) => p.id));
const dependentPackageIds = new Set(resolveNodes.filter((node) => node.deps.some((dep) => napiDerivePackageIds.has(dep.pkg))).map((node) => node.id));
return metadata.packages.filter((crate) => dependentPackageIds.has(crate.id));
}
async function parseMetadata(manifestPath, featureOptions) {
var _featureOptions$featu;
if (!fs.existsSync(manifestPath)) throw new Error(`No crate found in manifest: ${manifestPath}`);
const featureArgs = [];
if (featureOptions === null || featureOptions === void 0 ? void 0 : featureOptions.allFeatures) featureArgs.push("--all-features");
if (featureOptions === null || featureOptions === void 0 ? void 0 : featureOptions.noDefaultFeatures) featureArgs.push("--no-default-features");
if (featureOptions === null || featureOptions === void 0 || (_featureOptions$featu = featureOptions.features) === null || _featureOptions$featu === void 0 ? void 0 : _featureOptions$featu.length) featureArgs.push("--features", featureOptions.features.join(","));
if (featureArgs.length) try {
return await execCargoMetadata(manifestPath, featureArgs);
} catch (e) {
debug$9.warn(`cargo metadata failed with feature flags, retrying without them: ${e}`);
}
return execCargoMetadata(manifestPath, []);
}
async function execCargoMetadata(manifestPath, extraArgs) {
const childProcess = spawn("cargo", [
"metadata",
"--manifest-path",
manifestPath,
"--format-version",
"1",
...extraArgs
], { 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;
});
childProcess.on("error", (err) => {
error = err;
});
await new Promise((resolve) => {
childProcess.on("close", (code) => {
status = code ?? 0;
resolve();
});
});
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, configPath) {
if (configPath && !await fileExists(configPath)) throw new Error(`NAPI-RS config not found at ${configPath}`);
if (!await fileExists(path)) throw new Error(`package.json not found at ${path}`);
const content = await readFileAsync(path, "utf8");
let pkgJson;
try {
pkgJson = JSON.parse(content);
} catch (e) {
throw new Error(`Failed to parse package.json at ${path}`, { 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);
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.`));
}
if (separatedConfig) 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, bin) {
if (detectCargoBinary(bin)) {
debug$10("Cargo binary already installed: %s", name);
return;
}
try {
debug$10("Installing cargo binary: %s", name);
execSync(`cargo install ${name}`, { stdio: "inherit" });
} catch (e) {
throw new Error(`Failed to install cargo binary: ${name}`, { cause: e });
}
}
function detectCargoBinary(bin) {
debug$10("Detecting cargo binary: %s", bin);
try {
execSync(`cargo help ${bin}`, { stdio: "ignore" });
debug$10("Cargo binary detected: %s", bin);
return true;
} catch {
debug$10("Cargo binary not detected: %s", bin);
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 */
`;
/**
* Render a single intermediate type-def line as the TypeScript source it
* should produce in `index.d.ts`.
*
* @param line - The intermediate type-def entry to render.
* @param constEnum - When true, emit numeric and string `#[napi]` enums as
* `const enum`. When false (`--no-const-enum`), numeric enums become
* regular runtime enums and string enums fall back to a type-only union
* unless `runtimeStringEnum` is also true.
* @param runtimeStringEnum - When true under `--no-const-enum`, emit
* `#[napi(string_enum)]` as a runtime enum (`export declare enum`)
* instead of a type-only union. No-op when `constEnum` is true.
* @param ident - Indentation level applied to the rendered output.
* @param ambient - When true, emit declarations in the ambient form used
* inside `declare namespace` blocks (e.g. drop the `declare` keyword).
*/
function prettyPrint(line, constEnum, runtimeStringEnum, ident, ambient = false) {
let s = line.js_doc ?? "";
switch (line.kind) {
case "interface":
s += `export interface ${line.name} {\n${line.def}\n}`;
break;
case "type":
s += `export type ${line.name} = \n${line.def}`;
break;
case "enum": {
const enumName = constEnum ? "const enum" : "enum";
s += `${exportDeclare(ambient)} ${enumName} ${line.name} {\n${line.def}\n}`;
break;
}
case "string_enum":
if (constEnum) s += `${exportDeclare(ambient)} const enum ${line.name} {\n${line.def}\n}`;
else if (runtimeStringEnum) s += `${exportDeclare(ambient)} enum ${line.name} {\n${line.def}\n}`;
else s += `export type ${line.name} = ${line.def.replaceAll(/.*=/g, "").replaceAll(",", "|")};`;
break;
case "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 "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";
}
/**
* Read the napi-derive-emitted intermediate type-def file and render its
* entries into the `index.d.ts` source string plus the list of names to
* re-export from `index.js`.
*
* @param intermediateTypeFile - Path to the JSONL type-def file produced
* by napi-derive (one entry per `#[napi]` item).
* @param constEnum - See {@link prettyPrint}.
* @param runtimeStringEnum - See {@link prettyPrint}. Defaults to `false`.
*/
async function processTypeDef(intermediateTypeFile, constEnum, runtimeStringEnum = false) {
const exports = [];
const groupedDefs = preprocessTypeDef(await readIntermediateTypeFile(intermediateTypeFile));
return {
dts: sortBy(Array.from(groupedDefs), [([namespace]) => namespace]).map(([namespace, defs]) => {
if (namespace === TOP_LEVEL_NAMESPACE) return defs.map((def) => {
switch (def.kind) {
case "const":
case "enum":
case "string_enum":
case "fn":
case "struct":
exports.push(def.name);
if (def.original_name && def.original_name !== def.name) exports.push(def.original_name);
break;
default: break;
}
return prettyPrint(def, constEnum, runtimeStringEnum, 0);
}).join("\n\n");
else {
exports.push(namespace);
let declaration = "";
declaration += `export declare namespace ${namespace} {\n`;
for (const def of defs) declaration += prettyPrint(def, constEnum, runtimeStringEnum, 2, true) + "\n";
declaration += "}";
return declaration;
}
}).join("\n\n") + "\n",
exports
};
}
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 === "struct") {
if (b.kind === "struct") return a.name.localeCompare(b.name);
return -1;
} else if (b.kind === "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 === "struct") {
group.push(def);
classDefs.set(def.name, def);
} else if (def.kind === "extends") {
const classDef = classDefs.get(def.name);
if (classDef) classDef.extends = def.def;
} else if (def.kind === "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 = distDirs.find((dir) => dir.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 buildOutputDir = options.buildOutputDir ? resolve(options.cwd, options.buildOutputDir) : options.cwd;
const wasiDir = join(options.cwd, options.npmDir, wasiTarget.platformArchABI);
const cjsFile = join(buildOutputDir, `${binaryName}.wasi.cjs`);
const workerFile = join(buildOutputDir, `wasi-worker.mjs`);
const browserEntry = join(buildOutputDir, `${binaryName}.wasi-browser.js`);
const browserWorkerFile = join(buildOutputDir, `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 = await readdirAsync(root, { withFileTypes: true });
const nodeBinaries = files.filter((file) => file.isFile() && (file.name.endsWith(".node") || file.name.endsWith(".wasm"))).map((file) => join(root, file.name));
const dirs = files.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/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 '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);
return `try {
${ident}return require('./${localName}.${tuple}.node')
${identLow}} catch (e) {
${ident}loadErrors.push(e)
${identLow}}${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 `const { readFileSync } = require('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 (process.report && 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 && process.config.variables && process.config.variables.shlib_suffix === 'dll.a') || (process.config && process.config.variables && 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()
// NAPI_RS_FORCE_WASI is a tri-state flag:
// unset / any other value → native binding preferred, WASI is only a fallback
// 'true' → force WASI fallback even if native loaded
// 'error' → force WASI and throw if no WASI binding is found
// Treating any non-empty string as truthy (the historical behavior) meant
// NAPI_RS_FORCE_WASI=false, NAPI_RS_FORCE_WASI=0, etc. inadvertently triggered
// the WASI path, causing ENOENT for packages shipped without a .wasi.cjs file.
const forceWasi =
process.env.NAPI_RS_FORCE_WASI === 'true' || process.env.NAPI_RS_FORCE_WASI === 'error'
if (!nativeBinding || forceWasi) {
let wasiBinding = null
let wasiBindingError = null
try {
wasiBinding = require('./${localName}.wasi.cjs')
nativeBinding = wasiBinding
} catch (err) {
if (forceWasi) {
wasiBindingError = err
}
}
if (!nativeBinding || forceWasi) {
try {
wasiBinding = require('${pkgName}-wasm32-wasi')
nativeBinding = wasiBinding
} catch (err) {
if (forceWasi) {
if (!wasiBindingError) {
wasiBindingError = err
} else {
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) {
const error = 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.',
)
// assign instead of the \`new Error(message, { cause })\` options form,
// which Node < 16.9 silently ignores
error.cause = loadErrors.reduce((err, cur) => {
cur.cause = err
return cur
})
throw error
}
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 = false, asyncInit = false, buffer = false, errorEvent = 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 ? buffer ? `import { memfs, Buffer } from '@napi-rs/wasm-runtime/fs'` : `import { memfs } from '@napi-rs/wasm-runtime/fs'` : ""}
${buffer && !fs ? `import { Buffer } from 'buffer'` : ""}
${fs ? `
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 ? ` worker.addEventListener('message', __wasmCreateOnMessageForFsProxy(__fs))\n` : ""}
${errorEvent ? ` worker.addEventListener('message', (event) => {
if (event.data && typeof event.data === 'object' && event.data.type === 'error') {
window.dispatchEvent(new CustomEvent('napi-rs-worker-error', { detail: event.data }))
}
})
` : ""}
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 = require.resolve('${packageName}-wasm32-wasi/${wasmFileName}.wasm')
} 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, errorEvent) => {
const fsImport = fs ? `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 errorOutputsAppend = errorEvent ? `\n errorOutputs.push([...arguments])` : "";
const wasiCreation = fs ? `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)
${errorOutputsAppend}
},
})` : `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)
${errorOutputsAppend}
},
})`;
return `${fsImport}
${errorEvent ? `\nconst errorOutputs = []\n` : ""}
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,
}
},
})
},
${errorEvent ? `onError(error) {
postMessage({ type: 'error', error, errorOutputs })
errorOutputs.length = 0
}` : ""}
})
globalThis.onmessage = function (e) {
handler.handle(e)
}
`;
};
//#endregion
//#region src/api/build.ts
const debug$7 = debugFactory("build");
const require$1 = 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()
};
validateCrossCompileFlags(options);
if (options.useNapiCross) {
validateNapiCrossHost();
validateNapiCrossSupport(resolveTarget(options.target).triple);
}
const resolvePath = (...paths) => resolve(options.cwd, ...paths);
const manifestPath = resolvePath(options.manifestPath ?? "Cargo.toml");
const metadata = await parseMetadata(manifestPath, {
features: options.features,
allFeatures: options.allFeatures,
noDefaultFeatures: options.noDefaultFeatures
});
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.");
return new Builder(metadata, crate, await readNapiConfig(resolvePath(options.packageJsonPath ?? "package.json"), options.configPath ? resolvePath(options.configPath) : void 0), options).build();
}
/**
* Resolve the target triple the build will run against, following the same
* precedence the build itself uses: the explicit `--target` option, then the
* `CARGO_BUILD_TARGET` environment variable, then the host default target.
*/
function resolveTarget(targetOption) {
return targetOption ? parseTriple(targetOption) : process.env.CARGO_BUILD_TARGET ? parseTriple(process.env.CARGO_BUILD_TARGET) : getSystemDefaultTarget();
}
/**
* Validate the combination of the cross-compilation related flags.
*
* `--use-cross`, `--use-napi-cross` and `--cross-compile` (`-x`) are three
* mutually exclusive cross-compilation mechanisms; combining any two of them
* leaves both active at once and produces broken builds, so it is rejected
* upfront before any side effect (like auto-installing cargo binaries or
* downloading toolchains) happens.
*
* `--cross-compile` with a `windows-gnu` target is rejected as well: on a
* non-Windows host it routes the build to `cargo xwin build`, but
* `cargo-xwin` only sets up MSVC toolchains — for `windows-gnu` targets it
* silently does nothing and the build dies much later with a cryptic
* ``error: linker `x86_64-w64-mingw32-gcc` not found`` that never mentions
* `cargo-xwin`. Only an explicitly requested target (`--target` or
* `CARGO_BUILD_TARGET`) is inspected, so this validation never has to spawn
* `rustc -vV`; a non-Windows host's default target can never be
* `windows-gnu` anyway.
*/
function validateCrossCompileFlags(options, hostPlatform = process.platform) {
const enabledCrossFlags = [
options.useCross ? "`--use-cross`" : null,
options.useNapiCross ? "`--use-napi-cross`" : null,
options.crossCompile ? "`--cross-compile` (`-x`)" : null
].filter((flag) => flag !== null);
if (enabledCrossFlags.length > 1) throw new Error(`${enabledCrossFlags.join(" and ")} cannot be used together. Please pick exactly one cross-compilation mechanism: \`--use-cross\`, \`--use-napi-cross\`, or \`--cross-compile\` (\`-x\`).`);
if (options.watch && options.useCross) throw new Error("`--watch` cannot be used with `--use-cross`. `cargo watch` only supports the plain `cargo build` flow, please drop one of the two flags.");
if (options.watch && options.crossCompile) throw new Error("`--watch` cannot be used with `--cross-compile` (`-x`). `cargo watch` only supports the plain `cargo build` flow, please drop one of the two flags.");
if (options.crossCompile && hostPlatform !== "win32") {
const explicitTarget = options.target ?? process.env.CARGO_BUILD_TARGET;
if (explicitTarget) {
var _target$abi;
const target = pa