443 lines
16 KiB
JavaScript
443 lines
16 KiB
JavaScript
"use strict";
|
|
var __create = Object.create;
|
|
var __defProp = Object.defineProperty;
|
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
var __getProtoOf = Object.getPrototypeOf;
|
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
var __export = (target, all) => {
|
|
for (var name in all)
|
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
};
|
|
var __copyProps = (to, from, except, desc) => {
|
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
for (let key of __getOwnPropNames(from))
|
|
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
}
|
|
return to;
|
|
};
|
|
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
// If the importer is in node compatibility mode or this is not an ESM
|
|
// file that has been converted to a CommonJS file using a Babel-
|
|
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
mod
|
|
));
|
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
|
|
// src/index.ts
|
|
var src_exports = {};
|
|
__export(src_exports, {
|
|
build: () => build,
|
|
prepareCache: () => prepareCache,
|
|
shouldServe: () => shouldServe,
|
|
startDevServer: () => startDevServer2,
|
|
version: () => version
|
|
});
|
|
module.exports = __toCommonJS(src_exports);
|
|
var import_node_path4 = __toESM(require("path"));
|
|
var import_build_utils6 = require("@vercel/build-utils");
|
|
var import_execa4 = __toESM(require("execa"));
|
|
|
|
// src/lib/rust-toolchain.ts
|
|
var import_build_utils = require("@vercel/build-utils");
|
|
var import_execa = __toESM(require("execa"));
|
|
async function downloadRustToolchain() {
|
|
try {
|
|
await (0, import_execa.default)(
|
|
`curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y`,
|
|
[],
|
|
{ shell: true, stdio: "inherit" }
|
|
);
|
|
} catch (err) {
|
|
let message = "Unknown Error";
|
|
if (err instanceof Error) {
|
|
message = err.message;
|
|
}
|
|
throw new Error(`Installing Rust Toolchain via rustup failed: ${message}`);
|
|
}
|
|
}
|
|
var installRustToolchain = async () => {
|
|
try {
|
|
await (0, import_execa.default)(`rustup -V`, [], { shell: true, stdio: "ignore" });
|
|
(0, import_build_utils.debug)("Rust Toolchain is already installed, skipping download");
|
|
} catch (err) {
|
|
await downloadRustToolchain();
|
|
}
|
|
};
|
|
|
|
// src/lib/cargo.ts
|
|
var import_node_fs = __toESM(require("fs"));
|
|
var import_node_path = __toESM(require("path"));
|
|
var import_toml = __toESM(require("@iarna/toml"));
|
|
var import_execa2 = __toESM(require("execa"));
|
|
async function getCargoMetadata(options) {
|
|
const { stdout: cargoMetaData } = await (0, import_execa2.default)(
|
|
"cargo",
|
|
["metadata", "--format-version", "1"],
|
|
options
|
|
);
|
|
return JSON.parse(cargoMetaData);
|
|
}
|
|
async function findCargoWorkspace(config) {
|
|
const { stdout: projectDescriptionStr } = await (0, import_execa2.default)(
|
|
"cargo",
|
|
["locate-project"],
|
|
config
|
|
);
|
|
const projectDescription = JSON.parse(projectDescriptionStr);
|
|
return {
|
|
toml: await import_toml.default.parse.stream(import_node_fs.default.createReadStream(projectDescription.root)),
|
|
root: projectDescription.root
|
|
};
|
|
}
|
|
async function findCargoBuildConfiguration(workspace) {
|
|
const configPath = import_node_path.default.join(
|
|
import_node_path.default.dirname(workspace.root),
|
|
".cargo/config.toml"
|
|
);
|
|
if (!import_node_fs.default.existsSync(configPath)) {
|
|
return null;
|
|
}
|
|
const config = await import_toml.default.parse.stream(import_node_fs.default.createReadStream(configPath));
|
|
return config;
|
|
}
|
|
function findBinaryName(workspace, entryPath) {
|
|
const { bin } = workspace.toml;
|
|
if (bin) {
|
|
const relativePath = import_node_path.default.relative(import_node_path.default.dirname(workspace.root), entryPath);
|
|
const entry = bin.find((binEntry) => binEntry.path === relativePath);
|
|
if (entry?.name) {
|
|
return entry.name;
|
|
}
|
|
}
|
|
return import_node_path.default.basename(entryPath, ".rs").replace("[", "_").replace("]", "_");
|
|
}
|
|
|
|
// src/lib/utils.ts
|
|
var import_node_fs2 = __toESM(require("fs"));
|
|
var import_node_path2 = __toESM(require("path"));
|
|
var import_build_utils2 = require("@vercel/build-utils");
|
|
function getExecutableName(binName) {
|
|
return process.platform === "win32" ? `${binName}.exe` : binName;
|
|
}
|
|
function assertEnv(name) {
|
|
if (!process.env[name]) {
|
|
throw new Error(`Missing ENV variable process.env.${name}`);
|
|
}
|
|
return process.env[name];
|
|
}
|
|
async function runUserScripts(dir) {
|
|
const buildScriptPath = import_node_path2.default.join(dir, "build.sh");
|
|
const buildScriptExists = import_node_fs2.default.existsSync(buildScriptPath);
|
|
if (buildScriptExists) {
|
|
(0, import_build_utils2.debug)("Running `build.sh`");
|
|
await (0, import_build_utils2.runShellScript)(buildScriptPath);
|
|
}
|
|
}
|
|
async function gatherExtraFiles(globMatcher, workPath) {
|
|
if (!globMatcher)
|
|
return {};
|
|
(0, import_build_utils2.debug)(
|
|
`Gathering extra files for glob \`${JSON.stringify(
|
|
globMatcher
|
|
)}\` in ${workPath}`
|
|
);
|
|
if (Array.isArray(globMatcher)) {
|
|
const allMatches = await Promise.all(
|
|
globMatcher.map((pattern) => (0, import_build_utils2.glob)(pattern, workPath))
|
|
);
|
|
return allMatches.reduce((acc, matches) => ({ ...acc, ...matches }), {});
|
|
}
|
|
return (0, import_build_utils2.glob)(globMatcher, workPath);
|
|
}
|
|
|
|
// src/lib/start-dev-server.ts
|
|
var import_child_process = require("child_process");
|
|
var import_events = require("events");
|
|
var import_build_utils5 = require("@vercel/build-utils");
|
|
|
|
// src/lib/dev-build.ts
|
|
var import_node_path3 = __toESM(require("path"));
|
|
var import_execa3 = __toESM(require("execa"));
|
|
var import_build_utils3 = require("@vercel/build-utils");
|
|
async function buildExecutableForDev(workPath, entrypoint) {
|
|
(0, import_build_utils3.debug)(`Building executable for development: ${entrypoint}`);
|
|
const HOME = process.platform === "win32" ? assertEnv("USERPROFILE") : assertEnv("HOME");
|
|
const PATH = assertEnv("PATH");
|
|
const rustEnv = {
|
|
PATH: `${import_node_path3.default.join(HOME, ".cargo/bin")}${import_node_path3.default.delimiter}${PATH}`,
|
|
RUSTFLAGS: [process.env.RUSTFLAGS].filter(Boolean).join(" ")
|
|
};
|
|
const entryPath = import_node_path3.default.join(workPath, entrypoint);
|
|
const cargoWorkspace = await findCargoWorkspace({
|
|
env: rustEnv,
|
|
cwd: import_node_path3.default.dirname(entryPath)
|
|
});
|
|
const binaryName = findBinaryName(cargoWorkspace, entryPath);
|
|
(0, import_build_utils3.debug)(`Building binary "${binaryName}" in debug mode for dev server`);
|
|
try {
|
|
await (0, import_execa3.default)(
|
|
"cargo",
|
|
[
|
|
"build",
|
|
"--bin",
|
|
binaryName,
|
|
"--message-format=json-diagnostic-rendered-ansi"
|
|
],
|
|
{
|
|
cwd: workPath,
|
|
env: rustEnv,
|
|
stdio: "pipe"
|
|
}
|
|
);
|
|
} catch (err) {
|
|
(0, import_build_utils3.debug)(`Cargo build failed for ${binaryName}`);
|
|
throw new Error(`Failed to build Rust binary for development: ${err}`);
|
|
}
|
|
const { target_directory: targetDirectory } = await getCargoMetadata({
|
|
cwd: workPath,
|
|
env: rustEnv
|
|
});
|
|
const executablePath = import_node_path3.default.join(
|
|
targetDirectory,
|
|
"debug",
|
|
getExecutableName(binaryName)
|
|
);
|
|
(0, import_build_utils3.debug)(`Built executable at: ${executablePath}`);
|
|
return executablePath;
|
|
}
|
|
|
|
// src/lib/dev-server.ts
|
|
var import_build_utils4 = require("@vercel/build-utils");
|
|
function createDevServerEnv(baseEnv, meta = {}) {
|
|
const devEnv = {
|
|
// Base environment
|
|
...Object.fromEntries(
|
|
Object.entries(baseEnv).filter(([, value]) => value !== void 0)
|
|
),
|
|
// Development-specific variables
|
|
VERCEL_DEV: "1",
|
|
RUST_LOG: process.env.RUST_LOG || "info",
|
|
// Runtime environment from meta
|
|
...meta.env || {}
|
|
};
|
|
Object.keys(devEnv).forEach((key) => {
|
|
if (devEnv[key] === void 0) {
|
|
delete devEnv[key];
|
|
}
|
|
});
|
|
(0, import_build_utils4.debug)(`Dev server environment: ${Object.keys(devEnv).join(", ")}`);
|
|
return devEnv;
|
|
}
|
|
|
|
// src/lib/start-dev-server.ts
|
|
var startDevServer = async (opts) => {
|
|
const { entrypoint, workPath, meta = {} } = opts;
|
|
try {
|
|
await installRustToolchain();
|
|
const executablePath = await buildExecutableForDev(workPath, entrypoint);
|
|
(0, import_build_utils5.debug)(`Starting Rust dev server: ${executablePath}`);
|
|
const devEnv = createDevServerEnv(process.env, meta);
|
|
const child = (0, import_child_process.spawn)(executablePath, [], {
|
|
cwd: workPath,
|
|
env: devEnv,
|
|
stdio: ["pipe", "pipe", "pipe"]
|
|
});
|
|
if (!child.pid) {
|
|
throw new Error("Failed to start dev server process");
|
|
}
|
|
(0, import_build_utils5.debug)(`Dev server process started with PID: ${child.pid}`);
|
|
let buffer = "";
|
|
let portEmitted = false;
|
|
child.stdout?.on("data", (data) => {
|
|
const output = data.toString();
|
|
buffer += output;
|
|
if (!portEmitted && buffer.includes("Dev server listening:")) {
|
|
const portMatch = buffer.match(/Dev server listening: (\d+)/);
|
|
if (portMatch) {
|
|
const port = parseInt(portMatch[1], 10);
|
|
(0, import_build_utils5.debug)(
|
|
`Rust dev server detected port ${port}, emitting message event`
|
|
);
|
|
child.emit("message", { port }, null);
|
|
portEmitted = true;
|
|
}
|
|
buffer = "";
|
|
}
|
|
console.log(output);
|
|
});
|
|
child.stderr?.on("data", (data) => {
|
|
console.error(data.toString());
|
|
});
|
|
child.on("error", (err) => {
|
|
(0, import_build_utils5.debug)(`Dev server error: ${err}`);
|
|
});
|
|
child.on("exit", (code, signal) => {
|
|
(0, import_build_utils5.debug)(`Dev server exited with code ${code}, signal ${signal}`);
|
|
});
|
|
const onMessage = (0, import_events.once)(child, "message");
|
|
const onExit = (0, import_events.once)(child, "close");
|
|
const result = await Promise.race([
|
|
onMessage.then((args) => {
|
|
const [messageData] = args;
|
|
return { state: "message", value: messageData };
|
|
}),
|
|
onExit.then((args) => {
|
|
const [code, signal] = args;
|
|
return { state: "exit", value: [code, signal] };
|
|
})
|
|
]);
|
|
if (result.state === "message") {
|
|
const { port } = result.value;
|
|
(0, import_build_utils5.debug)(`Rust dev server ready on port ${port}`);
|
|
if (!child.pid) {
|
|
throw new Error("Child process has no PID");
|
|
}
|
|
const shutdown = async () => {
|
|
try {
|
|
process.kill(child.pid, "SIGTERM");
|
|
} catch (err) {
|
|
(0, import_build_utils5.debug)(`Error terminating Rust dev server: ${err}`);
|
|
}
|
|
};
|
|
return { port, pid: child.pid, shutdown };
|
|
} else {
|
|
const [exitCode, signal] = result.value;
|
|
const reason = signal ? `"${signal}" signal` : `exit code ${exitCode}`;
|
|
throw new Error(`Rust dev server failed with ${reason}`);
|
|
}
|
|
} catch (error) {
|
|
(0, import_build_utils5.debug)(`Failed to start dev server: ${error}`);
|
|
return null;
|
|
}
|
|
};
|
|
|
|
// src/index.ts
|
|
async function buildHandler(options) {
|
|
const BUILDER_DEBUG = Boolean(process.env.VERCEL_BUILDER_DEBUG ?? false);
|
|
const isVercelBuild = Boolean(process.env.VERCEL_BUILD_IMAGE ?? false);
|
|
const { files, entrypoint, workPath, config, meta } = options;
|
|
const crossCompilationEnabled = !isVercelBuild && !meta?.isDev;
|
|
if (crossCompilationEnabled && process.platform === "win32") {
|
|
throw new Error(
|
|
"Production prebuilt deployments for @vercel/rust are not yet supported on Windows. Please use a Linux or macOS environment, or deploy directly on Vercel."
|
|
);
|
|
}
|
|
await installRustToolchain();
|
|
(0, import_build_utils6.debug)("Creating file system");
|
|
const downloadedFiles = await (0, import_build_utils6.download)(files, workPath, meta);
|
|
const entryPath = downloadedFiles[entrypoint].fsPath;
|
|
const HOME = process.platform === "win32" ? assertEnv("USERPROFILE") : assertEnv("HOME");
|
|
const PATH = assertEnv("PATH");
|
|
const rustEnv = {
|
|
PATH: `${import_node_path4.default.join(HOME, ".cargo/bin")}${import_node_path4.default.delimiter}${PATH}`,
|
|
RUSTFLAGS: [process.env.RUSTFLAGS].filter(Boolean).join(" ")
|
|
};
|
|
const cargoWorkspace = await findCargoWorkspace({
|
|
env: rustEnv,
|
|
cwd: import_node_path4.default.dirname(entryPath)
|
|
});
|
|
const binaryName = findBinaryName(cargoWorkspace, entryPath);
|
|
const cargoBuildConfiguration = await findCargoBuildConfiguration(cargoWorkspace);
|
|
await runUserScripts(workPath);
|
|
const extraFiles = await gatherExtraFiles(config.includeFiles, workPath);
|
|
const lambdaOptions = await (0, import_build_utils6.getLambdaOptionsFromFunction)({
|
|
sourceFile: entrypoint,
|
|
config
|
|
});
|
|
const architecture = lambdaOptions?.architecture || "x86_64";
|
|
const buildVariant = meta?.isDev ? "debug" : "release";
|
|
const buildTarget = cargoBuildConfiguration?.build.target ?? "";
|
|
try {
|
|
const args = crossCompilationEnabled ? [
|
|
"zigbuild",
|
|
"--target",
|
|
architecture === "x86_64" ? "x86_64-unknown-linux-gnu" : "aarch64-unknown-linux-gnu",
|
|
"--bin",
|
|
binaryName
|
|
].concat(BUILDER_DEBUG ? ["--verbose"] : ["--quiet"], ["--release"]) : ["build", "--bin", binaryName].concat(
|
|
BUILDER_DEBUG ? ["--verbose"] : ["--quiet"],
|
|
meta?.isDev ? [] : ["--release"]
|
|
);
|
|
(0, import_build_utils6.debug)(
|
|
`Running \`cargo build\` for \`${binaryName}\` (\`${architecture}\`)`
|
|
);
|
|
await (0, import_execa4.default)("cargo", args, {
|
|
cwd: workPath,
|
|
env: rustEnv
|
|
});
|
|
} catch (err) {
|
|
(0, import_build_utils6.debug)(`Running \`cargo build\` for \`${binaryName}\` failed`);
|
|
throw err;
|
|
}
|
|
(0, import_build_utils6.debug)(
|
|
`Building \`${binaryName}\` for \`${process.platform}\` (\`${architecture}\`) completed`
|
|
);
|
|
let { target_directory: targetDirectory } = await getCargoMetadata({
|
|
cwd: workPath,
|
|
env: rustEnv
|
|
});
|
|
if (crossCompilationEnabled) {
|
|
targetDirectory = import_node_path4.default.join(
|
|
targetDirectory,
|
|
architecture === "x86_64" ? "x86_64-unknown-linux-gnu" : "aarch64-unknown-linux-gnu"
|
|
);
|
|
}
|
|
targetDirectory = import_node_path4.default.join(targetDirectory, buildTarget);
|
|
const bin = import_node_path4.default.join(
|
|
targetDirectory,
|
|
buildVariant,
|
|
getExecutableName(binaryName)
|
|
);
|
|
const handler = getExecutableName("executable");
|
|
const executableFile = new import_build_utils6.FileFsRef({ mode: 493, fsPath: bin });
|
|
const lambda = new import_build_utils6.Lambda({
|
|
...lambdaOptions,
|
|
files: {
|
|
...extraFiles,
|
|
[handler]: executableFile
|
|
},
|
|
handler,
|
|
supportsResponseStreaming: true,
|
|
architecture,
|
|
runtime: "executable",
|
|
runtimeLanguage: "rust"
|
|
});
|
|
lambda.zipBuffer = await lambda.createZip();
|
|
(0, import_build_utils6.debug)(`generating function for \`${entrypoint}\``);
|
|
return {
|
|
output: lambda
|
|
};
|
|
}
|
|
var runtime = {
|
|
version: 3,
|
|
build: buildHandler,
|
|
prepareCache: async ({ workPath }) => {
|
|
(0, import_build_utils6.debug)(`Caching \`${workPath}\``);
|
|
const cacheFiles = await (0, import_build_utils6.glob)("target/**", workPath);
|
|
for (const f of Object.keys(cacheFiles)) {
|
|
const accept = /(?:^|\/)target\/release\/\.fingerprint\//.test(f) || /(?:^|\/)target\/release\/build\//.test(f) || /(?:^|\/)target\/release\/deps\//.test(f) || /(?:^|\/)target\/debug\/\.fingerprint\//.test(f) || /(?:^|\/)target\/debug\/build\//.test(f) || /(?:^|\/)target\/debug\/deps\//.test(f);
|
|
if (!accept) {
|
|
delete cacheFiles[f];
|
|
}
|
|
}
|
|
return cacheFiles;
|
|
},
|
|
startDevServer,
|
|
shouldServe: async (options) => {
|
|
(0, import_build_utils6.debug)(`Requested ${options.requestPath} for ${options.entrypoint}`);
|
|
return Promise.resolve(options.requestPath === options.entrypoint);
|
|
}
|
|
};
|
|
var { version, build, prepareCache, startDevServer: startDevServer2, shouldServe } = runtime;
|
|
// Annotate the CommonJS export names for ESM import in node:
|
|
0 && (module.exports = {
|
|
build,
|
|
prepareCache,
|
|
shouldServe,
|
|
startDevServer,
|
|
version
|
|
});
|