refactor(version): single-source the version from the root package.json
The Inspector ships as one package with one version, but four client package.jsons each carried their own `version` and were read at runtime (cli.ts, tui App.tsx), and core hardcoded a third, stale value (0.20.0) that the TUI and web actually reported to servers. Consolidate onto the root package.json as the single source of truth. - Add core/node/version.ts: shared, Node-only readInspectorVersion() that walks up to the root package.json (always shipped in the tarball). 100% covered by a new integration test. - CLI and TUI read the version via the shared reader instead of their own manifest; TUI now also passes a real clientIdentity (was falling through to the stale core default). - Drop the `version` field from all four client package.jsons; the root keeps the only version. TUI header name/description become local display constants. - core default client identity: replace the stale 0.20.0 hardcode with a neutral 0.0.0 placeholder (real clients pass clientIdentity; web will source its version from GET /api/config, see #1639). - Stop shipping clients/cli/package.json and clients/tui/package.json (no longer read at runtime) — supersedes the earlier "ship them" fix; update pack:verify and the root README packaging invariants accordingly. - Add core/node/** to the web coverage include so the new module is gated. - Add the shared-code architecture diagram to the root README. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BrihGWcrM9JGRyu41nzZYw
This commit is contained in:
parent
79b80b459d
commit
7815548300
14 changed files with 178 additions and 66 deletions
|
|
@ -74,6 +74,8 @@ npm run web:dev # web launcher in --dev mode (Vite)
|
||||||
|
|
||||||
## The `@inspector/core` shared package
|
## The `@inspector/core` shared package
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
`core/` holds the logic shared by all three clients so that web, CLI, and TUI behave identically. Its entry point is the **`InspectorClient`** class (`core/mcp/`), which owns the connection to an MCP server, the request/response lifecycle, and a set of state stores; `core/react/` exposes React hooks over those stores that both the web and TUI (Ink) React trees consume. OAuth (`core/auth/`) is factored into isomorphic logic plus browser/node/remote backends so the same flows work in the browser, in Node, and against a remote backend.
|
`core/` holds the logic shared by all three clients so that web, CLI, and TUI behave identically. Its entry point is the **`InspectorClient`** class (`core/mcp/`), which owns the connection to an MCP server, the request/response lifecycle, and a set of state stores; `core/react/` exposes React hooks over those stores that both the web and TUI (Ink) React trees consume. OAuth (`core/auth/`) is factored into isomorphic logic plus browser/node/remote backends so the same flows work in the browser, in Node, and against a remote backend.
|
||||||
|
|
||||||
`core/` intentionally has **no `package.json`** — it is not published on its own. Each client bundles it in via a `@inspector/core` alias:
|
`core/` intentionally has **no `package.json`** — it is not published on its own. Each client bundles it in via a `@inspector/core` alias:
|
||||||
|
|
@ -140,7 +142,7 @@ The root `package.json` `"files"` allowlist is the source of truth for the tarba
|
||||||
|
|
||||||
- **No source maps.** The client bundlers set `sourcemap: false` (`clients/{cli,tui}/tsup.config.ts`, `clients/web/tsup.runner.config.ts`); Vite and the launcher's `tsc` already emit none. Maps are ~half the unpacked size and aren't needed at runtime — debug via `npm run dev` on the source.
|
- **No source maps.** The client bundlers set `sourcemap: false` (`clients/{cli,tui}/tsup.config.ts`, `clients/web/tsup.runner.config.ts`); Vite and the launcher's `tsc` already emit none. Maps are ~half the unpacked size and aren't needed at runtime — debug via `npm run dev` on the source.
|
||||||
- **`clients/web/build` ships via `clients/web/.npmignore`.** `clients/web/.gitignore` lists `build/`, and npm's packlist honors that nested `.gitignore` over the root `"files"` allowlist — so the prod web-server runner was silently missing from the tarball while `clients/web/dist` slipped through (its `.gitignore` only lists `dist-ssr`). `clients/web/.npmignore` overrides the `.gitignore` for publishing so both `build/` (runner) and `dist/` (SPA) ship. The other clients don't need this — none ship a nested `.gitignore`.
|
- **`clients/web/build` ships via `clients/web/.npmignore`.** `clients/web/.gitignore` lists `build/`, and npm's packlist honors that nested `.gitignore` over the root `"files"` allowlist — so the prod web-server runner was silently missing from the tarball while `clients/web/dist` slipped through (its `.gitignore` only lists `dist-ssr`). `clients/web/.npmignore` overrides the `.gitignore` for publishing so both `build/` (runner) and `dist/` (SPA) ship. The other clients don't need this — none ship a nested `.gitignore`.
|
||||||
- **`clients/cli/package.json` and `clients/tui/package.json` ship** because those bundles read their own `package.json` at runtime (CLI client identity; TUI header `name`/`description`/`version`) relative to the bundle. Without them the installed CLI crashes on connect and the installed TUI crashes on launch — even though both work in-repo, where the files are present.
|
- **A single version number, read from the root `package.json`.** The Inspector ships as one package with one version, so only the **root** `package.json` carries a `version` — the four `clients/*/package.json`s deliberately have none. Every Node client (CLI, TUI, and the web backend) resolves the version through the shared `readInspectorVersion()` reader in `core/node/version.ts`, which walks up to the root manifest (always present in the tarball). No client `package.json` is read at runtime, so none needs to ship. The web **browser** can't read the filesystem; it gets its version from the backend via `GET /api/config` (see [#1639](https://github.com/modelcontextprotocol/inspector/issues/1639)).
|
||||||
|
|
||||||
### `npm run pack:verify` — publish smoke against the real tarball
|
### `npm run pack:verify` — publish smoke against the real tarball
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "@modelcontextprotocol/inspector-cli",
|
"name": "@modelcontextprotocol/inspector-cli",
|
||||||
"version": "2.0.0",
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "CLI for the Model Context Protocol inspector",
|
"description": "CLI for the Model Context Protocol inspector",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
import { dirname, join } from "path";
|
|
||||||
import { fileURLToPath, pathToFileURL } from "url";
|
|
||||||
import { Command } from "commander";
|
import { Command } from "commander";
|
||||||
type McpResponse = Record<string, unknown>;
|
type McpResponse = Record<string, unknown>;
|
||||||
import { awaitableLog } from "./utils/awaitable-log.js";
|
import { awaitableLog } from "./utils/awaitable-log.js";
|
||||||
|
|
@ -49,11 +47,15 @@ import {
|
||||||
LoggingLevelSchema,
|
LoggingLevelSchema,
|
||||||
type LoggingLevel,
|
type LoggingLevel,
|
||||||
} from "@modelcontextprotocol/sdk/types.js";
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
import { readInspectorVersion } from "@inspector/core/node/version.js";
|
||||||
|
|
||||||
export const validLogLevels: LoggingLevel[] = Object.values(
|
export const validLogLevels: LoggingLevel[] = Object.values(
|
||||||
LoggingLevelSchema.enum,
|
LoggingLevelSchema.enum,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** Client identity name the CLI reports to servers. */
|
||||||
|
const CLI_CLIENT_NAME = "inspector-cli";
|
||||||
|
|
||||||
type MethodArgs = {
|
type MethodArgs = {
|
||||||
method?: string;
|
method?: string;
|
||||||
promptName?: string;
|
promptName?: string;
|
||||||
|
|
@ -74,20 +76,13 @@ async function callMethod(
|
||||||
cliAuthOverrides: RunnerClientConfigOverrides,
|
cliAuthOverrides: RunnerClientConfigOverrides,
|
||||||
callbackUrlConfig: RunnerOAuthCallbackConfig,
|
callbackUrlConfig: RunnerOAuthCallbackConfig,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
// Version comes from the single source of truth — the root package.json —
|
||||||
const packageJsonPath = join(__dirname, "../package.json");
|
// via the shared core reader, not the CLI's own manifest.
|
||||||
const packageJsonData = await import(pathToFileURL(packageJsonPath).href, {
|
const clientIdentity = {
|
||||||
with: { type: "json" },
|
name: CLI_CLIENT_NAME,
|
||||||
});
|
version: readInspectorVersion(import.meta.url),
|
||||||
const packageJson = packageJsonData.default as {
|
|
||||||
name: string;
|
|
||||||
version: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const [, name = packageJson.name] = packageJson.name.split("/");
|
|
||||||
const version = packageJson.version;
|
|
||||||
const clientIdentity = { name, version };
|
|
||||||
|
|
||||||
const environment: InspectorClientEnvironment = {
|
const environment: InspectorClientEnvironment = {
|
||||||
transport: createTransportNode,
|
transport: createTransportNode,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "@modelcontextprotocol/inspector-launcher",
|
"name": "@modelcontextprotocol/inspector-launcher",
|
||||||
"version": "2.0.0",
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Launcher for MCP Inspector (web, CLI, TUI)",
|
"description": "Launcher for MCP Inspector (web, CLI, TUI)",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "@modelcontextprotocol/inspector-tui",
|
"name": "@modelcontextprotocol/inspector-tui",
|
||||||
"version": "2.0.0",
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Terminal User Interface (TUI) for the Model Context Protocol inspector",
|
"description": "Terminal User Interface (TUI) for the Model Context Protocol inspector",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,6 @@ import React, {
|
||||||
useRef,
|
useRef,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { Box, Text, useInput, useApp, type Key } from "ink";
|
import { Box, Text, useInput, useApp, type Key } from "ink";
|
||||||
import { readFileSync } from "fs";
|
|
||||||
import { fileURLToPath } from "url";
|
|
||||||
import { dirname, join } from "path";
|
|
||||||
import type {
|
import type {
|
||||||
MessageEntry,
|
MessageEntry,
|
||||||
FetchRequestEntry,
|
FetchRequestEntry,
|
||||||
|
|
@ -56,6 +53,7 @@ import {
|
||||||
isOAuthCapableServerConfig,
|
isOAuthCapableServerConfig,
|
||||||
} from "@inspector/core/client/runner.js";
|
} from "@inspector/core/client/runner.js";
|
||||||
import { formatRunnerOAuthRedirectUrl } from "@inspector/core/auth/node/runner-oauth-callback.js";
|
import { formatRunnerOAuthRedirectUrl } from "@inspector/core/auth/node/runner-oauth-callback.js";
|
||||||
|
import { readInspectorVersion } from "@inspector/core/node/version.js";
|
||||||
import {
|
import {
|
||||||
createOAuthCallbackServer,
|
createOAuthCallbackServer,
|
||||||
type OAuthCallbackServer,
|
type OAuthCallbackServer,
|
||||||
|
|
@ -85,33 +83,17 @@ import { PromptTestModal } from "./components/PromptTestModal.js";
|
||||||
import { DetailsModal } from "./components/DetailsModal.js";
|
import { DetailsModal } from "./components/DetailsModal.js";
|
||||||
import type { TuiServer } from "./tui-servers.js";
|
import type { TuiServer } from "./tui-servers.js";
|
||||||
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
// Header branding. The version is the single source of truth — the root
|
||||||
const __dirname = dirname(__filename);
|
// package.json — read via the shared core reader; the name/description are the
|
||||||
|
// TUI's own display strings (they live in code, not the npm manifest, which no
|
||||||
|
// longer carries them per-client).
|
||||||
|
const APP_NAME = "MCP Inspector TUI";
|
||||||
|
const APP_DESCRIPTION =
|
||||||
|
"Terminal User Interface for the Model Context Protocol Inspector";
|
||||||
|
const APP_VERSION = readInspectorVersion(import.meta.url);
|
||||||
|
|
||||||
// Read package.json to get project info
|
/** Client identity name the TUI reports to servers. */
|
||||||
// Strategy: Try multiple paths to handle both local dev and global install
|
const TUI_CLIENT_NAME = "inspector-tui";
|
||||||
// - Local dev (tsx): __dirname = src/, package.json is one level up
|
|
||||||
// - Global install: __dirname = dist/src/, package.json is two levels up
|
|
||||||
let packagePath: string;
|
|
||||||
let packageJson: { name: string; description: string; version: string };
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Try two levels up first (global install case)
|
|
||||||
packagePath = join(__dirname, "..", "..", "package.json");
|
|
||||||
packageJson = JSON.parse(readFileSync(packagePath, "utf-8")) as {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
version: string;
|
|
||||||
};
|
|
||||||
} catch {
|
|
||||||
// Fall back to one level up (local dev case)
|
|
||||||
packagePath = join(__dirname, "..", "package.json");
|
|
||||||
packageJson = JSON.parse(readFileSync(packagePath, "utf-8")) as {
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
version: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Focus management types
|
// Focus management types
|
||||||
type FocusArea =
|
type FocusArea =
|
||||||
|
|
@ -310,6 +292,7 @@ function App({
|
||||||
);
|
);
|
||||||
const opts: InspectorClientOptions = {
|
const opts: InspectorClientOptions = {
|
||||||
environment,
|
environment,
|
||||||
|
clientIdentity: { name: TUI_CLIENT_NAME, version: APP_VERSION },
|
||||||
pipeStderr: true,
|
pipeStderr: true,
|
||||||
...(savedSettings &&
|
...(savedSettings &&
|
||||||
savedSettings.requestTimeout > 0 && {
|
savedSettings.requestTimeout > 0 && {
|
||||||
|
|
@ -1498,11 +1481,11 @@ function App({
|
||||||
>
|
>
|
||||||
<Box>
|
<Box>
|
||||||
<Text bold color="cyan">
|
<Text bold color="cyan">
|
||||||
{packageJson.name}
|
{APP_NAME}
|
||||||
</Text>
|
</Text>
|
||||||
<Text dimColor> - {packageJson.description}</Text>
|
<Text dimColor> - {APP_DESCRIPTION}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Text dimColor>v{packageJson.version}</Text>
|
<Text dimColor>v{APP_VERSION}</Text>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Main content area */}
|
{/* Main content area */}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@modelcontextprotocol/inspector-web",
|
"name": "@modelcontextprotocol/inspector-web",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.0.0",
|
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "build/index.js",
|
"main": "build/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|
|
||||||
69
clients/web/src/test/integration/node/version.test.ts
Normal file
69
clients/web/src/test/integration/node/version.test.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import {
|
||||||
|
mkdtempSync,
|
||||||
|
mkdirSync,
|
||||||
|
rmSync,
|
||||||
|
writeFileSync,
|
||||||
|
readFileSync,
|
||||||
|
} from "fs";
|
||||||
|
import { tmpdir } from "os";
|
||||||
|
import { join, dirname } from "path";
|
||||||
|
import { pathToFileURL, fileURLToPath } from "url";
|
||||||
|
import {
|
||||||
|
readInspectorVersion,
|
||||||
|
ROOT_PACKAGE_NAME,
|
||||||
|
} from "@inspector/core/node/version.js";
|
||||||
|
|
||||||
|
/** Absolute path to the repo root package.json (three levels above core/node). */
|
||||||
|
const rootPackageJsonPath = join(
|
||||||
|
dirname(fileURLToPath(import.meta.url)),
|
||||||
|
"..",
|
||||||
|
"..",
|
||||||
|
"..",
|
||||||
|
"..",
|
||||||
|
"..",
|
||||||
|
"..",
|
||||||
|
"package.json",
|
||||||
|
);
|
||||||
|
|
||||||
|
function readRootVersion(): string {
|
||||||
|
return (
|
||||||
|
JSON.parse(readFileSync(rootPackageJsonPath, "utf-8")) as {
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
).version;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("readInspectorVersion", () => {
|
||||||
|
it("resolves the version from the root package.json for a caller inside the repo", () => {
|
||||||
|
// This test file lives below the root manifest; walking up from it passes
|
||||||
|
// clients/web/package.json (wrong name → skipped) and several levels with no
|
||||||
|
// package.json (the catch branch) before reaching the root.
|
||||||
|
expect(readInspectorVersion(import.meta.url)).toBe(readRootVersion());
|
||||||
|
// Sanity: the root manifest really is the one we match on.
|
||||||
|
expect(ROOT_PACKAGE_NAME).toBe("@modelcontextprotocol/inspector");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("skips a matching-name manifest that has no version, then throws when no versioned root exists above the caller", () => {
|
||||||
|
// A fake root manifest with the right name but NO version must not satisfy
|
||||||
|
// the search — the walk keeps going up. Rooted in the OS temp dir (outside
|
||||||
|
// the repo), so the walk reaches the filesystem root without finding a
|
||||||
|
// versioned inspector manifest and throws.
|
||||||
|
const work = mkdtempSync(join(tmpdir(), "version-test-"));
|
||||||
|
try {
|
||||||
|
writeFileSync(
|
||||||
|
join(work, "package.json"),
|
||||||
|
JSON.stringify({ name: ROOT_PACKAGE_NAME }),
|
||||||
|
);
|
||||||
|
const sub = join(work, "sub");
|
||||||
|
mkdirSync(sub);
|
||||||
|
const callerUrl = pathToFileURL(join(sub, "caller.js")).href;
|
||||||
|
|
||||||
|
expect(() => readInspectorVersion(callerUrl)).toThrow(
|
||||||
|
/Could not locate the @modelcontextprotocol\/inspector root package\.json/,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
rmSync(work, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -109,6 +109,7 @@ export default defineConfig(({ command }) => {
|
||||||
path.join(repoRoot, 'core/auth/**/*.{ts,tsx}'),
|
path.join(repoRoot, 'core/auth/**/*.{ts,tsx}'),
|
||||||
path.join(repoRoot, 'core/storage/**/*.{ts,tsx}'),
|
path.join(repoRoot, 'core/storage/**/*.{ts,tsx}'),
|
||||||
path.join(repoRoot, 'core/logging/**/*.{ts,tsx}'),
|
path.join(repoRoot, 'core/logging/**/*.{ts,tsx}'),
|
||||||
|
path.join(repoRoot, 'core/node/**/*.{ts,tsx}'),
|
||||||
],
|
],
|
||||||
exclude: [
|
exclude: [
|
||||||
'**/*.stories.{ts,tsx}',
|
'**/*.stories.{ts,tsx}',
|
||||||
|
|
|
||||||
|
|
@ -26,11 +26,16 @@ export type {
|
||||||
AppRendererClient,
|
AppRendererClient,
|
||||||
} from "./types.js";
|
} from "./types.js";
|
||||||
import { getServerType as getServerTypeFromConfig } from "./config.js";
|
import { getServerType as getServerTypeFromConfig } from "./config.js";
|
||||||
// v2 doesn't have a core/package.json (the package isn't published independently),
|
// Fallback client identity, used ONLY when a caller doesn't pass
|
||||||
// so we hardcode the client identity that v1.5 read from corePackageJson.
|
// `clientIdentity`. Real clients supply their own: the Node clients (CLI, TUI)
|
||||||
|
// read the single-source version from the root package.json via
|
||||||
|
// `readInspectorVersion()`, and the web browser — which can't read the
|
||||||
|
// filesystem — will pass a version sourced from `GET /api/config` (see #1639).
|
||||||
|
// This stays a neutral placeholder rather than a hardcoded release number that
|
||||||
|
// would silently drift out of sync with the root package.json version.
|
||||||
const corePackageJson = {
|
const corePackageJson = {
|
||||||
name: "@modelcontextprotocol/inspector-core",
|
name: "mcp-inspector",
|
||||||
version: "0.20.0",
|
version: "0.0.0",
|
||||||
} as const;
|
} as const;
|
||||||
import type {
|
import type {
|
||||||
CreateTransport,
|
CreateTransport,
|
||||||
|
|
|
||||||
66
core/node/version.ts
Normal file
66
core/node/version.ts
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
/**
|
||||||
|
* Single source of truth for the Inspector's version, read from the **root**
|
||||||
|
* `package.json` of the published `@modelcontextprotocol/inspector` package.
|
||||||
|
*
|
||||||
|
* The Inspector ships as one package with one version number; the per-client
|
||||||
|
* `package.json`s deliberately carry no `version` field, so every Node client
|
||||||
|
* (CLI, TUI, and the web backend) resolves the version here instead of reading
|
||||||
|
* its own manifest.
|
||||||
|
*
|
||||||
|
* **Node-only** — this uses `node:fs`; never import it from browser code. The
|
||||||
|
* web *browser* cannot read the filesystem and gets its version from the
|
||||||
|
* backend via `GET /api/config` instead.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { dirname, join, parse } from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
/** The published root package's name — how we recognize the root manifest. */
|
||||||
|
export const ROOT_PACKAGE_NAME = "@modelcontextprotocol/inspector";
|
||||||
|
|
||||||
|
interface RootManifest {
|
||||||
|
name?: string;
|
||||||
|
version?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the Inspector version by walking up from the calling module to the
|
||||||
|
* root `package.json` (the one named {@link ROOT_PACKAGE_NAME}, which alone
|
||||||
|
* carries a `version`). Pass the caller's `import.meta.url`.
|
||||||
|
*
|
||||||
|
* This works identically in the dev tree and the published tarball: in both,
|
||||||
|
* a client entry (`clients/<name>/{src,build}/…`) sits below the root manifest,
|
||||||
|
* and npm always ships the root `package.json`. Client `package.json`s are
|
||||||
|
* skipped because they have no matching `name`/`version`, so a stray manifest
|
||||||
|
* on the way up never shadows the real root.
|
||||||
|
*
|
||||||
|
* @param callerUrl the calling module's `import.meta.url`
|
||||||
|
* @returns the version string from the root `package.json`
|
||||||
|
* @throws if no root manifest is found above the caller
|
||||||
|
*/
|
||||||
|
export function readInspectorVersion(callerUrl: string): string {
|
||||||
|
const startDir = dirname(fileURLToPath(callerUrl));
|
||||||
|
const { root } = parse(startDir);
|
||||||
|
|
||||||
|
let dir = startDir;
|
||||||
|
for (;;) {
|
||||||
|
let manifest: RootManifest | undefined;
|
||||||
|
try {
|
||||||
|
manifest = JSON.parse(
|
||||||
|
readFileSync(join(dir, "package.json"), "utf-8"),
|
||||||
|
) as RootManifest;
|
||||||
|
} catch {
|
||||||
|
// No (readable) package.json at this level — keep walking up.
|
||||||
|
}
|
||||||
|
if (manifest?.name === ROOT_PACKAGE_NAME && manifest.version) {
|
||||||
|
return manifest.version;
|
||||||
|
}
|
||||||
|
if (dir === root) break;
|
||||||
|
dir = dirname(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(
|
||||||
|
`Could not locate the ${ROOT_PACKAGE_NAME} root package.json above ${startDir}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -25,9 +25,7 @@
|
||||||
"clients/web/build",
|
"clients/web/build",
|
||||||
"clients/web/dist",
|
"clients/web/dist",
|
||||||
"clients/cli/build",
|
"clients/cli/build",
|
||||||
"clients/cli/package.json",
|
|
||||||
"clients/tui/build",
|
"clients/tui/build",
|
||||||
"clients/tui/package.json",
|
|
||||||
"scripts/install-clients.mjs"
|
"scripts/install-clients.mjs"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
|
|
@ -125,15 +125,14 @@ if (maps.length > 0) {
|
||||||
|
|
||||||
// 2b. Runtime files that are easy to omit from the packlist and only fail once
|
// 2b. Runtime files that are easy to omit from the packlist and only fail once
|
||||||
// installed: both web artifacts — the prod server runner (build) AND the SPA
|
// installed: both web artifacts — the prod server runner (build) AND the SPA
|
||||||
// (dist) — plus the TUI's package.json, which the TUI bundle reads at
|
// (dist). `clients/web/build` was previously dropped by the nested
|
||||||
// runtime for its header (name/description/version). `clients/web/build` was
|
// .gitignore. (The version the CLI/TUI report is read from the root
|
||||||
// previously dropped by the nested .gitignore; `clients/tui/package.json`
|
// package.json — always shipped — via readInspectorVersion(), so no client
|
||||||
// was never listed, so the installed TUI crashed on launch.
|
// package.json needs to ship; that read is exercised by driving the bin in
|
||||||
|
// step 4.)
|
||||||
for (const required of [
|
for (const required of [
|
||||||
"clients/web/build/index.js",
|
"clients/web/build/index.js",
|
||||||
"clients/web/dist/index.html",
|
"clients/web/dist/index.html",
|
||||||
"clients/cli/package.json",
|
|
||||||
"clients/tui/package.json",
|
|
||||||
]) {
|
]) {
|
||||||
if (!tarredPaths.includes(required)) {
|
if (!tarredPaths.includes(required)) {
|
||||||
fail(
|
fail(
|
||||||
|
|
@ -179,8 +178,6 @@ try {
|
||||||
join(installedPkg, "clients", "web", "build", "index.js"),
|
join(installedPkg, "clients", "web", "build", "index.js"),
|
||||||
join(installedPkg, "clients", "web", "dist", "index.html"),
|
join(installedPkg, "clients", "web", "dist", "index.html"),
|
||||||
join(installedPkg, "clients", "launcher", "build", "index.js"),
|
join(installedPkg, "clients", "launcher", "build", "index.js"),
|
||||||
join(installedPkg, "clients", "cli", "package.json"),
|
|
||||||
join(installedPkg, "clients", "tui", "package.json"),
|
|
||||||
]) {
|
]) {
|
||||||
if (!existsSync(required)) {
|
if (!existsSync(required)) {
|
||||||
fail(`expected installed file missing: ${required}`);
|
fail(`expected installed file missing: ${required}`);
|
||||||
|
|
|
||||||
BIN
specification/diagrams/shared-code-architecture.png
Normal file
BIN
specification/diagrams/shared-code-architecture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
Loading…
Reference in a new issue