@bytecodealliance/jco
Version:
JavaScript tooling for working with WebAssembly Components
160 lines (142 loc) • 4.49 kB
JavaScript
import { normalize, resolve, sep, dirname } from 'node:path';
import { tmpdir } from 'node:os';
import { readFile, writeFile, rm, mkdtemp, mkdir } from 'node:fs/promises';
import { spawn } from 'node:child_process';
import { argv0 } from 'node:process';
import { platform } from 'node:process';
import c from 'chalk-template';
export const isWindows = platform === 'win32';
export const ASYNC_WASI_IMPORTS = [
'wasi:io/poll#poll',
'wasi:io/poll#[method]pollable.block',
'wasi:io/streams#[method]input-stream.blocking-read',
'wasi:io/streams#[method]input-stream.blocking-skip',
'wasi:io/streams#[method]output-stream.blocking-flush',
'wasi:io/streams#[method]output-stream.blocking-write-and-flush',
'wasi:io/streams#[method]output-stream.blocking-write-zeroes-and-flush',
'wasi:io/streams#[method]output-stream.blocking-splice',
];
export const ASYNC_WASI_EXPORTS = [
'wasi:cli/run#run',
'wasi:http/incoming-handler#handle',
];
let _showSpinner = false;
export function setShowSpinner(val) {
_showSpinner = val;
}
export function getShowSpinner() {
const showSpinner = _showSpinner;
_showSpinner = false;
return showSpinner;
}
export function sizeStr(num) {
num /= 1024;
if (num < 1000) {
return `${fixedDigitDisplay(num, 4)} KiB`;
}
num /= 1024;
if (num < 1000) {
return `${fixedDigitDisplay(num, 4)} MiB`;
}
}
export function fixedDigitDisplay(num, maxChars) {
const significantDigits = String(num).split('.')[0].length;
let str;
if (significantDigits >= maxChars - 1) {
str = String(Math.round(num));
} else {
const decimalPlaces = maxChars - significantDigits - 1;
const rounding = 10 ** decimalPlaces;
str = String(Math.round(num * rounding) / rounding);
}
if (maxChars - str.length < 0) {
return str;
}
return ' '.repeat(maxChars - str.length) + str;
}
export function table(data, align = []) {
if (data.length === 0) {
return '';
}
const colLens = data.reduce(
(maxLens, cur) => maxLens.map((len, i) => Math.max(len, cur[i].length)),
data[0].map((cell) => cell.length)
);
let outTable = '';
for (const row of data) {
for (const [i, cell] of row.entries()) {
if (align[i] === 'right') {
outTable += ' '.repeat(colLens[i] - cell.length) + cell;
} else {
outTable += cell + ' '.repeat(colLens[i] - cell.length);
}
}
outTable += '\n';
}
return outTable;
}
/**
* Securely creates a temporary directory and returns its path.
*
* The new directory is created using `fsPromises.mkdtemp()`.
*/
export async function getTmpDir() {
return await mkdtemp(normalize(tmpdir() + sep));
}
async function readFileCli(file, encoding) {
try {
return await readFile(file, encoding);
} catch {
throw c`Unable to read file {bold ${file}}`;
}
}
export { readFileCli as readFile };
export async function spawnIOTmp(cmd, input, args) {
const tmpDir = await getTmpDir();
try {
const inFile = resolve(tmpDir, 'in.wasm');
let outFile = resolve(tmpDir, 'out.wasm');
await writeFile(inFile, input);
const cp = spawn(argv0, [cmd, inFile, ...args, outFile], {
stdio: 'pipe',
});
let stderr = '';
const p = new Promise((resolve, reject) => {
cp.stderr.on('data', (data) => (stderr += data.toString()));
cp.on('error', (e) => {
reject(e);
});
cp.on('exit', (code) => {
if (code === 0) {
resolve();
} else {
reject(stderr);
}
});
});
await p;
var output = await readFile(outFile);
return output;
} finally {
await rm(tmpDir, { recursive: true });
}
}
export async function writeFiles(files, summaryTitle) {
await Promise.all(
Object.entries(files).map(async ([name, file]) => {
await mkdir(dirname(name), { recursive: true });
await writeFile(name, file);
})
);
if (!summaryTitle) {
return;
}
console.log(c`
{bold ${summaryTitle}:}
${table(
Object.entries(files).map(([name, source]) => [
c` - {italic ${name}} `,
c`{black.italic ${sizeStr(source.length)}}`,
])
)}`);
}