Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7152148410 |
@@ -9,8 +9,9 @@
|
|||||||
// and the per-module shape is the list with one entry. A bundle that fails to import is named —
|
// and the per-module shape is the list with one entry. A bundle that fails to import is named —
|
||||||
// in the log and in what `tools` answers for it — and the others serve.
|
// in the log and in what `tools` answers for it — and the others serve.
|
||||||
|
|
||||||
import { pathToFileURL } from "node:url";
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
||||||
import { resolve } from "node:path";
|
import { dirname, join, resolve } from "node:path";
|
||||||
|
import { existsSync, readFileSync } from "node:fs";
|
||||||
import { registerHooks } from "node:module";
|
import { registerHooks } from "node:module";
|
||||||
import { useBroker } from "@novox/mesh-sdk/messaging";
|
import { useBroker } from "@novox/mesh-sdk/messaging";
|
||||||
import { collectTools, toolKey, type ToolDefinition } from "@novox/mesh-sdk/tools";
|
import { collectTools, toolKey, type ToolDefinition } from "@novox/mesh-sdk/tools";
|
||||||
@@ -331,17 +332,49 @@ const SDK = "@novox/mesh-sdk";
|
|||||||
* one registry, one broker — the runtime's. Everything else a bundle carries resolves from the
|
* one registry, one broker — the runtime's. Everything else a bundle carries resolves from the
|
||||||
* bundle's own tree, as before. A launched bundle (ADR 0188) is another process and is untouched.
|
* bundle's own tree, as before. A launched bundle (ADR 0188) is another process and is untouched.
|
||||||
*
|
*
|
||||||
* Installed once, in-thread, before the first bundle is imported; the hook sees every import after.
|
* Installed once, in-thread, before the first bundle is imported; the hook sees every import after,
|
||||||
|
* `require` included. A bundle whose own copy is another version than the runtime's is said once,
|
||||||
|
* so a tool failing against the runtime's SDK points at the bundle rather than at the runtime.
|
||||||
*/
|
*/
|
||||||
function oneSdk(): void {
|
function oneSdk(): void {
|
||||||
if (sdkHooked) return;
|
if (sdkHooked) return;
|
||||||
sdkHooked = true;
|
|
||||||
registerHooks({
|
registerHooks({
|
||||||
resolve(specifier, context, next) {
|
resolve(specifier, context, next) {
|
||||||
if (specifier === SDK || specifier.startsWith(SDK + "/")) {
|
if (specifier === SDK || specifier.startsWith(SDK + "/")) {
|
||||||
|
if (context.parentURL) sayOtherSdk(context.parentURL);
|
||||||
return next(specifier, { ...context, parentURL: import.meta.url });
|
return next(specifier, { ...context, parentURL: import.meta.url });
|
||||||
}
|
}
|
||||||
return next(specifier, context);
|
return next(specifier, context);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
sdkHooked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sdkSaid = new Set<string>();
|
||||||
|
/** The version of the SDK copy nearest a file, by its package.json, or nothing when the file has none above it. */
|
||||||
|
function sdkVersionNear(fileURL: string): { dir: string; version: string } | undefined {
|
||||||
|
let dir = dirname(fileURLToPath(fileURL));
|
||||||
|
for (;;) {
|
||||||
|
const pkg = join(dir, "node_modules", SDK, "package.json");
|
||||||
|
if (existsSync(pkg)) {
|
||||||
|
try {
|
||||||
|
return { dir, version: String((JSON.parse(readFileSync(pkg, "utf8")) as { version?: string }).version ?? "?") };
|
||||||
|
} catch {
|
||||||
|
return { dir, version: "?" };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const up = dirname(dir);
|
||||||
|
if (up === dir) return undefined;
|
||||||
|
dir = up;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function sayOtherSdk(parentURL: string): void {
|
||||||
|
if (!parentURL.startsWith("file:")) return;
|
||||||
|
const own = sdkVersionNear(import.meta.url);
|
||||||
|
const theirs = sdkVersionNear(parentURL);
|
||||||
|
if (!theirs || !own || theirs.dir === own.dir || sdkSaid.has(theirs.dir)) return;
|
||||||
|
sdkSaid.add(theirs.dir);
|
||||||
|
if (theirs.version !== own.version) {
|
||||||
|
console.log(`[mesh-tools] ${theirs.dir} carries ${SDK} ${theirs.version}; this runtime's is ${own.version}, and the bundle speaks to the runtime's`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import { test } from "node:test";
|
import { test } from "node:test";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import { cpSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
import { cpSync, mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { tmpdir } from "node:os";
|
import { tmpdir } from "node:os";
|
||||||
import { connect, StringCodec } from "nats";
|
import { connect, StringCodec } from "nats";
|
||||||
@@ -199,36 +199,45 @@ test("the node's runtime serves five modules' bundles on one credential — two
|
|||||||
test("a bundle carrying its own copy of the SDK registers into the runtime's registry, and its tools are served (issue 209)", async (t) => {
|
test("a bundle carrying its own copy of the SDK registers into the runtime's registry, and its tools are served (issue 209)", async (t) => {
|
||||||
if (!url) return t.skip("MESH_TEST_NATS unset");
|
if (!url) return t.skip("MESH_TEST_NATS unset");
|
||||||
resetTools();
|
resetTools();
|
||||||
|
let dir = "";
|
||||||
|
let stop = () => {};
|
||||||
|
const closing: Array<() => Promise<void>> = [];
|
||||||
|
const said: string[] = [];
|
||||||
|
const log = console.log;
|
||||||
|
try {
|
||||||
// A bundle as the toolchain packs one: its compiled entrypoint, a package.json saying ES modules,
|
// A bundle as the toolchain packs one: its compiled entrypoint, a package.json saying ES modules,
|
||||||
// and its dependencies copied in — the SDK among them, a second copy beside the runtime's own.
|
// and its dependencies copied in — the SDK among them, a second copy beside the runtime's own,
|
||||||
const dir = mkdtempSync(join(tmpdir(), "mesh-bundle-"));
|
// and a dependency of the bundle's own that the runtime does not carry.
|
||||||
|
dir = mkdtempSync(join(tmpdir(), "mesh-bundle-"));
|
||||||
const sdk = realpathSync(fileURLToPath(new URL("../node_modules/@novox/mesh-sdk/", import.meta.url)));
|
const sdk = realpathSync(fileURLToPath(new URL("../node_modules/@novox/mesh-sdk/", import.meta.url)));
|
||||||
cpSync(sdk, join(dir, "node_modules", "@novox", "mesh-sdk"), { recursive: true });
|
cpSync(sdk, join(dir, "node_modules", "@novox", "mesh-sdk"), { recursive: true });
|
||||||
|
mkdirSync(join(dir, "node_modules", "zeta-flavour"), { recursive: true });
|
||||||
|
writeFileSync(join(dir, "node_modules", "zeta-flavour", "package.json"), '{"name":"zeta-flavour","type":"module","main":"index.js"}\n');
|
||||||
|
writeFileSync(join(dir, "node_modules", "zeta-flavour", "index.js"), 'export const flavour = "the bundle\'s own";\n');
|
||||||
writeFileSync(join(dir, "package.json"), '{"type":"module","private":true}\n');
|
writeFileSync(join(dir, "package.json"), '{"type":"module","private":true}\n');
|
||||||
writeFileSync(join(dir, "index.js"),
|
writeFileSync(join(dir, "index.js"),
|
||||||
'import { registerModuleTools } from "@novox/mesh-sdk/tools";\n' +
|
'import { registerModuleTools } from "@novox/mesh-sdk/tools";\n' +
|
||||||
'registerModuleTools("zeta", () => [{ name: "probe", description: "answers", input: {}, run: async () => ({ zeta: true }) }]);\n');
|
'import { flavour } from "zeta-flavour";\n' +
|
||||||
|
'registerModuleTools("zeta", () => [{ name: "probe", description: "answers", input: {}, run: async () => ({ zeta: true, flavour }) }]);\n');
|
||||||
const mesh = await aMesh();
|
const mesh = await aMesh();
|
||||||
|
closing.push(() => mesh.close());
|
||||||
await mesh.issue(membershipOf("zeta", "anchor"));
|
await mesh.issue(membershipOf("zeta", "anchor"));
|
||||||
const credential = { url, node: "anchor", module: "node-tools" };
|
const credential = { url, node: "anchor", module: "node-tools" };
|
||||||
const nodeTools = await connectNats(credential);
|
const nodeTools = await connectNats(credential);
|
||||||
|
closing.push(() => nodeTools.close());
|
||||||
const asker = await connectNats({ url, module: "console", node: "workstation" });
|
const asker = await connectNats({ url, module: "console", node: "workstation" });
|
||||||
const said: string[] = [];
|
closing.push(() => asker.close());
|
||||||
const log = console.log;
|
|
||||||
console.log = (...a: unknown[]) => said.push(a.join(" "));
|
console.log = (...a: unknown[]) => said.push(a.join(" "));
|
||||||
let stop = () => {};
|
|
||||||
try {
|
|
||||||
stop = await runTools({ broker: nodeTools, credential, serves: [{ module: "zeta", entrypoints: [join(dir, "index.js")] }] });
|
stop = await runTools({ broker: nodeTools, credential, serves: [{ module: "zeta", entrypoints: [join(dir, "index.js")] }] });
|
||||||
console.log = log;
|
console.log = log;
|
||||||
assert.ok(said.some((s) => /serving 1 tool\(s\) for 1 module\(s\): zeta\.probe/.test(s)), said.join("\n"));
|
assert.ok(said.some((s) => /serving 1 tool\(s\) for 1 module\(s\): zeta\.probe/.test(s)), said.join("\n"));
|
||||||
assert.deepEqual((await callTool(asker, "zeta.probe@anchor", {})).result, { zeta: true });
|
// The SDK is the runtime's (the registration arrived); the bundle's other dependency is its own.
|
||||||
|
assert.deepEqual((await callTool(asker, "zeta.probe@anchor", {})).result, { zeta: true, flavour: "the bundle's own" });
|
||||||
} finally {
|
} finally {
|
||||||
console.log = log;
|
console.log = log;
|
||||||
stop();
|
stop();
|
||||||
await asker.close();
|
for (const close of closing.reverse()) await close();
|
||||||
await nodeTools.close();
|
if (dir) rmSync(dir, { recursive: true, force: true });
|
||||||
await mesh.close();
|
|
||||||
rmSync(dir, { recursive: true, force: true });
|
|
||||||
resetTools();
|
resetTools();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user