build: move mcp to pnpm and delete the second lockfile

This repo committed BOTH package-lock.json and pnpm-lock.yaml — two descriptions of
one dependency tree, neither reconciled against the other. CI then ran
`npm ci || npm install`, which is the shape that lets that persist: when the lockfile
stops matching package.json, the fallback silently resolves a different tree and the
run still goes green. A lockfile nobody fails on is not a lockfile.

Now there is one. pnpm-lock.yaml is tracked, package-lock.json is deleted and
ignored along with every other manager's, corepack takes the pnpm version from
`packageManager`, and Docker and CI both install with --frozen-lockfile so a drifted
tree stops the build instead of being papered over.

The `pnpm.onlyBuiltDependencies` field in package.json was dead — pnpm 11 reads
settings from pnpm-workspace.yaml and prints a warning that it ignored it. Its five
entries moved there verbatim, documented, so the build-script denial is actually in
effect rather than merely written down.

esbuild goes ^0.27.4 -> ^0.28.1, the version console already builds with. 0.27.4's
install script overwrites its own bin/esbuild with a raw ELF, and pnpm's shim reads
a missing shebang as "run this with node", so `pnpm build` died on
`SyntaxError: Invalid or unexpected token` at the first byte. Same minor series, and
it is what makes the build work under pnpm at all.

Verified: frozen-lockfile install clean, `pnpm build` emits a 617KB bundle, 354
tests passing.

Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
2026-07-27 08:24:23 -07:00
co-authored by Hanzo Dev
parent 5a5e017d26
commit b33bcad727
6 changed files with 329 additions and 8685 deletions
+6
View File
@@ -52,3 +52,9 @@ GEMINI.md
QWEN.md
# Generated site
site/
# pnpm is the one package manager here — pnpm-lock.yaml is the tracked lockfile.
# A lockfile from any other manager is a second source of truth that silently drifts.
package-lock.json
yarn.lock
bun.lockb
+5 -5
View File
@@ -28,10 +28,10 @@ COPY --chown=mcp:nodejs package.json package-lock.json* ./
USER mcp
# Install dependencies
RUN npm ci --only=production && npm cache clean --force
RUN corepack enable && pnpm install --prod --frozen-lockfile && pnpm store prune
# Install dev dependencies for building
RUN npm ci
RUN corepack enable && pnpm install --frozen-lockfile
# ===== Builder Stage =====
FROM deps AS builder
@@ -40,7 +40,7 @@ FROM deps AS builder
COPY --chown=mcp:nodejs . .
# Build the application
RUN npm run build
RUN pnpm build
# ===== Development Stage =====
FROM deps AS development
@@ -52,7 +52,7 @@ COPY --chown=mcp:nodejs . .
ENV NODE_ENV=development
# Development command with hot reload
CMD ["npm", "run", "dev"]
CMD ["pnpm", "dev"]
# ===== Production Stage =====
FROM base AS production
@@ -64,7 +64,7 @@ COPY --chown=mcp:nodejs package.json package-lock.json* ./
USER mcp
# Install only production dependencies
RUN npm ci --only=production && npm cache clean --force
RUN corepack enable && pnpm install --prod --frozen-lockfile && pnpm store prune
# Copy built application from builder
COPY --from=builder --chown=mcp:nodejs /app/dist ./dist
-8382
View File
File diff suppressed because it is too large Load Diff
+5 -13
View File
@@ -1,6 +1,7 @@
{
"name": "@hanzo/mcp",
"version": "2.4.4",
"packageManager": "[email protected]",
"description": "Hanzo MCP Server - Unified tool surface with AI, cloud control, vector search, and multi-framework UI",
"main": "dist/index.js",
"type": "module",
@@ -17,14 +18,14 @@
"LICENSE"
],
"scripts": {
"build": "npm run build:lib && npm run build:cli",
"build": "pnpm build:lib && pnpm build:cli",
"build:lib": "esbuild src/index.ts --bundle --platform=node --target=node18 --format=esm --outfile=dist/index.js --external:@modelcontextprotocol/sdk --external:glob --external:commander --external:zod --external:zod-to-json-schema --external:mime-types --external:@lancedb/lancedb --external:@xenova/transformers --external:jsautogui --external:playwright --external:playwright-core --external:ws --external:path --external:fs --external:fs/promises --external:os --external:url",
"build:cli": "esbuild src/cli.ts --bundle --platform=node --target=node18 --format=esm --outfile=dist/cli.js --external:@modelcontextprotocol/sdk --external:glob --external:commander --external:zod --external:zod-to-json-schema --external:mime-types --external:@lancedb/lancedb --external:@xenova/transformers --external:jsautogui --external:playwright --external:playwright-core --external:ws --external:path --external:fs --external:fs/promises --external:os --external:url && chmod +x dist/cli.js",
"dev": "tsc --watch",
"smoke": "node scripts/smoke-mcp.mjs",
"test": "jest",
"lint": "echo 'No linter configured'",
"prepublishOnly": "npm run build"
"prepublishOnly": "pnpm build"
},
"keywords": [
"mcp",
@@ -61,7 +62,7 @@
"@types/minimatch": "^6.0.0",
"@types/node": "^25.5.0",
"@types/ws": "^8.18.1",
"esbuild": "^0.27.4",
"esbuild": "^0.28.1",
"jest": "^30.3.0",
"ts-jest": "^29.4.6",
"typescript": "^5.9.3"
@@ -76,14 +77,5 @@
"homepage": "https://hanzo.ai",
"bugs": {
"url": "https://github.com/hanzoai/mcp/issues"
},
"pnpm": {
"onlyBuiltDependencies": [
"esbuild",
"jsautogui",
"protobufjs",
"sharp",
"unrs-resolver"
]
}
}
}
+288 -285
View File
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
# pnpm settings for this package. There is no workspace here — mcp is one package —
# but pnpm reads its settings from this file, so this is where they live.
# pnpm does not run a dependency's install scripts unless it is named here. Each of
# these ships a native binary or generates code at install time and does not work
# without doing so: esbuild is what `pnpm build` invokes to bundle, sharp and
# jsautogui are native addons, protobufjs generates its runtime, and unrs-resolver
# unpacks a platform binary. Anything not listed stays inert, which is the point — a
# new transitive dependency cannot run code at install time just by arriving.
allowBuilds:
esbuild: true
jsautogui: true
protobufjs: true
sharp: true
unrs-resolver: true
# pnpm refuses packages published in the last few days, so a compromised release has
# to survive a cooling-off period before it can reach a build. Keep that for the
# registry at large; exclude our own scopes, which we publish and consume the same
# hour. Excluding by SCOPE rather than by version matters: pnpm rewrites a
# per-version entry on every bump, and a file that rewrites itself during install
# makes --frozen-lockfile fail in CI.
minimumReleaseAgeExclude:
- '@hanzo/*'
- '@hanzogui/*'