feat: add multi-engine regex flavour support

This commit is contained in:
2026-07-27 11:43:51 +02:00
parent 7079cde15f
commit 7f3a91ad37
340 changed files with 643286 additions and 483 deletions

View File

@@ -0,0 +1,806 @@
import { spawn } from "node:child_process";
import { createHash } from "node:crypto";
import {
access,
constants as fsConstants,
copyFile,
lstat,
mkdir,
mkdtemp,
readFile,
readdir,
realpath,
rename,
rm,
stat,
writeFile,
} from "node:fs/promises";
import path from "node:path";
export const RUST_LOCK = Object.freeze({
bridgeAbi: 1,
engineVersion: "1.13.1",
rustcVersion: "1.97.1",
rustcIdentity: "rustc 1.97.1 (8bab26f4f 2026-07-14)",
rustcCommit: "8bab26f4f68e0e26f0bb7960be334d5b520ea452",
cargoIdentity: "cargo 1.97.1 (c980f4866 2026-06-30)",
llvmVersion: "22.1.6",
wasmBindgenVersion: "0.2.126",
wasmBindgenCrateChecksum:
"4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4",
wasmBindgenCliChecksum:
"8e2ebf6eef87c34e662347f1dddfbea541304bb7294711ed2c2acd8746cc5b50",
sourceDateEpoch: 1_784_146_753,
source: Object.freeze({
repository: "https://github.com/rust-lang/regex",
tag: "1.13.1",
tagObject: "7c28ee4dc4aef53571c96757f4825c51ac0c3d44",
commit: "2b527599eb9eea0dcc288c704584f242f26a5c61",
tree: "25bea9f6f55a4cf528afefaa00f5335c595c182c",
crateChecksum:
"f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d",
license: "MIT OR Apache-2.0",
}),
distribution: Object.freeze({
manifest: "https://static.rust-lang.org/dist/channel-rust-1.97.1.toml",
manifestSha256:
"03569b1886ceb5c05276b50c8431ab111de944cd6140fe1fa7d821dd8e0f29cf",
rustcArchiveSha256:
"1c441e430c1cca49dff54a8d59c41038bf6f79f7b8756596cb2f36511a015eba",
wasmStdArchiveSha256:
"13902d5573eeea50701d75acc774b6df2dfb4942ec88cdbe40bb07e448c307ea",
licenseHashes: Object.freeze({
apache:
"074e6e32c86a4c0ef8b3ed25b721ca23aca83df277cd88106ef7177c354615ff",
mit: "b85dcd3e453d05982552c52b5fc9e0bdd6d23c6f8e844b984a88af32570b0cc0",
unicode:
"f5062c9a188d81dfe66b56db4182dcf9e4b17c0d9b0d311a8e20b3a1b075c443",
stdlibCopyright:
"0a65bb747c49c7bb816cbc7188319bd6e4e8d08091c1190b8a3c0971c47968ed",
}),
}),
limits: Object.freeze({
maximumPatternBytes: 262_144,
maximumSubjectBytes: 16_777_216,
maximumReplacementBytes: 262_144,
maximumMatches: 10_000,
maximumCaptureRows: 100_000,
maximumCaptureGroups: 1_000,
maximumOutputBytes: 67_108_864,
maximumRequestJSONBytes: 103_874_560,
compiledSizeLimitBytes: 10_485_760,
dfaSizeLimitBytes: 2_097_152,
}),
});
const PACK_FILES = Object.freeze([
"LICENSE-Apache-2.0.txt",
"LICENSE-MIT.txt",
"LICENSE-Unicode-3.0.txt",
"RUST-STDLIB-COPYRIGHT.html",
"SHA256SUMS",
"THIRD_PARTY_LICENSES.txt",
"engine-metadata.json",
"rust-regex.mjs",
"rust-regex_bg.wasm",
]);
const CHECKSUM_FILES = Object.freeze(
PACK_FILES.filter((name) => name !== "SHA256SUMS"),
);
const SOURCE_FILES = Object.freeze([
"engines/rust/Cargo.lock",
"engines/rust/Cargo.toml",
"engines/rust/README.md",
"engines/rust/rust-toolchain.toml",
"engines/rust/src/lib.rs",
]);
function sha256(value) {
return createHash("sha256").update(value).digest("hex");
}
async function sha256File(file) {
return sha256(await readFile(file));
}
async function assertRealDirectory(candidate, label) {
const details = await lstat(candidate).catch(() => null);
if (!details?.isDirectory() || details.isSymbolicLink()) {
throw new Error(`${label} must be a real directory: ${candidate}`);
}
return realpath(candidate);
}
async function assertRegularFile(candidate, label) {
const details = await lstat(candidate).catch(() => null);
if (!details?.isFile() || details.isSymbolicLink()) {
throw new Error(`${label} must be a regular file: ${candidate}`);
}
return realpath(candidate);
}
function commandLabel(command, arguments_) {
return [command, ...arguments_]
.map((part) =>
/^[A-Za-z0-9_./:=,+-]+$/u.test(part) ? part : JSON.stringify(part),
)
.join(" ");
}
async function run(command, arguments_, options = {}) {
const child = spawn(command, arguments_, {
cwd: options.cwd,
env: options.env,
stdio: ["ignore", "pipe", "pipe"],
});
const stdout = [];
const stderr = [];
child.stdout.on("data", (chunk) => stdout.push(chunk));
child.stderr.on("data", (chunk) => stderr.push(chunk));
const result = await new Promise((resolve, reject) => {
child.once("error", reject);
child.once("close", (code, signal) => resolve({ code, signal }));
});
const output = Buffer.concat(stdout).toString("utf8");
const errorOutput = Buffer.concat(stderr).toString("utf8");
if (result.code !== 0) {
const detail = [output, errorOutput].filter(Boolean).join("\n").trim();
throw new Error(
`${commandLabel(command, arguments_)} failed with ${
result.signal ? `signal ${result.signal}` : `exit code ${result.code}`
}${detail ? `:\n${detail}` : "."}`,
);
}
return { stdout: output, stderr: errorOutput };
}
function parseCargoLock(contents) {
const packages = [];
for (const block of contents.split("[[package]]").slice(1)) {
const field = (name) => {
const match = block.match(new RegExp(`^${name} = "([^"]+)"`, "mu"));
return match?.[1];
};
const name = field("name");
const version = field("version");
if (!name || !version) {
throw new Error("Cargo.lock contains a malformed package block.");
}
const source = field("source");
const checksum = field("checksum");
packages.push({
name,
version,
...(source ? { source } : {}),
...(checksum ? { checksum } : {}),
});
}
return packages;
}
async function findRegistryCrate(cargoHome, package_) {
const registrySource = path.join(cargoHome, "registry", "src");
const indexes = await readdir(registrySource, { withFileTypes: true });
const matches = [];
for (const index of indexes) {
if (!index.isDirectory() || index.isSymbolicLink()) continue;
const candidate = path.join(
registrySource,
index.name,
`${package_.name}-${package_.version}`,
);
const details = await lstat(candidate).catch(() => null);
if (details?.isDirectory() && !details.isSymbolicLink()) {
matches.push(await realpath(candidate));
}
}
if (matches.length !== 1) {
throw new Error(
`Expected one cached source directory for ${package_.name} ${package_.version}.`,
);
}
return matches[0];
}
async function thirdPartyLicenses(cargoHome, packages) {
const sections = [
"Regex Tools Rust engine third-party source notices",
"Generated from the checked-in Cargo.lock; package order is deterministic.",
"",
];
for (const package_ of packages.filter(({ source }) => source)) {
const crate = await findRegistryCrate(cargoHome, package_);
const entries = (await readdir(crate, { withFileTypes: true }))
.filter(
(entry) =>
entry.isFile() &&
!entry.isSymbolicLink() &&
/^(?:COPYING|COPYRIGHT|LICENSE|UNLICENSE)/iu.test(entry.name),
)
.sort((left, right) =>
left.name < right.name ? -1 : left.name > right.name ? 1 : 0,
);
sections.push(
`===============================================================================`,
`${package_.name} ${package_.version}`,
`Cargo source: ${package_.source}`,
`Cargo checksum: ${package_.checksum ?? "not recorded"}`,
);
if (entries.length === 0) {
sections.push(
"No root licence file was present in the packaged crate source.",
"",
);
continue;
}
for (const entry of entries) {
sections.push(
`-------------------------------------------------------------------------------`,
entry.name,
"",
(await readFile(path.join(crate, entry.name), "utf8")).trimEnd(),
"",
);
}
}
return `${sections.join("\n")}\n`;
}
async function verifyToolchain(toolchainRootDirectory) {
const root = await assertRealDirectory(
toolchainRootDirectory,
"Rust 1.97.1 toolchain",
);
const cargoHome = await assertRealDirectory(
path.join(root, "cargo"),
"Rust Cargo home",
);
const rustupHome = await assertRealDirectory(
path.join(root, "rustup"),
"Rustup home",
);
const toolchain = await assertRealDirectory(
path.join(rustupHome, "toolchains", "1.97.1-x86_64-unknown-linux-gnu"),
"Rust distribution toolchain",
);
const cargo = await assertRegularFile(
path.join(toolchain, "bin", "cargo"),
"cargo",
);
const rustc = await assertRegularFile(
path.join(toolchain, "bin", "rustc"),
"rustc",
);
const wasmBindgen = await assertRegularFile(
path.join(cargoHome, "bin", "wasm-bindgen"),
"wasm-bindgen",
);
for (const executable of [cargo, rustc, wasmBindgen]) {
await access(executable, fsConstants.X_OK);
}
const baseEnvironment = {
...process.env,
CARGO_HOME: cargoHome,
RUSTUP_HOME: rustupHome,
PATH: [
path.join(toolchain, "bin"),
path.join(cargoHome, "bin"),
process.env.PATH ?? "",
].join(path.delimiter),
};
const rustcVersion = await run(rustc, ["--version", "--verbose"], {
env: baseEnvironment,
});
if (
!rustcVersion.stdout.includes(`release: ${RUST_LOCK.rustcVersion}`) ||
!rustcVersion.stdout.includes(`commit-hash: ${RUST_LOCK.rustcCommit}`) ||
!rustcVersion.stdout.includes(`LLVM version: ${RUST_LOCK.llvmVersion}`)
) {
throw new Error("The Rust engine requires pinned rustc 1.97.1.");
}
const cargoVersion = await run(cargo, ["--version"], {
env: baseEnvironment,
});
if (cargoVersion.stdout.trim() !== RUST_LOCK.cargoIdentity) {
throw new Error("The Rust engine requires pinned Cargo 1.97.1.");
}
const bindgenVersion = await run(wasmBindgen, ["--version"], {
env: baseEnvironment,
});
if (
bindgenVersion.stdout.trim() !==
`wasm-bindgen ${RUST_LOCK.wasmBindgenVersion}`
) {
throw new Error("The Rust engine requires pinned wasm-bindgen 0.2.126.");
}
const docs = path.join(toolchain, "share", "doc", "rust");
const files = {
apache: await assertRegularFile(
path.join(docs, "licenses", "Apache-2.0.txt"),
"Rust Apache licence",
),
mit: await assertRegularFile(
path.join(docs, "licenses", "MIT.txt"),
"Rust MIT licence",
),
unicode: await assertRegularFile(
path.join(docs, "licenses", "Unicode-3.0.txt"),
"Rust Unicode licence",
),
stdlibCopyright: await assertRegularFile(
path.join(docs, "COPYRIGHT-library.html"),
"Rust standard-library copyright notice",
),
};
for (const [name, file] of Object.entries(files)) {
if (
(await sha256File(file)) !== RUST_LOCK.distribution.licenseHashes[name]
) {
throw new Error(`The pinned Rust ${name} notice does not match 1.97.1.`);
}
}
return {
root,
cargoHome,
rustupHome,
cargo,
rustc,
wasmBindgen,
baseEnvironment,
files,
};
}
function buildEnvironment(toolchain, repositoryRoot, targetDirectory) {
const environment = { ...toolchain.baseEnvironment };
for (const variable of [
"CARGO_BUILD_RUSTC",
"CARGO_ENCODED_RUSTFLAGS",
"CARGO_PROFILE_RELEASE_DEBUG",
"RUSTC",
"RUSTDOC",
"RUSTDOCFLAGS",
"RUSTFLAGS",
]) {
delete environment[variable];
}
environment.CARGO_INCREMENTAL = "0";
environment.CARGO_NET_OFFLINE = "true";
environment.CARGO_TARGET_DIR = targetDirectory;
environment.LANG = "C";
environment.LC_ALL = "C";
environment.REGEX_TOOLS_RUSTC_VERSION = RUST_LOCK.rustcIdentity;
environment.RUST_BACKTRACE = "0";
environment.RUSTUP_TOOLCHAIN = "1.97.1-x86_64-unknown-linux-gnu";
environment.RUSTFLAGS = [
`--remap-path-prefix=${repositoryRoot}=.`,
`--remap-path-prefix=${toolchain.cargoHome}=./cargo-home`,
].join(" ");
environment.SOURCE_DATE_EPOCH = String(RUST_LOCK.sourceDateEpoch);
environment.TZ = "UTC";
return environment;
}
async function sourceMetadata(root) {
return Promise.all(
SOURCE_FILES.map(async (relativePath) => {
const contents = await readFile(path.join(root, relativePath));
return {
path: relativePath,
sha256: sha256(contents),
bytes: contents.byteLength,
};
}),
);
}
async function fileMetadata(pack, name) {
const contents = await readFile(path.join(pack, name));
return { path: name, sha256: sha256(contents), bytes: contents.byteLength };
}
async function lockedPackages(root) {
const contents = await readFile(
path.join(root, "engines", "rust", "Cargo.lock"),
"utf8",
);
return parseCargoLock(contents)
.filter(({ source }) => source)
.map(({ name, version, checksum }) => ({ name, version, checksum }));
}
async function expectedMetadata(root, pack) {
return {
schemaVersion: 1,
engine: "rust",
engineName: "Rust regex crate",
engineVersion: RUST_LOCK.engineVersion,
semanticIdentity:
"Rust regex crate 1.13.1 compiled to WebAssembly with rustc 1.97.1",
status: "production-native-runtime",
bridge: {
abiVersion: RUST_LOCK.bridgeAbi,
offsetUnit: "utf8-byte",
requestEncoding:
"Exact UTF-8-bounded JSON values with worst-case escape allowance; fixed exports; base64 UTF-8 replacement output; no source evaluation",
supportedApplicationFlags: ["g", "i", "m", "s", "U", "u", "x", "R"],
unicode:
"Enabled by default, matching the Rust regex crate; u explicitly affirms that default.",
replacement:
"Bounded streaming expansion with regex::Captures::expand grammar, parity-checked against the native API.",
limits: { ...RUST_LOCK.limits },
sourceFiles: await sourceMetadata(root),
},
source: {
repository: RUST_LOCK.source.repository,
tag: RUST_LOCK.source.tag,
tagObjectSha1: RUST_LOCK.source.tagObject,
commitSha1: RUST_LOCK.source.commit,
treeSha1: RUST_LOCK.source.tree,
crateChecksumSha256: RUST_LOCK.source.crateChecksum,
license: RUST_LOCK.source.license,
},
toolchain: {
rustcVersion: RUST_LOCK.rustcVersion,
rustcIdentity: RUST_LOCK.rustcIdentity,
rustcCommitSha1: RUST_LOCK.rustcCommit,
llvmVersion: RUST_LOCK.llvmVersion,
cargoIdentity: RUST_LOCK.cargoIdentity,
target: "wasm32-unknown-unknown",
wasmBindgenVersion: RUST_LOCK.wasmBindgenVersion,
wasmBindgenCrateChecksumSha256: RUST_LOCK.wasmBindgenCrateChecksum,
wasmBindgenCliChecksumSha256: RUST_LOCK.wasmBindgenCliChecksum,
distributionManifest: RUST_LOCK.distribution.manifest,
distributionManifestSha256: RUST_LOCK.distribution.manifestSha256,
rustcArchiveSha256: RUST_LOCK.distribution.rustcArchiveSha256,
wasmStdArchiveSha256: RUST_LOCK.distribution.wasmStdArchiveSha256,
cargoLockPackages: await lockedPackages(root),
},
sourceDateEpoch: RUST_LOCK.sourceDateEpoch,
files: await Promise.all(
CHECKSUM_FILES.filter((name) => name !== "engine-metadata.json").map(
(name) => fileMetadata(pack, name),
),
),
};
}
async function checksumText(pack) {
const lines = await Promise.all(
CHECKSUM_FILES.map(
async (name) => `${await sha256File(path.join(pack, name))} ${name}`,
),
);
return `${lines.join("\n")}\n`;
}
function equalJson(left, right) {
return JSON.stringify(left) === JSON.stringify(right);
}
async function smokePack(pack) {
const moduleUrl = new URL(`file://${path.join(pack, "rust-regex.mjs")}`).href;
const wasmPath = path.join(pack, "rust-regex_bg.wasm");
const smoke = `
import { readFile } from "node:fs/promises";
const engine = await import(process.argv[1]);
engine.initSync({
module: await WebAssembly.compile(await readFile(process.argv[2])),
});
const identity = JSON.parse(engine.identity());
if (
identity.abi !== 1 ||
identity.ok !== true ||
identity.identity.engineVersion !== "1.13.1" ||
identity.identity.rustcVersion !==
"rustc 1.97.1 (8bab26f4f 2026-07-14)" ||
identity.identity.selfTest !== true
) throw new Error("Rust identity self-test failed.");
const result = JSON.parse(engine.run(JSON.stringify({
abi: 1,
operation: "replace",
pattern: "(?P<word>😀+)",
flags: "g",
options: {},
subject: "x😀😀",
scanAll: false,
maximumMatches: 10,
maximumCaptureRows: 10,
replacement: "\${word}-$$",
maximumOutputBytes: 100,
})));
if (
result.ok !== true ||
result.matches[0].span[0] !== 1 ||
result.matches[0].span[1] !== 9 ||
Buffer.from(result.outputBase64, "base64").toString("utf8") !== "x😀😀-$"
) throw new Error("Rust execution self-test failed.");
`;
await run(process.execPath, [
"--input-type=module",
"--eval",
smoke,
moduleUrl,
wasmPath,
]);
}
export async function verifyRustPack(packDirectory, root) {
const repositoryRoot = await assertRealDirectory(
root,
"Regex Tools repository",
);
const pack = await assertRealDirectory(packDirectory, "Rust engine pack");
const entries = (await readdir(pack, { withFileTypes: true })).sort(
(left, right) =>
left.name < right.name ? -1 : left.name > right.name ? 1 : 0,
);
if (
entries.length !== PACK_FILES.length ||
entries.some(
(entry, index) =>
entry.name !== PACK_FILES[index] ||
!entry.isFile() ||
entry.isSymbolicLink(),
)
) {
throw new Error(
`The Rust pack must contain only: ${PACK_FILES.join(", ")}.`,
);
}
const licenseFiles = {
"LICENSE-Apache-2.0.txt": RUST_LOCK.distribution.licenseHashes.apache,
"LICENSE-MIT.txt": RUST_LOCK.distribution.licenseHashes.mit,
"LICENSE-Unicode-3.0.txt": RUST_LOCK.distribution.licenseHashes.unicode,
"RUST-STDLIB-COPYRIGHT.html":
RUST_LOCK.distribution.licenseHashes.stdlibCopyright,
};
for (const [name, digest] of Object.entries(licenseFiles)) {
if ((await sha256File(path.join(pack, name))) !== digest) {
throw new Error(`${name} does not match the Rust 1.97.1 distribution.`);
}
}
const wasm = await readFile(path.join(pack, "rust-regex_bg.wasm"));
if (
wasm.byteLength < 100_000 ||
wasm.subarray(0, 4).toString("hex") !== "0061736d"
) {
throw new Error("The staged Rust WebAssembly module is invalid.");
}
const moduleText = await readFile(path.join(pack, "rust-regex.mjs"), "utf8");
if (
moduleText.includes("sourceMappingURL") ||
moduleText.includes("/mnt/") ||
moduleText.includes("/home/") ||
!moduleText.includes("export function identity") ||
!moduleText.includes("export function run") ||
!moduleText.includes("export { initSync")
) {
throw new Error(
"rust-regex.mjs leaks a build path, has a source map, or lacks bridge exports.",
);
}
const packages = await lockedPackages(repositoryRoot);
const notices = await readFile(
path.join(pack, "THIRD_PARTY_LICENSES.txt"),
"utf8",
);
for (const package_ of packages) {
if (
!notices.includes(`${package_.name} ${package_.version}\n`) ||
!notices.includes(`Cargo checksum: ${package_.checksum}`)
) {
throw new Error(
`Third-party notices omit ${package_.name} ${package_.version}.`,
);
}
}
let metadata;
try {
metadata = JSON.parse(
await readFile(path.join(pack, "engine-metadata.json"), "utf8"),
);
} catch (error) {
throw new Error("Rust engine-metadata.json is invalid JSON.", {
cause: error,
});
}
if (!equalJson(metadata, await expectedMetadata(repositoryRoot, pack))) {
throw new Error(
"The Rust metadata does not match the pinned build contract.",
);
}
if (
(await readFile(path.join(pack, "SHA256SUMS"), "utf8")) !==
(await checksumText(pack))
) {
throw new Error("The Rust SHA256SUMS file is incorrect.");
}
await smokePack(pack);
return metadata;
}
async function replaceDirectory(stage, target, label) {
const details = await lstat(target).catch(() => null);
if (details && (!details.isDirectory() || details.isSymbolicLink())) {
throw new Error(`${label} must be a real directory.`);
}
if (!details) {
await rename(stage, target);
return;
}
const backup = `${target}.replaced-${process.pid}`;
await rename(target, backup);
try {
await rename(stage, target);
} catch (error) {
await rename(backup, target);
throw error;
}
await rm(backup, { recursive: true });
}
async function bindgen(toolchain, rawWasm, output) {
await mkdir(output, { recursive: true });
await run(
toolchain.wasmBindgen,
[
"--target",
"web",
"--no-typescript",
"--out-name",
"rust-regex",
"--out-dir",
output,
rawWasm,
],
{ env: toolchain.baseEnvironment },
);
await rename(
path.join(output, "rust-regex.js"),
path.join(output, "rust-regex.mjs"),
);
}
export async function buildRustPack(
root,
toolchainRootDirectory,
outputDirectory,
) {
const repositoryRoot = await assertRealDirectory(
root,
"Regex Tools repository",
);
const engine = await assertRealDirectory(
path.join(repositoryRoot, "engines", "rust"),
"Rust engine source",
);
const toolchain = await verifyToolchain(toolchainRootDirectory);
const lockPackages = parseCargoLock(
await readFile(path.join(engine, "Cargo.lock"), "utf8"),
);
const regexPackage = lockPackages.find(
({ name, version }) => name === "regex" && version === "1.13.1",
);
const bindgenPackage = lockPackages.find(
({ name, version }) =>
name === "wasm-bindgen" && version === RUST_LOCK.wasmBindgenVersion,
);
if (
regexPackage?.checksum !== RUST_LOCK.source.crateChecksum ||
bindgenPackage?.checksum !== RUST_LOCK.wasmBindgenCrateChecksum
) {
throw new Error("Cargo.lock does not contain the pinned engine crates.");
}
const buildParent = path.join(repositoryRoot, ".engine-build");
await mkdir(buildParent, { recursive: true });
const temporary = await mkdtemp(path.join(buildParent, "rust-build-"));
try {
const generated = [];
for (const label of ["first", "second"]) {
const target = path.join(temporary, `${label}-target`);
const environment = buildEnvironment(toolchain, repositoryRoot, target);
await run(
toolchain.cargo,
[
"build",
"--locked",
"--offline",
"--release",
"--target",
"wasm32-unknown-unknown",
],
{ cwd: engine, env: environment },
);
const rawWasm = await assertRegularFile(
path.join(
target,
"wasm32-unknown-unknown",
"release",
"regex_tools_rust_engine.wasm",
),
"compiled Rust WebAssembly",
);
const output = path.join(temporary, `${label}-bindgen`);
await bindgen(toolchain, rawWasm, output);
generated.push(output);
}
for (const name of ["rust-regex.mjs", "rust-regex_bg.wasm"]) {
if (
(await sha256File(path.join(generated[0], name))) !==
(await sha256File(path.join(generated[1], name)))
) {
throw new Error(
`Two pinned Rust builds produced different ${name} hashes.`,
);
}
}
const target = path.resolve(
repositoryRoot,
outputDirectory ?? path.join(".engine-build", "rust"),
);
const stage = await mkdtemp(path.join(buildParent, "rust-pack-"));
await copyFile(
toolchain.files.apache,
path.join(stage, "LICENSE-Apache-2.0.txt"),
);
await copyFile(toolchain.files.mit, path.join(stage, "LICENSE-MIT.txt"));
await copyFile(
toolchain.files.unicode,
path.join(stage, "LICENSE-Unicode-3.0.txt"),
);
await copyFile(
toolchain.files.stdlibCopyright,
path.join(stage, "RUST-STDLIB-COPYRIGHT.html"),
);
await copyFile(
path.join(generated[0], "rust-regex.mjs"),
path.join(stage, "rust-regex.mjs"),
);
await copyFile(
path.join(generated[0], "rust-regex_bg.wasm"),
path.join(stage, "rust-regex_bg.wasm"),
);
await writeFile(
path.join(stage, "THIRD_PARTY_LICENSES.txt"),
await thirdPartyLicenses(toolchain.cargoHome, lockPackages),
"utf8",
);
const metadata = await expectedMetadata(repositoryRoot, stage);
await writeFile(
path.join(stage, "engine-metadata.json"),
`${JSON.stringify(metadata, null, 2)}\n`,
"utf8",
);
await writeFile(path.join(stage, "SHA256SUMS"), await checksumText(stage));
await verifyRustPack(stage, repositoryRoot);
await mkdir(path.dirname(target), { recursive: true });
await replaceDirectory(stage, target, "Rust staged pack");
return { metadata, output: target };
} finally {
await rm(temporary, { recursive: true });
}
}
export async function installRustPack(sourceDirectory, root) {
const repositoryRoot = await assertRealDirectory(
root,
"Regex Tools repository",
);
const source = await assertRealDirectory(sourceDirectory, "Rust staged pack");
const metadata = await verifyRustPack(source, repositoryRoot);
const engineParent = path.join(repositoryRoot, "public", "engines");
await mkdir(engineParent, { recursive: true });
const stage = await mkdtemp(path.join(engineParent, "rust-install-"));
for (const name of PACK_FILES) {
await copyFile(path.join(source, name), path.join(stage, name));
}
await verifyRustPack(stage, repositoryRoot);
const output = path.join(engineParent, "rust");
await replaceDirectory(stage, output, "Installed Rust engine pack");
return { metadata, output };
}