UNPKG

@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.

165 lines (146 loc) 6.33 kB
import { resolve, join, isAbsolute, relative } from 'path' import { existsSync, statSync, mkdirSync, readdirSync, copyFileSync, mkdir, rmSync } from 'fs'; import { builtAssetsDirectory, tryLoadProjectConfig } from './config.js'; import { copyFilesSync, formatBytes } from '../common/files.js'; import { needleBlue, needleDim, needleLog, needleSupportsColor } from './logging.js'; const pluginName = "needle-copy-files"; /** Copy files on build from assets to dist. * @param {"build" | "serve"} command * @param {import('../types/needleConfig').needleMeta | null | undefined} config * @param {import('../types').userSettings} userSettings * @returns {import('vite').Plugin | null} */ export function needleCopyFiles(command, config, userSettings) { if (config?.noCopy === true || userSettings?.noCopy === true) { return null; } return { name: 'needle-copy-files', // explicitly don't enforce post or pre because we want to run before the build-pipeline plugin enforce: "pre", buildStart() { return run("start", config); }, closeBundle() { return run("end", config); }, } } /** * @param {"start" | "end"} buildstep * @param {import('../types').userSettings} config */ async function run(buildstep, config) { const copyIncludesFromEngine = config?.copyIncludesFromEngine ?? true; const copied = { count: 0, bytes: 0 }; const baseDir = process.cwd(); const override = buildstep === "start"; const supportsColor = needleSupportsColor(); const key = (text) => supportsColor ? needleBlue(text) : text; const rel = (pathValue) => { const result = relative(baseDir, pathValue).replaceAll("\\", "/"); if (!result || result.length === 0) return "."; return result; }; let assetsDirName = "assets"; let outdirName = "dist"; const needleConfig = tryLoadProjectConfig(); if (needleConfig) { assetsDirName = needleConfig.assetsDirectory || assetsDirName; while (assetsDirName.startsWith('/')) assetsDirName = assetsDirName.substring(1); if (needleConfig.buildDirectory) outdirName = needleConfig.buildDirectory; } // For the "end" step, the first log line carries a leading newline to visually // separate this section from the build-pipeline output above it. let firstLog = true; const logLine = (msg) => { needleLog(pluginName, msg, "log", { dimBody: false, leadingNewline: firstLog && buildstep === "end", }); firstLog = false; }; if (copyIncludesFromEngine !== false) { // copy include from engine const engineIncludeDir = resolve(baseDir, 'node_modules', '@needle-tools', 'engine', 'src', 'include'); if (existsSync(engineIncludeDir)) { logLine(needleDim(`Copy Needle Engine includes: ${rel(engineIncludeDir)}${rel(resolve(baseDir, 'include'))}`)); const projectIncludeDir = resolve(baseDir, 'include'); copyFilesSync(engineIncludeDir, projectIncludeDir, true, copied); } } const outDir = resolve(baseDir, outdirName); if (!existsSync(outDir)) { mkdirSync(outDir, { recursive: true }); } // copy a list of files or directories declared in build.copy = [] in the needle.config.json /* "build": { "copy": ["myFolder", "myFile.txt"] } */ if (needleConfig?.build?.copy) { const arr = needleConfig.build.copy; for (let i = 0; i < arr.length; i++) { const entry = arr[i]; if (Array.isArray(entry)) { console.log("WARN: build.copy can only contain string paths to copy to. Found array instead."); continue; } const src = resolve(baseDir, entry); const dest = resolvePath(outDir, entry); if (existsSync(src) && dest) { logLine(`Configured copy: ${rel(src)}${rel(dest)}`); copyFilesSync(src, dest, override, copied); } } } // copy assets dir const assetsDir = resolve(baseDir, assetsDirName); if (existsSync(assetsDir)) { const targetDir = resolve(outDir, 'assets'); // ensure that the target directory exists and is cleared if it already exists // otherwise we might run into issues where the build pipeline is running for already compressed files if (override && existsSync(targetDir)) { logLine(needleDim(`Clearing target directory "${rel(targetDir)}"`)); rmSync(targetDir, { recursive: true, force: true }); } logLine(`${key("Copying assets")}: ${rel(assetsDir)}${rel(targetDir)}`); copyFilesSync(assetsDir, targetDir, override, copied); } else logLine(`No assets directory found. Skipping copy of ${assetsDirName} resolved to ${rel(assetsDir)}`); // copy include dir const includeDir = resolve(baseDir, 'include'); if (existsSync(includeDir)) { const targetDir = resolve(outDir, 'include'); logLine(needleDim(`Include: ${rel(includeDir)}${rel(targetDir)}`)); copyFilesSync(includeDir, targetDir, override, copied); } logLine(`${key("Copied files")}: ${copied.count}, ${formatBytes(copied.bytes || 0)}`); if (buildstep === "end") { logLine("✨ Happy creating! 🌵"); } } /** resolves relative or absolute paths to a path inside the out directory * for example D:/myFile.txt would resolve to outDir/myFile.txt * wherereas "some/relative/path" would become outDir/some/relative/path * @param {string} outDir * @param {string} pathValue */ function resolvePath(outDir, pathValue) { if (isAbsolute(pathValue)) { var exists = existsSync(pathValue); if (!exists) return null; var stats = exists && statSync(pathValue); if (stats.isDirectory()) { const dirName = pathValue.replaceAll('\\', '/').split('/').pop(); if (!dirName) return null; return resolve(outDir, dirName); } const fileName = pathValue.replaceAll('\\', '/').split('/').pop(); if (!fileName) return null; return resolve(outDir, fileName); } return resolve(outDir, pathValue); }