mirror of
https://github.com/aphrody-labs/vu.git
synced 2026-10-14 04:09:18 +00:00
138 lines
4.0 KiB
TypeScript
138 lines
4.0 KiB
TypeScript
// SPDX-License-Identifier: Apache-2.0
|
|
//! Shared helpers of the vu build scripts (Bun only).
|
|
|
|
import { createHash } from "node:crypto";
|
|
import { createReadStream } from "node:fs";
|
|
import { homedir } from "node:os";
|
|
import { join, resolve } from "node:path";
|
|
|
|
export const ROOT = resolve(import.meta.dir, "..");
|
|
|
|
export interface Source {
|
|
name: string;
|
|
path: string;
|
|
url: string;
|
|
upstream: string;
|
|
upstreamTag: string;
|
|
ref: string;
|
|
forkBranch: string;
|
|
}
|
|
|
|
export interface Asset {
|
|
python: string;
|
|
role: string;
|
|
name: string;
|
|
sha256: string;
|
|
size: number;
|
|
}
|
|
|
|
export interface Release {
|
|
name: string;
|
|
tag: string;
|
|
commit: string;
|
|
python: string[];
|
|
defaultPython: string;
|
|
assets: Asset[];
|
|
}
|
|
|
|
export interface Vendor {
|
|
sources: Source[];
|
|
releases: Release[];
|
|
}
|
|
|
|
export interface Identity {
|
|
name: string;
|
|
derived: Record<string, string>;
|
|
}
|
|
|
|
export async function readVendor(root = ROOT): Promise<Vendor> {
|
|
return (await Bun.file(join(root, "vendor.json")).json()) as Vendor;
|
|
}
|
|
|
|
export async function readIdentity(root = ROOT): Promise<Identity> {
|
|
return (await Bun.file(join(root, "vu.json")).json()) as Identity;
|
|
}
|
|
|
|
/** The version of the vu workspace (`[workspace.package] version` of the root manifest). */
|
|
export async function workspaceVersion(root = ROOT): Promise<string> {
|
|
const manifest = await Bun.file(join(root, "Cargo.toml")).text();
|
|
const match = manifest.match(/^\[workspace\.package\][\s\S]*?^version\s*=\s*"([^"]+)"/m);
|
|
if (!match?.[1]) throw new Error("Cargo.toml has no [workspace.package] version");
|
|
return match[1];
|
|
}
|
|
|
|
/** Download cache of the pinned CPython archives (`VU_CACHE`, default `~/.cache/vu`). */
|
|
export function cacheDir(): string {
|
|
return process.env["VU_CACHE"] ?? join(homedir(), ".cache", "vu");
|
|
}
|
|
|
|
export function sha256Bytes(bytes: Uint8Array): string {
|
|
return createHash("sha256").update(bytes).digest("hex");
|
|
}
|
|
|
|
export function sha256File(path: string): Promise<string> {
|
|
return new Promise((resolvePromise, reject) => {
|
|
const hash = createHash("sha256");
|
|
createReadStream(path)
|
|
.on("data", (chunk) => hash.update(chunk))
|
|
.on("error", reject)
|
|
.on("end", () => resolvePromise(hash.digest("hex")));
|
|
});
|
|
}
|
|
|
|
export interface RunResult {
|
|
code: number;
|
|
stdout: string;
|
|
stderr: string;
|
|
ms: number;
|
|
}
|
|
|
|
/** Runs a command to completion and captures its output; never throws on a non-zero exit. */
|
|
export async function run(
|
|
command: string[],
|
|
options: { cwd?: string; env?: Record<string, string | undefined>; stdin?: string } = {},
|
|
): Promise<RunResult> {
|
|
const started = performance.now();
|
|
const child = Bun.spawn(command, {
|
|
cwd: options.cwd,
|
|
env: { ...process.env, ...options.env },
|
|
stdin: options.stdin === undefined ? "ignore" : new Blob([options.stdin]),
|
|
stdout: "pipe",
|
|
stderr: "pipe",
|
|
});
|
|
const [stdout, stderr, code] = await Promise.all([
|
|
new Response(child.stdout).text(),
|
|
new Response(child.stderr).text(),
|
|
child.exited,
|
|
]);
|
|
return { code, stdout, stderr, ms: Math.round(performance.now() - started) };
|
|
}
|
|
|
|
/** Maps with at most `limit` calls in flight (thousands of files must not open thousands of descriptors). */
|
|
export async function mapLimit<T, R>(
|
|
items: readonly T[],
|
|
limit: number,
|
|
fn: (item: T) => Promise<R>,
|
|
): Promise<R[]> {
|
|
const results: R[] = Array.from({ length: items.length });
|
|
let next = 0;
|
|
const worker = async (): Promise<void> => {
|
|
for (let index = next++; index < items.length; index = next++) {
|
|
// The workers are the concurrency: each one runs its items in sequence.
|
|
// eslint-disable-next-line no-await-in-loop
|
|
results[index] = await fn(items[index] as T);
|
|
}
|
|
};
|
|
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
|
return results;
|
|
}
|
|
|
|
/** `3.12.15` to `3.12`. */
|
|
export function minorOf(version: string): string {
|
|
const parts = version.split(".");
|
|
if (parts.length < 3 || parts.some((part) => !/^\d+/.test(part))) {
|
|
throw new Error(`not a CPython version: ${version}`);
|
|
}
|
|
return `${parts[0]}.${parts[1]}`;
|
|
}
|