Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,858 changes: 1,128 additions & 730 deletions package-lock.json

Large diffs are not rendered by default.

39 changes: 11 additions & 28 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,11 @@
"node": ">=20"
},
"dependencies": {
"@hono/node-server": "2.0.10",
"@hono/node-server": "2.1.1",
"@modelcontextprotocol/client": "^2.0.0",
"@modelcontextprotocol/core": "^2.0.0",
"@modelcontextprotocol/server": "^2.0.0",
"@modelcontextprotocol/sdk": "^1.30.0",
"@modelcontextprotocol/server": "^2.0.0",
"@napi-rs/keyring": "1.3.0",
"dotenv": "^17.4.2",
"hono": "4.13.7",
Expand All @@ -78,32 +78,20 @@
"@eslint/js": "^10.0.1",
"@modelcontextprotocol/node": "^2.0.0",
"@modelcontextprotocol/server-legacy": "^2.0.0",
"@types/node": "^22.15.30",
"@vitest/coverage-v8": "^3.2.7",
"esbuild": "0.28.1",
"eslint": "^10.8.0",
"@types/node": "^26.5.0",
"@vitest/coverage-v8": "^5.0.0",
"esbuild": "0.28.2",
"eslint": "^10.10.0",
"tsup": "^8.5.0",
"typescript": "^5.8.3",
"typescript-eslint": "^8.65.0",
"vitest": "^3.2.4"
"typescript-eslint": "^8.70.0",
"vite": "^8.0.0",
"vitest": "^5.0.0"
},
"overrides": {
"@hono/node-server": "2.0.10",
"@vitest/coverage-v8": {
"test-exclude": {
"glob": {
".": "13.0.6",
"minimatch": {
"brace-expansion": "5.0.9"
}
},
"minimatch": {
"brace-expansion": "5.0.9"
}
}
},
"@hono/node-server": "2.1.1",
"brace-expansion": "5.0.9",
"esbuild": "0.28.1",
"esbuild": "0.28.2",
"eslint": {
"minimatch": {
"brace-expansion": "5.0.9"
Expand All @@ -117,11 +105,6 @@
"minimatch": {
"brace-expansion": "5.0.9"
}
},
"vitest": {
"vite": {
"postcss": "8.5.23"
}
}
},
"license": "MIT",
Expand Down
3 changes: 2 additions & 1 deletion src/cli/doctor-report.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { randomUUID } from "node:crypto";
import type { Stats } from "node:fs";
import { stat } from "node:fs/promises";
import { SecretRedactor } from "../secrets/redact.js";
import type { UpstreamStartupDiagnostic } from "../upstream/startup-diagnostic.js";
Expand Down Expand Up @@ -297,7 +298,7 @@ export async function diagnosePathPermissions(target: PermissionTarget, path: st
};
}

let metadata: Awaited<ReturnType<typeof stat>>;
let metadata: Stats;
try {
metadata = await stat(path);
} catch {
Expand Down
39 changes: 20 additions & 19 deletions src/isolation/profile-runtime-isolation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createHash, randomUUID } from "node:crypto";
import type { Stats } from "node:fs";
import { chmod, lstat, mkdir, open, realpath, rename, rm, stat } from "node:fs/promises";
import { basename, dirname, isAbsolute, join, relative } from "node:path";
import type { ProfileIsolationConfig, ProfileIsolationContainerVolume, TransportType } from "../config/types.js";
Expand Down Expand Up @@ -211,7 +212,7 @@ export class ProfileRuntimeIsolation {
private async readSource(configDirectory: string, value: string): Promise<{ content: Buffer; text: string }> {
const segments = safeRelativeSegments(value);
let path = configDirectory;
let sourceEntry: Awaited<ReturnType<typeof lstat>> | undefined;
let sourceEntry: Stats | undefined;
for (const [index, segment] of segments.entries()) {
path = join(path, segment);
const entry = await lstat(path);
Expand Down Expand Up @@ -269,7 +270,7 @@ export class ProfileRuntimeIsolation {
await assertSafeReplacementTarget(destination, root, this.ownerUid);
const temporary = join(directory, `.${basename(fileName)}.${randomUUID()}.tmp`);
let handle: Awaited<ReturnType<typeof open>> | undefined;
let temporaryEntry: Awaited<ReturnType<typeof stat>> | undefined;
let temporaryEntry: Stats | undefined;
try {
handle = await open(temporary, "wx", 0o600);
await setHandleRestrictiveMode(handle, 0o600);
Expand Down Expand Up @@ -322,7 +323,7 @@ export async function buildContainerIsolationArguments(
const destinations = new Set<string>();
const generatedVolumes: Array<{
mount: string;
source: { path: string; entry: Awaited<ReturnType<typeof stat>> };
source: { path: string; entry: Stats };
sourceValue: string;
bindings: Array<{ name: string; destination: string }>;
}> = [];
Expand Down Expand Up @@ -442,8 +443,8 @@ function environmentValue(environment: Readonly<Record<string, string | undefine

async function verifyContainerRuntimeRoot(
root: string,
expected?: Awaited<ReturnType<typeof lstat>>
): Promise<{ path: string; entry: Awaited<ReturnType<typeof lstat>> }> {
expected?: Stats
): Promise<{ path: string; entry: Stats }> {
const entry = await lstat(root);
if (!entry.isDirectory() || entry.isSymbolicLink() || (expected !== undefined && !sameEntry(expected, entry))) {
throw isolationFailure();
Expand All @@ -460,11 +461,11 @@ async function verifyContainerRuntimeRoot(
async function resolveContainerVolumeSource(
root: string,
value: string,
expected?: Awaited<ReturnType<typeof stat>>
): Promise<{ path: string; entry: Awaited<ReturnType<typeof stat>> }> {
expected?: Stats
): Promise<{ path: string; entry: Stats }> {
const segments = safeContainerRelativeSegments(value);
let path = root;
let sourceEntry: Awaited<ReturnType<typeof lstat>> | undefined;
let sourceEntry: Stats | undefined;
for (const [index, segment] of segments.entries()) {
path = join(path, segment);
const entry = await lstat(path);
Expand Down Expand Up @@ -735,7 +736,7 @@ async function verifyOwnedDirectory(
directory: string,
boundary: string,
ownerUid: number | undefined
): Promise<{ path: string; entry: Awaited<ReturnType<typeof lstat>> }> {
): Promise<{ path: string; entry: Stats }> {
const entry = await lstat(directory);
if (!entry.isDirectory() || entry.isSymbolicLink() || !hasExpectedOwner(entry, ownerUid)) throw isolationFailure();
const canonicalDirectory = await realpath(directory);
Expand All @@ -755,9 +756,9 @@ async function verifyOwnedDirectory(
async function verifyRegularFile(
path: string,
boundary: string,
expected: Awaited<ReturnType<typeof stat>> | undefined,
expected: Stats | undefined,
ownerUid: number | undefined
): Promise<{ path: string; entry: Awaited<ReturnType<typeof stat>> }> {
): Promise<{ path: string; entry: Stats }> {
const entry = await lstat(path);
if (!entry.isFile() || entry.isSymbolicLink() || !hasExpectedOwner(entry, ownerUid)) throw isolationFailure();
const canonicalPath = await realpath(path);
Expand All @@ -779,8 +780,8 @@ async function verifyOpenedRegularFile(
path: string,
boundary: string,
ownerUid: number | undefined,
expected?: Awaited<ReturnType<typeof stat>>
): Promise<{ path: string; entry: Awaited<ReturnType<typeof stat>> }> {
expected?: Stats
): Promise<{ path: string; entry: Stats }> {
const opened = await handle.stat();
if (
!opened.isFile() ||
Expand All @@ -795,7 +796,7 @@ async function verifyOpenedRegularFile(
async function removeVerifiedTemporary(
path: string,
boundary: string,
expected: Awaited<ReturnType<typeof stat>>,
expected: Stats,
ownerUid: number | undefined
): Promise<void> {
try {
Expand Down Expand Up @@ -824,25 +825,25 @@ function isWithinOrSame(parent: string, child: string): boolean {
}

function sameEntry(
first: Pick<Awaited<ReturnType<typeof stat>>, "dev" | "ino">,
second: Pick<Awaited<ReturnType<typeof stat>>, "dev" | "ino">
first: Pick<Stats, "dev" | "ino">,
second: Pick<Stats, "dev" | "ino">
): boolean {
return first.dev === second.dev && first.ino === second.ino;
}

function hasExpectedOwner(entry: Pick<Awaited<ReturnType<typeof stat>>, "uid">, ownerUid: number | undefined): boolean {
function hasExpectedOwner(entry: Pick<Stats, "uid">, ownerUid: number | undefined): boolean {
return ownerUid === undefined || entry.uid === ownerUid;
}

function isTrustedSourceFile(
entry: Pick<Awaited<ReturnType<typeof stat>>, "isFile" | "mode" | "uid">,
entry: Pick<Stats, "isFile" | "mode" | "uid">,
ownerUid: number | undefined
): boolean {
return entry.isFile() && hasExpectedOwner(entry, ownerUid) && (Number(entry.mode) & 0o022) === 0;
}

function isTrustedSourceDirectory(
entry: Pick<Awaited<ReturnType<typeof stat>>, "isDirectory" | "mode" | "uid">,
entry: Pick<Stats, "isDirectory" | "mode" | "uid">,
ownerUid: number | undefined
): boolean {
return entry.isDirectory() && hasExpectedOwner(entry, ownerUid) && (Number(entry.mode) & 0o022) === 0;
Expand Down
3 changes: 2 additions & 1 deletion src/oauth/profile-rename-transaction.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { createHash, randomUUID } from "node:crypto";
import type { Stats } from "node:fs";
import { lstat, readFile, realpath, rm } from "node:fs/promises";
import { basename, dirname, resolve } from "node:path";
import {
Expand Down Expand Up @@ -338,7 +339,7 @@ export function oauthProfileRenameJournalPath(configPath: string): string {
export class FileOAuthProfileRenameJournalStore implements OAuthProfileRenameJournalStore {
async load(configPath: string): Promise<OAuthProfileRenameJournal | undefined> {
const path = journalPath(configPath);
let stats: Awaited<ReturnType<typeof lstat>>;
let stats: Stats;
try {
stats = await lstat(path);
} catch (error) {
Expand Down
2 changes: 1 addition & 1 deletion tests/mcp-v2-migration-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe("MCP TypeScript SDK v2 migration contract", () => {
expect(dependencies).not.toHaveProperty("@modelcontextprotocol/node");
expect(dependencies).not.toHaveProperty("@modelcontextprotocol/server-legacy");
expect(dependencies).toMatchObject({
"@hono/node-server": "2.0.10",
"@hono/node-server": "2.1.1",
"@modelcontextprotocol/client": "^2.0.0",
"@modelcontextprotocol/core": "^2.0.0",
"@modelcontextprotocol/server": "^2.0.0",
Expand Down
16 changes: 8 additions & 8 deletions tests/package-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ function assertPatchedEsbuildLockEntries(lock: PackageLock): void {

expect(esbuildEntries).not.toHaveLength(0);
for (const [packagePath, packageEntry] of esbuildEntries) {
expect(packageEntry["version"], `${packagePath} must resolve to the patched esbuild release`).toBe("0.28.1");
expect(packageEntry["version"], `${packagePath} must resolve to the patched esbuild release`).toBe("0.28.2");
}
}

Expand Down Expand Up @@ -154,7 +154,7 @@ function assertPatchedHonoNodeServerLockEntries(lock: PackageLock): void {

expect(entries, "@hono/node-server must exist in the package lock").not.toHaveLength(0);
for (const [packagePath, packageEntry] of entries) {
expect(packageEntry["version"], `${packagePath} must resolve to the patched release`).toBe("2.0.10");
expect(packageEntry["version"], `${packagePath} must resolve to the patched release`).toBe("2.1.1");
}
}

Expand Down Expand Up @@ -768,7 +768,7 @@ describe("package metadata contract", () => {
const manifest = readPackageManifest();
const lock = JSON.parse(readFileSync(new URL("../package-lock.json", import.meta.url), "utf8")) as PackageLock;

expect(manifest.overrides?.esbuild).toBe("0.28.1");
expect(manifest.overrides?.esbuild).toBe("0.28.2");
assertPatchedEsbuildLockEntries(lock);
});

Expand Down Expand Up @@ -804,7 +804,7 @@ describe("package metadata contract", () => {
const lock = JSON.parse(readFileSync(new URL("../package-lock.json", import.meta.url), "utf8")) as PackageLock;

expect(manifest.dependencies).toMatchObject({
"@hono/node-server": "2.0.10",
"@hono/node-server": "2.1.1",
"@modelcontextprotocol/client": "^2.0.0",
"@modelcontextprotocol/core": "^2.0.0",
"@modelcontextprotocol/server": "^2.0.0",
Expand All @@ -816,14 +816,14 @@ describe("package metadata contract", () => {
"@modelcontextprotocol/node": "^2.0.0",
"@modelcontextprotocol/server-legacy": "^2.0.0"
});
expect(manifest.overrides?.["@hono/node-server"]).toBe("2.0.10");
expect(manifest.overrides?.["@hono/node-server"]).toBe("2.1.1");
assertPatchedHonoNodeServerLockEntries(lock);
});

it("rejects stale nested esbuild lock entries", () => {
const lock: PackageLock = {
packages: {
"node_modules/esbuild": { version: "0.28.1" },
"node_modules/esbuild": { version: "0.28.2" },
"node_modules/vite/node_modules/esbuild": { version: "0.27.0" }
}
};
Expand Down Expand Up @@ -863,7 +863,7 @@ describe("package metadata contract", () => {
it("rejects stale nested Hono Node server lock entries", () => {
const lock: PackageLock = {
packages: {
"node_modules/@hono/node-server": { version: "2.0.10" },
"node_modules/@hono/node-server": { version: "2.1.1" },
"node_modules/@modelcontextprotocol/node/node_modules/@hono/node-server": { version: "1.19.9" }
}
};
Expand Down Expand Up @@ -1111,7 +1111,7 @@ describe("packed artifact contract", () => {
const installedHono = JSON.parse(
await readFile(join(directory, "node_modules", "hono", "package.json"), "utf8")
) as PackageManifest;
expect(installedHonoNodeServer.version).toBe("2.0.10");
expect(installedHonoNodeServer.version).toBe("2.1.1");
expect(installedHono.version).toBe("4.13.7");

const consumerPath = join(directory, "consumer.mjs");
Expand Down
35 changes: 16 additions & 19 deletions tests/release-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -169,30 +169,25 @@ describe("continuous integration workflow contract", () => {
const lockedPackages = readLockedPackages();

expect(overrides).toMatchObject({
"@hono/node-server": "2.0.10",
"@hono/node-server": "2.1.1",
"brace-expansion": "5.0.9",
"fast-uri": "3.1.7",
"ip-address": "10.3.1",
nanoid: "3.3.18",
qs: "6.16.0",
"@vitest/coverage-v8": {
"test-exclude": {
glob: {
".": "13.0.6",
minimatch: { "brace-expansion": "5.0.9" }
},
minimatch: { "brace-expansion": "5.0.9" }
}
},
eslint: { minimatch: { "brace-expansion": "5.0.9" } },
"typescript-eslint": { minimatch: { "brace-expansion": "5.0.9" } },
vitest: { vite: { postcss: "8.5.23" } }
"typescript-eslint": { minimatch: { "brace-expansion": "5.0.9" } }
});
// Vitest 5 dropped the test-exclude/glob chain and takes vite as a direct peer,
// so the former nested override paths no longer resolve to anything.
for (const name of ["@vitest/coverage-v8", "vitest"]) {
expect(overrides).not.toHaveProperty(name);
}
for (const name of ["glob", "postcss"]) {
expect(overrides).not.toHaveProperty(name);
}
expect(dependencies).toMatchObject({
"@hono/node-server": "2.0.10",
"@hono/node-server": "2.1.1",
hono: "4.13.7"
});
expect(developmentDependencies).not.toHaveProperty("@hono/node-server");
Expand All @@ -203,21 +198,23 @@ describe("continuous integration workflow contract", () => {
});
const fastUri = lockedPackages["node_modules/fast-uri"];
if (fastUri !== undefined) expect(fastUri).toMatchObject({ version: "3.1.7" });
expect(lockedPackages["node_modules/glob"]).toMatchObject({ version: "13.0.6", dev: true });
expect(lockedPackages["node_modules/glob"]).toBeUndefined();
expect(lockedPackages["node_modules/test-exclude"]).toBeUndefined();
expect(lockedPackages["node_modules/hono"]).toMatchObject({ version: "4.13.7" });
expect(lockedPackages["node_modules/qs"]).toMatchObject({ version: "6.16.0" });
expect(lockedPackages["node_modules/ip-address"]).toMatchObject({ version: "10.3.1" });
expect(lockedPackages["node_modules/nanoid"]).toMatchObject({ version: "3.3.18", dev: true });
expect(lockedPackages["node_modules/postcss"]).toMatchObject({ version: "8.5.23", dev: true });
expect(lockedPackages["node_modules/postcss"]).toMatchObject({ version: "8.5.28", dev: true });
expect(developmentDependencies).toHaveProperty("vite");
});

it("keeps process-backed files serial while replacing their isolated fork", () => {
it("keeps process-backed files serial while replacing their isolated worker", () => {
const config = readRepositoryFile("vitest.config.ts");

expect(config).toContain("fileParallelism: false");
expect(config).toMatch(
/poolOptions:\s*\{\s*forks:\s*\{\s*singleFork:\s*false,\s*isolate:\s*true\s*\}\s*\}/u
);
// Vitest 4 removed `test.poolOptions` and promoted its contents to top-level options.
expect(config).toMatch(/^\s*isolate: true,$/mu);
expect(config).not.toMatch(/^\s*poolOptions:/mu);
});
});

Expand Down
Loading