Files
ansiblings/packages/nopy/src/cubes/resolve-hook.mjs
T
Benjamin Diedrichsen 6ecb2c366f
Publish snapshot / snapshot (push) Successful in 1m2s
[refactor] moving cubes into own package"
[fix] default parameter run records parameters in session for replay[fix] remove default parameters for several cubes
2026-07-28 12:18:10 +02:00

63 lines
2.3 KiB
JavaScript

/**
* A module resolve hook that lets a hand-written cube import the packages nopy
* itself already has.
*
* A manifest is loaded with `import(manifestPath)`, so its imports resolve from
* its own directory. A cube sitting in an arbitrary `cubeDirs` entry — no
* package.json above it, no node_modules beside it — therefore cannot import
* `@bitsquare/nopy-cube` or `zod` at all, and the run dies on
* ERR_MODULE_NOT_FOUND before a single deploy is built.
*
* A published cube bundle never reaches this: it declares its own dependencies
* and Node resolves them normally. This is for the local tree.
*
* Plain `.mjs` rather than TypeScript because the hook runs on its own thread,
* loaded by Node directly from `dist` — there is no compile step in that path.
*
* @module cubes/resolve-hook
*/
import { createRequire } from 'node:module';
import { pathToFileURL } from 'node:url';
/**
* The specifiers worth rescuing: what a manifest legitimately needs and cannot
* be expected to install for itself. Anything else stays a hard failure — a
* cube that wants a library should depend on it.
*/
const FALLBACK_ROOTS = ['@bitsquare/nopy-cube', '@bitsquare/nopy', 'zod'];
/** @type {NodeRequire | undefined} */
let fallbackRequire;
/**
* @param {{ from: string }} data - a URL inside the running CLI's own package,
* which is where the fallback resolution starts from.
*/
export function initialize(data) {
fallbackRequire = createRequire(data.from);
}
/** True for `zod` and for subpaths like `zod/v4` or `@bitsquare/nopy/package.json`. */
function isCovered(specifier) {
return FALLBACK_ROOTS.some((root) => specifier === root || specifier.startsWith(`${root}/`));
}
export async function resolve(specifier, context, next) {
try {
// Normal resolution first, always. A consumer that has its own copy
// installed keeps using it, so the hook can never introduce version skew —
// it only fills in for a lookup that was going to fail.
return await next(specifier, context);
} catch (error) {
if (!fallbackRequire || !isCovered(specifier)) throw error;
try {
return { url: pathToFileURL(fallbackRequire.resolve(specifier)).href, shortCircuit: true };
} catch {
// The CLI cannot see it either. Report the original failure, which names
// the importer rather than the CLI.
throw error;
}
}
}