Files
LocalAI/addons/internal/host-compat/test/loader.mjs
T
Ettore Di Giacinto c6bb76939b feat(addons): let add-on pages keep the host modules they were written against
A page written for the web UI imports React, the router, i18next and a
few shared components. The add-on builder refuses a bundle that holds a
host file, so moving such a page into an add-on meant rewriting it.

Add a compat mode to the shared builder. It answers those imports from a
directory of stand-ins that read the SDK, bundles the dynamic import of
the page code into the one output file, and collects the style sheets the
page imports. A relative import that is not in the add-on's own source
resolves to the file at the same path under the compat directory.

Widen the SDK for these pages, without a version change: more host
components (spinner, modal, lightbox, model picker) and hooks (models,
resources, voice profiles, operations, MCP client), a style on icons,
the icon names the pages draw, sdk.navigation.openApp to open a page of
the app (only addresses under /app, and the way back as the only state)
and sdk.portal. The shell passes the query string to the page.

The stand-ins cover the router (the tab in the path, the model in the
query), the translator over the host locale files, the core API calls
through sdk.core.fetch, and byte copies of the pure helpers, with tests
that keep the copies equal to the originals.

Assisted-by: Claude Code:claude-sonnet-5-5 node
Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
2026-10-20 20:54:44 +00:00

29 lines
1.4 KiB
JavaScript

// A node resolve hook for tests that import add-on source files directly. The
// bundler answers a relative import whose target is not in the add-on's src/
// from the compat directory (see addons/internal/uibuild/uibuild.mjs); this
// hook does the same for node, so a source file runs without a build.
import { existsSync, statSync } from 'node:fs'
import { dirname, join, relative, resolve, sep } from 'node:path'
import { fileURLToPath, pathToFileURL } from 'node:url'
const compatSrc = resolve(dirname(fileURLToPath(import.meta.url)), '../src')
const tails = ['', '.js', '.jsx', '.mjs']
const find = (base) => tails.map((t) => base + t).find((f) => existsSync(f) && statSync(f).isFile())
export async function resolve_(specifier, context, nextResolve) {
if (specifier.startsWith('.') && context.parentURL?.startsWith('file:')) {
const parent = fileURLToPath(context.parentURL)
const m = /^(.*[\\/]addons[\\/][^\\/]+[\\/]ui[\\/]src)[\\/]/.exec(parent)
if (m) {
const target = resolve(dirname(parent), specifier)
const found = find(target)
if (found) return nextResolve(pathToFileURL(found).href, context)
const rel = relative(m[1], target)
const hit = !rel.startsWith('..') && find(join(compatSrc, rel))
if (hit) return nextResolve(pathToFileURL(hit).href, context)
}
}
return nextResolve(specifier, context)
}
export { resolve_ as resolve }