UNPKG

@adonis-agora/agent

Version:

Governed, durable-backed AI agent (chat + tool-calling + governance) for AdonisJS — part of the Agora ecosystem.

195 lines • 8.52 kB
/** * One in-memory build with the app's Vite (`vite.build`, nothing written) — what both the kit bundle * and the compiled styles come from. The app's `resolve` (aliases) and `css` options (preprocessor * options, CSS modules, PostCSS, Lightning CSS) are kept, so `@/lib/utils`, a Sass `additionalData` * or a PostCSS plugin work as they do in the app; assets referenced by the CSS are inlined as * `data:` URIs up to a limit and a budget, the rest left out (the frame has no network). */ import { createHash } from 'node:crypto'; import { existsSync, readFileSync, statSync } from 'node:fs'; import { extname, join, relative, resolve } from 'node:path'; import { INLINE_ASSET_DEFAULTS, inlineCssUrls } from '../sandbox-styles.js'; /** The entry's file name (Vite resolves an input against the root) and its virtual id. */ const ENTRY_FILE = 'genui-sandbox-kit-entry.js'; const ENTRY_ID = '\0genui-sandbox-kit-entry'; const MIME = { '.woff2': 'font/woff2', '.woff': 'font/woff', '.ttf': 'font/ttf', '.otf': 'font/otf', '.eot': 'application/vnd.ms-fontobject', '.svg': 'image/svg+xml', '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.webp': 'image/webp', '.avif': 'image/avif', '.ico': 'image/x-icon', '.cur': 'image/x-icon', }; export async function runKitViteBuild(options) { const vite = (await import('vite')); const major = Number.parseInt(String(vite.version).split('.')[0] ?? '0', 10); const limit = options.inlineAssetLimit ?? INLINE_ASSET_DEFAULTS.limit; const budget = options.inlineAssetBudget ?? INLINE_ASSET_DEFAULTS.budget; let inlined = 0; const reasons = new Map(); const result = await vite.build({ configFile: false, root: options.root, logLevel: 'silent', mode: 'production', publicDir: options.publicDir ?? false, // One React: components living outside the app (a workspace package, another repo) resolve // `react` from the app root too, never a second copy that would break their hooks. resolve: { ...(options.resolve ?? {}), dedupe: [ ...(options.resolve?.dedupe ?? []), 'react', 'react-dom', ], }, css: quietCss(options.css), define: { 'process.env.NODE_ENV': JSON.stringify('production') }, // Vite 8 compiles with Oxc, earlier versions with esbuild: the automatic JSX runtime on both. // Production JSX (`jsx`, never `jsxDEV`) whatever NODE_ENV the dev server runs under. ...(major >= 8 ? { oxc: { jsx: { runtime: 'automatic', development: false } } } : { esbuild: { jsx: 'automatic', jsxDev: false } }), plugins: [ { name: 'genui-sandbox-kit-entry', resolveId(id) { if (id === ENTRY_ID || id.endsWith(ENTRY_FILE)) return ENTRY_ID; return null; }, load(id) { return id === ENTRY_ID ? options.entry : null; }, }, ...(options.plugins ?? []), ], build: { write: false, emptyOutDir: false, copyPublicDir: false, minify: options.minify ?? true, sourcemap: false, cssCodeSplit: false, reportCompressedSize: false, modulePreload: false, // Inlined (`data:`) within the limit and the budget; the rest is emitted, then left out below. assetsInlineLimit: (file, content) => { const name = relative(options.root, file) || file; if (content.length > limit) { reasons.set(file, `${name} (${kb(content.length)}) is over the ${kb(limit)} limit`); return false; } if (inlined + content.length > budget) { reasons.set(file, `${name} is over the ${kb(budget)} budget for inlined assets`); return false; } inlined += content.length; return true; }, rollupOptions: { input: ENTRY_FILE, // The app's own build reports its warnings (`"use client"` directives…); this one is quiet. onwarn() { }, output: { format: 'iife', name: '__genuiSandboxKit', // One file (Rolldown already bundles an IIFE into one, and warns about the option). ...(major >= 8 ? {} : { inlineDynamicImports: true }), entryFileNames: 'genui-sandbox-kit.js', }, }, }, }); const outputs = (Array.isArray(result) ? result : [result]); let code = ''; let css = ''; const modules = new Set(); const emitted = new Map(); for (const out of outputs) { for (const item of out.output ?? []) { if (item.type === 'chunk') { code += item.code ?? ''; for (const id of item.moduleIds ?? Object.keys(item.modules ?? {})) { if (!id.startsWith('\0') && (!id.includes('/node_modules/') || /\/node_modules\/\.pnpm\/[^/]*@file\+/.test(id))) modules.add(id.split('?')[0] ?? id); } } else if (item.fileName.endsWith('.css')) { css += typeof item.source === 'string' ? item.source : Buffer.from(item.source ?? new Uint8Array()).toString('utf8'); } else { const original = item.originalFileNames?.[0]; emitted.set(item.fileName, original === undefined ? undefined : resolve(options.root, original)); } } } const publicDir = options.publicDir === false || options.publicDir === undefined ? undefined : options.publicDir; css = inlineCssUrls(css, { limit, // Vite counted what it inlined; this pass only checks what is left. budget: Number.POSITIVE_INFINITY, ...(options.warn !== undefined ? { warn: options.warn } : {}), resolve(url) { const path = (url.split(/[?#]/)[0] ?? url).replace(/^\.?\//, ''); for (const [fileName, original] of emitted) { if (path === fileName || path.endsWith(`/${fileName}`) || path.endsWith(fileName)) { return { dropped: (original !== undefined ? reasons.get(original) : undefined) ?? `${fileName} was not inlined`, }; } } if (publicDir !== undefined && url.startsWith('/')) { const file = join(publicDir, decodeURIComponent(path)); if (existsSync(file) && statSync(file).isFile()) { const bytes = readFileSync(file); if (inlined + bytes.length > budget) return { dropped: `over the ${kb(budget)} budget for inlined assets` }; inlined += bytes.length; return { data: bytes.toString('base64'), mime: MIME[extname(file).toLowerCase()] ?? 'application/octet-stream', bytes: bytes.length, }; } } return null; }, }); return { code, css, modules: [...modules] }; } /** Sass's deprecation warnings (Bootstrap's own, by the thousand) are not the kit's to print. */ const QUIET_SASS = { quietDeps: true, logger: { warn() { }, debug() { } } }; /** The app's `css` options, with Sass told to keep quiet unless the app said otherwise. */ function quietCss(css) { const given = (css ?? {}); const pre = given.preprocessorOptions ?? {}; return { ...given, preprocessorOptions: { ...pre, scss: { ...QUIET_SASS, ...(pre.scss ?? {}) }, sass: { ...QUIET_SASS, ...(pre.sass ?? {}) }, }, }; } export function contentHash(text) { return createHash('sha256').update(text).digest('hex').slice(0, 12); } function kb(bytes) { return `${Math.round(bytes / 1024)} KB`; } //# sourceMappingURL=vite-build.js.map