Skip to content

Commit d95d3b9

Browse files
chore: update Dockerfile and CI configuration for Node 20, enhance pa… (#112)
* chore: update Dockerfile and CI configuration for Node 20, enhance package structure - Updated Dockerfile to use Node 20 for both build and runtime stages. - Modified GitHub Actions workflow to support testing across Node versions 20.x, 22.x, and 24.x. - Enhanced package.json to include new module exports and updated dependencies. - Introduced tsconfig.cjs.json for CommonJS builds and adjusted TypeScript configurations. - Added new scripts for CJS package JSON generation and updated test scripts for improved coverage. - Refactored source code structure, including the introduction of an API entry point and integration tests for ESM and CJS compatibility. * refactor: simplify integration test and enhance action descriptions - Updated integration test to always run for package consumers, removing the dual build check. - Reformatted action descriptions in the server registration to improve readability and maintain consistency across tools. * refactor: update schema handling in action tools and improve integration test timeout - Changed action tools to directly use the zodSchema instead of its shape for schema definition. - Enhanced the integration test timeout for the packaged CLI to ensure reliability during execution. * chore: enhance integration tests for packaged CLI and add published ESM test - Updated the GitHub Actions workflow to clarify test coverage for workspace ESM/CJS and added a new test for npm pack and import behavior. - Introduced new integration tests for packaged CLI functionality, ensuring proper execution of the `bin` field and `npx` behavior. - Added a test for resolving the package name `@currents/mcp` using `package.json` exports after installation from a tarball. - Included a fixture to validate the ESM entry point and its functionality in a consumer project context. * fix: improve error handling for missing CURRENTS_API_KEY and update integration tests - Enhanced error logging in the main entry point to provide clearer guidance when the CURRENTS_API_KEY is not set. - Updated the server startup logic to throw an error if the CURRENTS_API_KEY is missing, improving robustness. - Added a default value for CURRENTS_API_KEY in the environment module to prevent empty strings. - Modified integration tests to include a mock environment variable for CURRENTS_API_KEY, ensuring proper test execution. --------- Co-authored-by: miguelangarano <miguelangarano@gmail.com>
1 parent c6a529f commit d95d3b9

50 files changed

Lines changed: 805 additions & 357 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/test.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ on:
88

99
jobs:
1010
test:
11-
name: Run Unit Tests
11+
name: Test (Node ${{ matrix.node-version }})
1212
runs-on: ubuntu-latest
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
node-version: [20.x, 22.x, 24.x]
1317

1418
steps:
1519
- name: Checkout code
@@ -18,7 +22,7 @@ jobs:
1822
- name: Setup Node.js
1923
uses: actions/setup-node@v4
2024
with:
21-
node-version: 24.x
25+
node-version: ${{ matrix.node-version }}
2226
cache: "npm"
2327
cache-dependency-path: mcp-server/package-lock.json
2428

Dockerfile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,23 @@
22
# syntax=docker/dockerfile:1
33

44
# Build stage
5-
FROM node:lts-alpine AS build
5+
FROM node:20-alpine AS build
66
WORKDIR /app
77

88
# Install dependencies
99
COPY mcp-server/package.json mcp-server/package-lock.json ./
1010
RUN npm ci --production=false
1111

1212
# Copy source code
13-
COPY mcp-server/tsconfig.json ./tsconfig.json
13+
COPY mcp-server/tsconfig.json mcp-server/tsconfig.cjs.json ./
14+
COPY mcp-server/scripts ./scripts
1415
COPY mcp-server/src ./src
1516

1617
# Build the project
1718
RUN npm run build
1819

1920
# Runtime stage
20-
FROM node:lts-alpine AS runtime
21+
FROM node:20-alpine AS runtime
2122
WORKDIR /app
2223

2324
# Copy build artifacts and dependencies

mcp-server/package-lock.json

Lines changed: 32 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mcp-server/package.json

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,26 @@
1515
"bin": "./build/index.js",
1616
"version": "2.2.18",
1717
"description": "Currents MCP server",
18-
"main": "index.js",
18+
"main": "./build/cjs/api.js",
19+
"module": "./build/api.js",
20+
"types": "./build/api.d.ts",
21+
"exports": {
22+
".": {
23+
"types": "./build/api.d.ts",
24+
"import": "./build/api.js",
25+
"require": "./build/cjs/api.js"
26+
}
27+
},
1928
"scripts": {
20-
"build": "tsc && chmod 755 build/index.js",
29+
"build": "tsc && tsc -p tsconfig.cjs.json && node ./scripts/write-cjs-package-json.mjs && chmod 755 build/index.js",
2130
"publish:mcp": "npm run publish:npm",
2231
"publish:npm": "npm run rm && npm run build && ./publish.cjs",
2332
"start": "node build/index.js",
2433
"rm": "rimraf dist",
2534
"test": "vitest",
2635
"test:ui": "vitest --ui",
27-
"test:run": "vitest run",
28-
"test:coverage": "vitest run --coverage",
36+
"test:run": "npm run build && vitest run",
37+
"test:coverage": "npm run build && vitest run --coverage",
2938
"release": "release-it",
3039
"release:dry": "release-it --dry-run"
3140
},
@@ -36,14 +45,18 @@
3645
"url": "https://github.com/currents-dev/currents-mcp.git"
3746
},
3847
"license": "Apache-2.0",
48+
"engines": {
49+
"node": ">=20"
50+
},
3951
"dependencies": {
40-
"@modelcontextprotocol/sdk": "^1.25.2",
52+
"@modelcontextprotocol/sdk": "^1.28.0",
4153
"commander": "^12.1.0",
4254
"pino": "^9.9.5",
4355
"pino-pretty": "^13.1.1",
4456
"zod": "^4.3.5"
4557
},
4658
"devDependencies": {
59+
"@types/node": "^20.19.0",
4760
"@release-it/conventional-changelog": "^10.0.0",
4861
"release-it": "^19.0.3",
4962
"rimraf": "^6.0.1",
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { mkdirSync, writeFileSync } from "node:fs";
2+
import { join, dirname } from "node:path";
3+
import { fileURLToPath } from "node:url";
4+
5+
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
6+
const cjsDir = join(root, "build", "cjs");
7+
mkdirSync(cjsDir, { recursive: true });
8+
writeFileSync(join(cjsDir, "package.json"), `${JSON.stringify({ type: "commonjs" })}\n`);

mcp-server/src/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { startMcpServer } from "./server.js";
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* Packaged **CLI** integration tests: the `bin` field and `npx` behavior for a
3+
* tarball shaped like a publish (not the programmatic `exports` entry; see
4+
* `package-published-esm.integration.test.ts` for `import "@currents/mcp"`).
5+
*
6+
7+
* 1. Prerequisite: `build/index.js` exists (`npm run test:run` runs `build`
8+
* first). If missing, the suite is skipped so `vitest` without a prior
9+
* build does not fail noisily.
10+
* 2. `packTarball`: `npm pack` from the package root → one `.tgz` under a
11+
* temp dir. Contents follow `package.json` `files` and npm’s pack rules
12+
* (same artifact shape as registry install, minus release-only publish.cjs
13+
* mutations).
14+
*/
15+
import { execFileSync, spawnSync } from "node:child_process";
16+
import { existsSync, mkdtempSync, readdirSync } from "node:fs";
17+
import { tmpdir } from "node:os";
18+
import path from "node:path";
19+
import { fileURLToPath } from "node:url";
20+
import { describe, expect, it } from "vitest";
21+
22+
const root = fileURLToPath(new URL("..", import.meta.url));
23+
const buildIndex = path.join(root, "build", "index.js");
24+
25+
function packTarball(packDest: string): string {
26+
// Respect `files` and standard pack rules; do not mutate package.json (unlike release `publish.cjs`).
27+
execFileSync("npm", ["pack", "--pack-destination", packDest], {
28+
cwd: root,
29+
stdio: ["ignore", "pipe", "pipe"],
30+
});
31+
const tgz = readdirSync(packDest).filter((f) => f.endsWith(".tgz"));
32+
if (tgz.length !== 1) {
33+
throw new Error(`expected one .tgz in ${packDest}, got: ${tgz.join(", ")}`);
34+
}
35+
return path.join(packDest, tgz[0]);
36+
}
37+
38+
describe.skipIf(!existsSync(buildIndex))(
39+
"packaged CLI (npx / bin)",
40+
{ timeout: 60_000 },
41+
() => {
42+
/**
43+
* `npx -y --package <abs-path-to.tgz> mcp`: npm treats the tarball as the
44+
* package to install transiently; `mcp` is the bin name from that package’s
45+
* `package.json` `bin` map (not the scoped package name). The child should
46+
* start the MCP server and log the “live” line (stdio MCP servers run until
47+
* stdin closes; we cap wall time with `spawnSync` timeout). Accept either
48+
* that log line or process timeout as success so slow CI still passes.
49+
* */
50+
it("starts via npx --package tarball mcp", () => {
51+
const packDir = mkdtempSync(path.join(tmpdir(), "mcp-pack-"));
52+
const tarball = packTarball(packDir);
53+
const r = spawnSync("npx", ["-y", "--package", tarball, "mcp"], {
54+
cwd: packDir,
55+
timeout: 45_000,
56+
encoding: "utf-8",
57+
stdio: ["pipe", "pipe", "pipe"],
58+
env: {
59+
...process.env,
60+
// Required for server startup; value is unused in this smoke test.
61+
CURRENTS_API_KEY: "vitest-cli-pack-smoke",
62+
},
63+
});
64+
const combined = `${r.stdout ?? ""}${r.stderr ?? ""}`;
65+
const timedOut =
66+
r.error != null && "code" in r.error && r.error.code === "ETIMEDOUT";
67+
expect(combined.includes("Currents MCP Server is live") || timedOut).toBe(
68+
true
69+
);
70+
});
71+
72+
/*
73+
* Second `it` — consumer project + `node_modules/.bin`:
74+
* - `npm init -y` and `npm install <tgz>` in a fresh temp project. npm links
75+
* `node_modules/.bin/mcp` (or `mcp.cmd` on Windows) to the packed CLI.
76+
* - Assert the shim exists. This catches broken `bin`, wrong `files` (missing
77+
* `build/index.js`), or install layout issues without spawning the server.
78+
* */
79+
it("exposes mcp bin after npm install from tarball", () => {
80+
const packDir = mkdtempSync(path.join(tmpdir(), "mcp-pack-"));
81+
const installDir = mkdtempSync(path.join(tmpdir(), "mcp-install-"));
82+
const tarball = packTarball(packDir);
83+
execFileSync("npm", ["init", "-y"], {
84+
cwd: installDir,
85+
stdio: "ignore",
86+
});
87+
execFileSync("npm", ["install", tarball], {
88+
cwd: installDir,
89+
stdio: "ignore",
90+
});
91+
const binDir = path.join(installDir, "node_modules", ".bin");
92+
const hasMcp =
93+
existsSync(path.join(binDir, "mcp")) ||
94+
existsSync(path.join(binDir, "mcp.cmd"));
95+
expect(hasMcp).toBe(true);
96+
});
97+
}
98+
);

0 commit comments

Comments
 (0)