Update dependency isolation check for the required client peer
The app-only consumer now also exercises the ./react entry. The server-only consumer no longer asserts that client is absent from node_modules (npm auto-installs required peers) but still checks that the server entry does not bundle it. Surface npm and esbuild errors instead of swallowing them.
This commit is contained in:
parent
d6ebcca9a0
commit
7f686ff2b4
1 changed files with 44 additions and 18 deletions
|
|
@ -17,13 +17,20 @@ const packageJson = JSON.parse(
|
|||
const client = "@modelcontextprotocol/client";
|
||||
const server = "@modelcontextprotocol/server";
|
||||
|
||||
// App and AppBridge extend the client package's Protocol class, so client is
|
||||
// a required peer for every consumer. The server helpers are only needed by
|
||||
// server authors, so server stays optional: View-only consumers must not have
|
||||
// it installed or bundled.
|
||||
for (const role of [client, server]) {
|
||||
if (!packageJson.peerDependencies?.[role]) {
|
||||
throw new Error(`${role} must remain a peer dependency`);
|
||||
}
|
||||
if (packageJson.peerDependenciesMeta?.[role]?.optional !== true) {
|
||||
throw new Error(`${role} must be an optional peer dependency`);
|
||||
}
|
||||
if (packageJson.peerDependenciesMeta?.[client]?.optional) {
|
||||
throw new Error(`${client} must be a required peer dependency`);
|
||||
}
|
||||
if (packageJson.peerDependenciesMeta?.[server]?.optional !== true) {
|
||||
throw new Error(`${server} must be an optional peer dependency`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -42,6 +49,13 @@ function exactDevDependency(name) {
|
|||
return version;
|
||||
}
|
||||
|
||||
/** Exact version of a package as installed in this repository's node_modules. */
|
||||
function installedVersion(name) {
|
||||
return JSON.parse(
|
||||
readFileSync(join(root, "node_modules", name, "package.json"), "utf8"),
|
||||
).version;
|
||||
}
|
||||
|
||||
const temporaryRoot = mkdtempSync(join(tmpdir(), "ext-apps-role-peers-"));
|
||||
try {
|
||||
// Minimal environment: nothing inherited from the caller's npm config
|
||||
|
|
@ -72,17 +86,25 @@ try {
|
|||
|
||||
const consumers = [
|
||||
{
|
||||
// View / host author: ext-apps + client (+ react for the hooks entry).
|
||||
name: "app-only",
|
||||
dependencies: {
|
||||
"@types/node": exactDevDependency("@types/node"),
|
||||
"@types/react": installedVersion("@types/react"),
|
||||
[client]: exactDevDependency(client),
|
||||
"@modelcontextprotocol/ext-apps": `file:${tarball}`,
|
||||
react: installedVersion("react"),
|
||||
},
|
||||
absent: server,
|
||||
// server must be neither installed (it is an optional peer) nor bundled.
|
||||
mustNotInstall: true,
|
||||
entry:
|
||||
'import { App } from "@modelcontextprotocol/ext-apps"; import { AppBridge } from "@modelcontextprotocol/ext-apps/app-bridge"; console.log(App, AppBridge);',
|
||||
'import { App } from "@modelcontextprotocol/ext-apps"; import { AppBridge } from "@modelcontextprotocol/ext-apps/app-bridge"; import { useApp } from "@modelcontextprotocol/ext-apps/react"; console.log(App, AppBridge, useApp);',
|
||||
},
|
||||
{
|
||||
// Server author: ext-apps + server. npm auto-installs client as a
|
||||
// required peer (its types back the shared wire types), but the server
|
||||
// entry must not pull it into a runtime bundle.
|
||||
name: "server-only",
|
||||
dependencies: {
|
||||
"@types/node": exactDevDependency("@types/node"),
|
||||
|
|
@ -90,6 +112,7 @@ try {
|
|||
[server]: exactDevDependency(server),
|
||||
},
|
||||
absent: client,
|
||||
mustNotInstall: false,
|
||||
entry:
|
||||
'import { registerAppTool } from "@modelcontextprotocol/ext-apps/server"; console.log(registerAppTool);',
|
||||
},
|
||||
|
|
@ -115,9 +138,10 @@ try {
|
|||
"--no-audit",
|
||||
"--no-fund",
|
||||
],
|
||||
{ cwd: directory, stdio: "pipe", env: npmEnvironment },
|
||||
{ cwd: directory, stdio: "inherit", env: npmEnvironment },
|
||||
);
|
||||
|
||||
if (consumer.mustNotInstall) {
|
||||
const absentPath = join(
|
||||
directory,
|
||||
"node_modules",
|
||||
|
|
@ -132,12 +156,14 @@ try {
|
|||
} catch (error) {
|
||||
if (error?.code !== "ENOENT") throw error;
|
||||
}
|
||||
}
|
||||
|
||||
writeFileSync(join(directory, "entry.ts"), consumer.entry);
|
||||
writeFileSync(
|
||||
join(directory, "tsconfig.json"),
|
||||
JSON.stringify({
|
||||
compilerOptions: {
|
||||
jsx: "react-jsx",
|
||||
lib: ["ES2020", "DOM"],
|
||||
module: "ESNext",
|
||||
moduleResolution: "bundler",
|
||||
|
|
@ -165,7 +191,7 @@ try {
|
|||
"--outfile=bundle.js",
|
||||
`--metafile=${metafile}`,
|
||||
],
|
||||
{ cwd: directory, stdio: "pipe" },
|
||||
{ cwd: directory, stdio: "inherit" },
|
||||
);
|
||||
const bundleInputs = Object.keys(
|
||||
JSON.parse(readFileSync(metafile, "utf8")).inputs,
|
||||
|
|
|
|||
Loading…
Reference in a new issue