@needle-tools/engine
Version:
Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.
124 lines (105 loc) • 5.31 kB
JavaScript
import { collectBuildDirectoryStats, createBuildInfoFile } from '../common/buildinfo.js';
import { formatBytes } from '../common/files.js';
import { closeLogStreams } from '../common/logger.js';
import { waitForBuildPipelineToFinish } from './build-pipeline.js';
import { getOutputDirectory } from './config.js';
import { needleBlue, needleDim, needleLog, needleSupportsColor } from './logging.js';
let level = 0;
/** Create a buildinfo file in the build directory.
* @param {"build" | "serve"} command
* @param {import('../types/needleConfig').needleMeta | null | undefined} config
* @param {import('../types').userSettings} userSettings
* @returns {import('vite').Plugin | null}
*/
export function needleBuildInfo(command, config, userSettings) {
if (userSettings?.noBuildInfo) return null;
// let buildDirectory = null;
// /** @type {import('../common/buildinfo').BuildInfo | null} */
// let beforeStats = null;
return {
name: 'needle:buildinfo',
apply: "build",
enforce: "post",
buildStart: () => {
level++;
},
buildEnd(error) {
if (--level > 0) {
return; // nested build (e.g. worker entry) — skip
}
// waitForBuildPipelineToFinish().then(res => {
// beforeStats = collectBuildDirectoryStats(buildDirectory);
// })
},
closeBundle: () => {
if (--level > 0) {
return; // nested build (e.g. worker entry) — skip
}
const buildDirectory = getOutputDirectory();
const beforeStats = collectBuildDirectoryStats(buildDirectory);
const handleClose = async () => {
const task = waitForBuildPipelineToFinish();
if (task instanceof Promise) {
if (process.env.DEBUG) needleLog("needle-buildinfo", "Waiting for build pipeline to finish");
await task.catch(() => { });
}
// wait for gzip
await delay(500);
const closedHandles = closeDanglingNetworkHandles();
const result = createBuildInfoFile(buildDirectory, { log: false });
if (!result?.ok) {
needleLog("needle-buildinfo", result?.error || "Failed to create build info file", "warn", { dimBody: false });
return;
}
const supportsColor = needleSupportsColor();
const key = (text) => supportsColor ? needleBlue(text) : text;
const sizeDelta = (result.totalSizeBytes || 0) - (beforeStats.totalSizeBytes || 0);
const fileDelta = (result.fileCount || 0) - (beforeStats.fileCount || 0);
const sizePct = beforeStats.totalSizeBytes > 0 ? Math.round(sizeDelta / beforeStats.totalSizeBytes * 100) : 0;
const strTotalSize = beforeStats.totalSizeBytes !== result.totalSizeBytes ? `${formatBytes(beforeStats.totalSizeBytes)} → ${formatBytes(result.totalSizeBytes)} (${sizePct >= 0 ? "+" : ""}${sizePct}%)` : formatBytes(result.totalSizeBytes);
const strFileCount = beforeStats.fileCount !== result.fileCount ? `${beforeStats.fileCount} → ${result.fileCount} (${fileDelta >= 0 ? "+" : ""}${fileDelta})` : `${result.fileCount}`;
const lines = [];
lines.push(`${key("Build info")}: ${strTotalSize}, ${strFileCount} files`);
lines.push(
...(process.env.DEBUG ? [
needleDim(`Begin collecting files in \"${result.buildDirectory}\"`),
needleDim(`Collected ${result.fileCount} files (${result.totalSizeInMB.toFixed(2)} MB). Writing build info to \"${result.buildInfoPath}\"`),
needleDim(`Build info file successfully written to \"${result.buildInfoPath}\"`),
] : []),
)
if (closedHandles > 0) lines.push(`Closed ${closedHandles} dangling network handle(s)`);
needleLog("needle-buildinfo", lines.join("\n"), "log", { dimBody: false });
closeLogStreams();
};
return handleClose();
}
}
}
function delay(ms) {
return new Promise(res => setTimeout(res, ms));
}
function closeDanglingNetworkHandles() {
/** @type {() => any[] | undefined} */
// @ts-ignore private node api
const getActiveHandles = process._getActiveHandles;
if (typeof getActiveHandles !== "function") {
return 0;
}
let closed = 0;
const handles = getActiveHandles() || [];
for (const handle of handles) {
if (!handle || handle === process.stdin || handle === process.stdout || handle === process.stderr) continue;
const hasNetworkShape = typeof handle.destroy === "function" && (typeof handle.remotePort === "number" || typeof handle.remoteAddress === "string");
if (!hasNetworkShape) continue;
if (typeof handle.remotePort === "number" && handle.remotePort > 0) {
try {
handle.destroy();
closed++;
}
catch {
// ignore cleanup failures
}
}
}
return closed;
}