@conorbronsdon/substack-mcp
Advanced tools
+30
-1
@@ -9,4 +9,33 @@ # Changelog | ||
| ## [Unreleased] | ||
| ## [0.6.2] - 2026-08-03 | ||
| ### Added | ||
| - A 30-second deadline on every request to Substack, overridable with | ||
| `SUBSTACK_REQUEST_TIMEOUT_MS`. Node applies no request timeout by default — | ||
| only undici's 10s *connect* timeout — so a host that accepted the connection | ||
| and then went silent could hang a tool call indefinitely. A request that hits | ||
| the deadline now raises `TimeoutError`, which names the endpoint and the | ||
| limit, instead of surfacing a bare `DOMException`. (#31) | ||
| - `SIGTERM`/`SIGINT` handlers that close the transport and exit 0. Node installs | ||
| no handler by default and the kernel ignores default-disposition signals for | ||
| PID 1, so `docker stop` waited out its full 10s grace period and then | ||
| SIGKILLed (exit 137). Measured against the published Dockerfile: 10,334ms and | ||
| exit 137 before, 357ms and exit 0 after. No init process (`tini`) needed. (#31) | ||
| ### Fixed | ||
| - The startup auth check no longer blocks the MCP handshake. It ran *before* | ||
| `server.connect()`, so a host that hangs rather than refuses stalled | ||
| `initialize` for undici's full connect timeout with no output. Measured in a | ||
| container against a blackholed host: `initialize` took 11,182ms before, 822ms | ||
| after; the credential-free path (what the Docker MCP registry check exercises) | ||
| is unchanged at 14 tools and exit 0. The check still runs — it just runs after | ||
| connect, and it only ever warned. (#31) | ||
| - `tsconfig.json` now excludes `src/__tests__` from the build. `tsc` was emitting | ||
| the test suite into `dist/`, where its `vitest` imports cannot resolve because | ||
| `npm ci --omit=dev` correctly drops vitest — dead code in the published | ||
| package, and a stale `dist/` also made `vitest run` collect every suite twice. | ||
| The Dockerfile's `RUN rm -rf dist/__tests__` workaround is removed with it. | ||
| Tests are still type-checked: `npm run lint` now uses `tsconfig.lint.json`, | ||
| which covers all of `src/`. (#31) | ||
| ## [0.6.1] - 2026-08-03 | ||
@@ -13,0 +42,0 @@ |
+2
-2
@@ -24,4 +24,4 @@ # substack-mcp | ||
| npm ci | ||
| npm run lint # tsc --noEmit (type-check) | ||
| npm run build # tsc (outputs to dist/) | ||
| npm run lint # tsc --noEmit -p tsconfig.lint.json (type-checks all of src/, tests included) | ||
| npm run build # tsc (outputs to dist/; excludes src/__tests__) | ||
| npm test # vitest run | ||
@@ -28,0 +28,0 @@ ``` |
+4
-1
@@ -7,3 +7,6 @@ # Contributing | ||
| - `npm run lint` passes (this runs `tsc --noEmit` — the type checker is the linter). | ||
| - `npm run lint` passes (this runs `tsc --noEmit -p tsconfig.lint.json` — the type | ||
| checker is the linter). It covers `src/__tests__` too, which the build config | ||
| excludes so `tsc` doesn't emit tests into `dist/`; nothing else type-checks | ||
| them, since vitest strips types rather than checking them. | ||
| - `npm test` passes (`vitest run`). Add or update tests for any behavior you change. | ||
@@ -10,0 +13,0 @@ - The safe-by-design boundary stays: no publish, no delete, no schedule for |
+13
-1
| import { SubstackPost, SubstackDraft, SubstackComment, SubstackNote, NoteCreatePayload, NoteAttachment, DraftUpdatePayload, ImageUploadResult, SubstackSection, SubstackScheduledPost } from "./types.js"; | ||
| /** | ||
| * Per-request deadline applied to every outbound fetch. | ||
| * | ||
| * Node's default is no request timeout at all — only undici's 10s *connect* | ||
| * timeout — so a host that accepts the connection and then goes quiet hangs the | ||
| * call forever. 30s is long enough for a base64 image upload on a slow link and | ||
| * short enough that a dropped connection becomes an error instead of a stall. | ||
| * Override per client via the constructor (SUBSTACK_REQUEST_TIMEOUT_MS at the | ||
| * entry point). | ||
| */ | ||
| export declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000; | ||
| /** | ||
| * Largest `limit` Substack's post_management endpoints will accept. | ||
@@ -36,3 +47,4 @@ * | ||
| private userAgent; | ||
| constructor(publicationUrl: string, sessionToken: string, userId: string, userAgent?: string); | ||
| private timeoutMs; | ||
| constructor(publicationUrl: string, sessionToken: string, userId: string, userAgent?: string, timeoutMs?: number); | ||
| private request; | ||
@@ -39,0 +51,0 @@ validateAuth(): Promise<{ |
+38
-7
@@ -1,3 +0,14 @@ | ||
| import { mapHttpStatusToError, extractErrorDetail } from "../utils/errors.js"; | ||
| import { mapHttpStatusToError, extractErrorDetail, TimeoutError, isAbortError } from "../utils/errors.js"; | ||
| /** | ||
| * Per-request deadline applied to every outbound fetch. | ||
| * | ||
| * Node's default is no request timeout at all — only undici's 10s *connect* | ||
| * timeout — so a host that accepts the connection and then goes quiet hangs the | ||
| * call forever. 30s is long enough for a base64 image upload on a slow link and | ||
| * short enough that a dropped connection becomes an error instead of a stall. | ||
| * Override per client via the constructor (SUBSTACK_REQUEST_TIMEOUT_MS at the | ||
| * entry point). | ||
| */ | ||
| export const DEFAULT_REQUEST_TIMEOUT_MS = 30_000; | ||
| /** | ||
| * Largest `limit` Substack's post_management endpoints will accept. | ||
@@ -46,3 +57,4 @@ * | ||
| userAgent; | ||
| constructor(publicationUrl, sessionToken, userId, userAgent) { | ||
| timeoutMs; | ||
| constructor(publicationUrl, sessionToken, userId, userAgent, timeoutMs) { | ||
| this.publicationUrl = publicationUrl.replace(/\/$/, ""); | ||
@@ -60,2 +72,9 @@ // Substack uses connect.sid on custom domains, substack.sid on substack.com | ||
| "(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36"; | ||
| // A zero, negative, or non-finite override would abort every request | ||
| // instantly (or never), so it falls back to the default rather than | ||
| // silently breaking the client. | ||
| this.timeoutMs = | ||
| typeof timeoutMs === "number" && Number.isFinite(timeoutMs) && timeoutMs > 0 | ||
| ? timeoutMs | ||
| : DEFAULT_REQUEST_TIMEOUT_MS; | ||
| if (isNaN(this.userId)) { | ||
@@ -83,7 +102,18 @@ throw new Error(`Invalid SUBSTACK_USER_ID: "${userId}" — must be a number`); | ||
| } | ||
| const response = await fetch(url, { | ||
| ...options, | ||
| headers, | ||
| redirect: "follow", | ||
| }); | ||
| // The signal is set last so it can't be overridden by `options`: every | ||
| // request through this method is bounded, no exceptions. | ||
| let response; | ||
| try { | ||
| response = await fetch(url, { | ||
| ...options, | ||
| headers, | ||
| redirect: "follow", | ||
| signal: AbortSignal.timeout(this.timeoutMs), | ||
| }); | ||
| } | ||
| catch (err) { | ||
| if (isAbortError(err)) | ||
| throw new TimeoutError(url, this.timeoutMs); | ||
| throw err; | ||
| } | ||
| if (!response.ok) { | ||
@@ -160,2 +190,3 @@ const body = await response.text().catch(() => "unknown error"); | ||
| redirect: "follow", | ||
| signal: AbortSignal.timeout(this.timeoutMs), | ||
| }); | ||
@@ -162,0 +193,0 @@ if (!res.ok) |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/api/client.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAE1G;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,MAAM,CAAC;AAEjD;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,EAAE,CAAC;AAEhC,2DAA2D;AAC3D,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAEtC;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,aAAa,GAAG,mBAAmB,CAAC;AAExE;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;IAC1E,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACxC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC9F,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC;IACnC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC1B,CAAC;AAED,MAAM,OAAO,cAAc;IACjB,cAAc,CAAS;IACvB,MAAM,CAAS;IACf,MAAM,CAAS;IACf,SAAS,CAAS;IAClB,SAAS,CAAS;IAE1B,YACE,cAAsB,EACtB,YAAoB,EACpB,MAAc,EACd,SAAkB,EAClB,SAAkB;QAElB,IAAI,CAAC,cAAc,GAAG,cAAc,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACxD,4EAA4E;QAC5E,IAAI,CAAC,MAAM,GAAG,eAAe,YAAY,kBAAkB,YAAY,GAAG,CAAC;QAC3E,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACnC,yEAAyE;QACzE,wEAAwE;QACxE,4EAA4E;QAC5E,8CAA8C;QAC9C,IAAI,CAAC,SAAS;YACZ,SAAS;gBACT,qEAAqE;oBACnE,oDAAoD,CAAC;QACzD,qEAAqE;QACrE,oEAAoE;QACpE,gCAAgC;QAChC,IAAI,CAAC,SAAS;YACZ,OAAO,SAAS,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,SAAS,GAAG,CAAC;gBAC1E,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC,0BAA0B,CAAC;QAEjC,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,8BAA8B,MAAM,sBAAsB,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,OAAO,CACnB,GAAW,EACX,UAAuB,EAAE;QAEzB,MAAM,OAAO,GAA2B;YACtC,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,YAAY,EAAE,IAAI,CAAC,SAAS;YAC5B,MAAM,EAAE,mCAAmC;YAC3C,yEAAyE;YACzE,0EAA0E;YAC1E,mEAAmE;YACnE,mCAAmC;YACnC,OAAO,EAAE,GAAG,IAAI,CAAC,cAAc,eAAe;YAC9C,GAAG,CAAC,OAAO,CAAC,OAAiC,IAAI,EAAE,CAAC;SACrD,CAAC;QAEF,sEAAsE;QACtE,uEAAuE;QACvE,sEAAsE;QACtE,IAAI,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC,cAAc,IAAI,OAAO,CAAC,EAAE,CAAC;YACjD,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;QAC/C,CAAC;QAED,uEAAuE;QACvE,yDAAyD;QACzD,IAAI,QAAkB,CAAC;QACvB,IAAI,CAAC;YACH,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;gBAC1B,GAAG,OAAO;gBACV,OAAO;gBACP,QAAQ,EAAE,QAAQ;gBAClB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC;aAC5C,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,YAAY,CAAC,GAAG,CAAC;gBAAE,MAAM,IAAI,YAAY,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;YACnE,MAAM,GAAG,CAAC;QACZ,CAAC;QAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,CAAC;YAChE,MAAM,MAAM,GAAG,kBAAkB,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;YACzD,MAAM,oBAAoB,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;QAC3D,CAAC;QAED,OAAO,QAAQ,CAAC,IAAI,EAAgB,CAAC;IACvC,CAAC;IAED,KAAK,CAAC,YAAY;QAChB,gEAAgE;QAChE,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAC1C,kDAAkD;QAClD,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7C,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,eAAe,EAAE,CAAC;IAClE,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,KAAK,CAAC,kBAAkB;QAKtB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAC7B,GAAG,IAAI,CAAC,cAAc,sCAAsC,CAC7D,CAAC;YACF,+CAA+C;YAC/C,IAAI,OAAO,IAAI,CAAC,gBAAgB,KAAK,QAAQ,EAAE,CAAC;gBAC9C,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,gBAAgB,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,uCAAuC,EAAE,CAAC;YAC7G,CAAC;YACD,IAAI,OAAO,IAAI,CAAC,eAAe,KAAK,QAAQ,EAAE,CAAC;gBAC7C,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,eAAe,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,uCAAuC,EAAE,CAAC;YAC5G,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,mCAAmC;QACrC,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,yBAAyB,EAAE,CAAC;QACvD,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,OAAO;gBACL,KAAK,EAAE,OAAO;gBACd,SAAS,EAAE,aAAa;gBACxB,IAAI,EACF,kEAAkE,OAAO,cAAc;oBACvF,2BAA2B,GAAG,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,GAAG,sBAAsB;oBACtF,oFAAoF;aACvF,CAAC;QACJ,CAAC;QAED,OAAO;YACL,KAAK,EAAE,CAAC,CAAC;YACT,SAAS,EAAE,aAAa;YACxB,IAAI,EAAE,qEAAqE;SAC5E,CAAC;IACJ,CAAC;IAED;;;OAGG;IACK,KAAK,CAAC,yBAAyB;QACrC,IAAI,IAAY,CAAC;QACjB,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,cAAc,GAAG,EAAE;gBACjD,OAAO,EAAE,EAAE,YAAY,EAAE,IAAI,CAAC,SAAS,EAAE;gBACzC,QAAQ,EAAE,QAAQ;gBAClB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC;aAC5C,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,OAAO,IAAI,CAAC;YACzB,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;QAED,2DAA2D;QAC3D,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAE7C,MAAM,QAAQ,GAAG,UAAU,CAAC,KAAK,CAAC,0CAA0C,CAAC,CAAC;QAC9E,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC;YACtD,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;gBAAE,OAAO,CAAC,CAAC;QAC5C,CAAC;QAED,0DAA0D;QAC1D,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,uDAAuD,CAAC,CAAC;QACtF,IAAI,GAAG,EAAE,CAAC;YACR,MAAM,CAAC,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YACjC,IAAI,CAAC,KAAK,IAAI;gBAAE,OAAO,CAAC,CAAC;QAC3B,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,iBAAiB,CACrB,MAAM,GAAG,CAAC,EACV,KAAK,GAAG,EAAE;QAEV,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAC7B,GAAG,IAAI,CAAC,cAAc,4CAA4C,MAAM,UAAU,KAAK,0CAA0C,CAClI,CAAC;QACF,OAAO,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;IAC7D,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,KAAK,GAAG,EAAE;QACpC,0EAA0E;QAC1E,4EAA4E;QAC5E,2EAA2E;QAC3E,+DAA+D;QAC/D,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAC7B,GAAG,IAAI,CAAC,cAAc,yCAAyC,MAAM,UAAU,KAAK,iDAAiD,CACtI,CAAC;QACF,OAAO,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,EAAU;QACvB,OAAO,IAAI,CAAC,OAAO,CACjB,GAAG,IAAI,CAAC,cAAc,kBAAkB,EAAE,EAAE,CAC7C,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,EAAU;QACtB,OAAO,IAAI,CAAC,OAAO,CACjB,GAAG,IAAI,CAAC,cAAc,iBAAiB,EAAE,EAAE,CAC5C,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,WAAW,CACf,KAAa,EACb,IAAa,EACb,QAAiB,EACjB,WAAmB,UAAU;QAE7B,MAAM,OAAO,GAAuB;YAClC,WAAW,EAAE,KAAK;YAClB,aAAa,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;YACrD,QAAQ,EAAE,QAA0C;YACpD,IAAI,EAAE,YAAY;SACnB,CAAC;QAEF,IAAI,QAAQ;YAAE,OAAO,CAAC,cAAc,GAAG,QAAQ,CAAC;QAChD,IAAI,IAAI;YAAE,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;QAEpC,OAAO,IAAI,CAAC,OAAO,CACjB,GAAG,IAAI,CAAC,cAAc,gBAAgB,EACtC;YACE,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;SAC9B,CACF,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,WAAW,CACf,EAAU,EACV,OAA2B;QAE3B,OAAO,IAAI,CAAC,OAAO,CACjB,GAAG,IAAI,CAAC,cAAc,kBAAkB,EAAE,EAAE,EAC5C;YACE,MAAM,EAAE,KAAK;YACb,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;SAC9B,CACF,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,WAAmB;QACnC,OAAO,IAAI,CAAC,OAAO,CACjB,GAAG,IAAI,CAAC,cAAc,eAAe,EACrC;YACE,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;SAC7C,CACF,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,eAAe,CACnB,MAAc,EACd,KAAK,GAAG,EAAE;QAEV,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAC7B,GAAG,IAAI,CAAC,cAAc,gBAAgB,MAAM,WAAW,CACxD,CAAC;QACF,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;QACrC,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,WAAW;QACf,2EAA2E;QAC3E,2EAA2E;QAC3E,oEAAoE;QACpE,0EAA0E;QAC1E,oEAAoE;QACpE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAM5B,GAAG,IAAI,CAAC,cAAc,uBAAuB,CAAC,CAAC;QAElD,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,EAAE,CAAC;QAC7C,uEAAuE;QACvE,qEAAqE;QACrE,yEAAyE;QACzE,YAAY;QACZ,IAAI,IAAY,CAAC;QACjB,IAAI,CAAC;YACH,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,QAAQ,CAAC;QAC/C,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,GAAG,IAAI,CAAC,cAAc,CAAC;QAC7B,CAAC;QACD,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,CAC7B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,IAAI,IAAI,CAAC,CAAC,aAAa,KAAK,IAAI,CACvD,CAAC;QACF,OAAO,KAAK,EAAE,QAAQ,IAAI,EAAE,CAAC;IAC/B,CAAC;IAED,KAAK,CAAC,iBAAiB,CACrB,MAAM,GAAG,CAAC,EACV,KAAK,GAAG,EAAE;QAEV,yEAAyE;QACzE,sEAAsE;QACtE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,CAC7B,GAAG,IAAI,CAAC,cAAc,4CAA4C,MAAM,UAAU,KAAK,0CAA0C,CAClI,CAAC;QACF,OAAO,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,MAAc;QACnC,qEAAqE;QACrE,kEAAkE;QAClE,qEAAqE;QACrE,wEAAwE;QACxE,EAAE;QACF,4EAA4E;QAC5E,yEAAyE;QACzE,4EAA4E;QAC5E,MAAM,QAAQ,GAAG,aAAa,CAAC;QAC/B,MAAM,QAAQ,GAAG,mBAAmB,CAAC;QACrC,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC;YAC3C,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,iBAAiB,CAAC,IAAI,GAAG,QAAQ,EAAE,QAAQ,CAAC,CAAC;YAC1E,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC,CAAC;YACjD,IAAI,KAAK;gBAAE,OAAO,KAAK,CAAC;YACxB,IAAI,KAAK,CAAC,MAAM,GAAG,QAAQ;gBAAE,MAAM,CAAC,8BAA8B;QACpE,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,UAAU,CACd,QAAuC,EACvC,aAAwB;QAExB,MAAM,OAAO,GAAsB;YACjC,QAAQ;YACR,KAAK,EAAE,SAAS;YAChB,OAAO,EAAE,MAAM;YACf,gBAAgB,EAAE,UAAU;SAC7B,CAAC;QACF,IAAI,aAAa,EAAE,MAAM,EAAE,CAAC;YAC1B,OAAO,CAAC,aAAa,GAAG,aAAa,CAAC;QACxC,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CACjB,GAAG,IAAI,CAAC,cAAc,sBAAsB,EAC5C;YACE,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;SAC9B,CACF,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,oBAAoB,CAAC,GAAW;QACpC,OAAO,IAAI,CAAC,OAAO,CACjB,GAAG,IAAI,CAAC,cAAc,4BAA4B,EAClD;YACE,MAAM,EAAE,MAAM;YACd,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;SAC5C,CACF,CAAC;IACJ,CAAC;CACF"} |
+65
-6
@@ -6,2 +6,52 @@ #!/usr/bin/env node | ||
| import { resolveCredentials } from "./auth/resolve-credentials.js"; | ||
| /** | ||
| * Wire every way this process can be asked to stop to one clean transport close. | ||
| * | ||
| * SIGTERM/SIGINT: nothing installs a handler by default, and the kernel ignores | ||
| * default-disposition signals for PID 1 — so in a container `docker stop` waits | ||
| * out its full 10s grace period and then SIGKILLs. Handling the signal | ||
| * in-process keeps the image free of an init wrapper. | ||
| * | ||
| * stdin EOF: the normal end of an MCP stdio session. The transport doesn't | ||
| * watch for it; the process just exits once the event loop drains, which any | ||
| * still-pending request can hold open for the length of its network timeout. | ||
| * Closing explicitly makes the ordinary shutdown immediate either way. | ||
| * | ||
| * Idempotent by design: a second trigger arriving mid-shutdown is dropped, and | ||
| * a `close()` that never settles is capped by a forced exit. | ||
| */ | ||
| function installShutdownHandlers(close) { | ||
| let shuttingDown = false; | ||
| const shutdown = async (trigger) => { | ||
| if (shuttingDown) | ||
| return; | ||
| shuttingDown = true; | ||
| console.error(`Shutting down (${trigger}).`); | ||
| // Unref'd so it can never hold the event loop open by itself: it only | ||
| // fires when something *else* is still keeping the process alive, which is | ||
| // precisely the hang this guards against. | ||
| setTimeout(() => process.exit(0), 2000).unref(); | ||
| try { | ||
| await close(); | ||
| } | ||
| catch (err) { | ||
| console.error("Error while closing transport:", err instanceof Error ? err.message : String(err)); | ||
| } | ||
| process.exit(0); | ||
| }; | ||
| process.on("SIGTERM", () => void shutdown("SIGTERM")); | ||
| process.on("SIGINT", () => void shutdown("SIGINT")); | ||
| process.stdin.once("end", () => void shutdown("stdin EOF")); | ||
| } | ||
| /** Parse SUBSTACK_REQUEST_TIMEOUT_MS, warning (not failing) on garbage. */ | ||
| function resolveTimeoutMs(raw) { | ||
| if (raw === undefined || raw === "") | ||
| return undefined; | ||
| const parsed = Number(raw); | ||
| if (!Number.isFinite(parsed) || parsed <= 0) { | ||
| console.error(`Warning: ignoring invalid SUBSTACK_REQUEST_TIMEOUT_MS="${raw}" — using the default.`); | ||
| return undefined; | ||
| } | ||
| return parsed; | ||
| } | ||
| async function main() { | ||
@@ -20,7 +70,20 @@ // Env vars take precedence; a stored session (from `substack-mcp-login`) | ||
| const userAgent = process.env.SUBSTACK_USER_AGENT; | ||
| const timeoutMs = resolveTimeoutMs(process.env.SUBSTACK_REQUEST_TIMEOUT_MS); | ||
| // The client constructor rejects a non-numeric user id; fall back to "0" so | ||
| // startup surfaces the friendly missing-credentials warning above instead of | ||
| // throwing when nothing is configured yet. | ||
| const client = new SubstackClient(publicationUrl, sessionToken, userId || "0", userAgent); | ||
| // Validate auth on startup (warn but don't block — allows inspection without credentials) | ||
| const client = new SubstackClient(publicationUrl, sessionToken, userId || "0", userAgent, timeoutMs); | ||
| const server = createServer(client); | ||
| const transport = new StdioServerTransport(); | ||
| // Registered before connect so a signal arriving during startup is still | ||
| // handled; McpServer.close() is safe on a server that never connected. | ||
| installShutdownHandlers(() => server.close()); | ||
| await server.connect(transport); | ||
| console.error("Substack MCP server running on stdio"); | ||
| // Auth is validated *after* connect, and the result only warns. Awaiting it | ||
| // first put a network round trip in front of the MCP handshake: a host that | ||
| // hangs rather than refuses (corporate proxy, blackholed route) stalled | ||
| // `initialize` for undici's full connect timeout with no output at all. | ||
| // Tools still error individually on a bad token, which is where the failure | ||
| // is actionable anyway. | ||
| try { | ||
@@ -34,6 +97,2 @@ const user = await client.validateAuth(); | ||
| } | ||
| const server = createServer(client); | ||
| const transport = new StdioServerTransport(); | ||
| await server.connect(transport); | ||
| console.error("Substack MCP server running on stdio"); | ||
| } | ||
@@ -40,0 +99,0 @@ main().catch((err) => { |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAEnE,KAAK,UAAU,IAAI;IACjB,yEAAyE;IACzE,kBAAkB;IAClB,MAAM,KAAK,GAAG,kBAAkB,EAAE,CAAC;IACnC,MAAM,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC;IAEvD,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,KAAK,CAAC,iCAAiC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3E,OAAO,CAAC,KAAK,CACX,qGAAqG,CACtG,CAAC;IACJ,CAAC;SAAM,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;QAClC,OAAO,CAAC,KAAK,CAAC,qCAAqC,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;IAClD,4EAA4E;IAC5E,6EAA6E;IAC7E,2CAA2C;IAC3C,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC,cAAc,EAAE,YAAY,EAAE,MAAM,IAAI,GAAG,EAAE,SAAS,CAAC,CAAC;IAE1F,0FAA0F;IAC1F,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,YAAY,EAAE,CAAC;QACzC,OAAO,CAAC,KAAK,CAAC,yBAAyB,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC;IACpD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,KAAK,CAAC,2FAA2F,CAAC,CAAC;QAC3G,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAClE,CAAC;IAED,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACpC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAChC,OAAO,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;AACxD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;IACnC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"} | ||
| {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} |
@@ -26,2 +26,24 @@ export declare class SubstackAPIError extends Error { | ||
| /** | ||
| * The client's own request deadline fired before any response arrived. | ||
| * | ||
| * There is no real status here — Substack never answered — so 408 is synthetic, | ||
| * chosen only so this fits the `SubstackAPIError` shape every tool handler | ||
| * already renders. Without it, an aborted fetch surfaces as a bare | ||
| * `DOMException: The operation was aborted due to timeout`, which says nothing | ||
| * about which request died or how to fix it. | ||
| */ | ||
| export declare class TimeoutError extends SubstackAPIError { | ||
| constructor(endpoint: string, timeoutMs: number); | ||
| } | ||
| /** | ||
| * True when a rejected `fetch` was aborted rather than failing on its own. | ||
| * | ||
| * Shape varies by runtime: `AbortSignal.timeout()` rejects with a DOMException | ||
| * named `TimeoutError`, older undici builds substitute a generic `AbortError` | ||
| * instead of propagating the signal's reason, and some wrap the abort in a | ||
| * `TypeError` with the real reason on `.cause`. All three mean the same thing, | ||
| * so all three are matched. | ||
| */ | ||
| export declare function isAbortError(err: unknown): boolean; | ||
| /** | ||
| * Maps an HTTP status code + error detail string to the appropriate typed | ||
@@ -28,0 +50,0 @@ * error. 401/403 route to AuthenticationError with `detail` intentionally |
+34
-0
@@ -46,2 +46,36 @@ export class SubstackAPIError extends Error { | ||
| /** | ||
| * The client's own request deadline fired before any response arrived. | ||
| * | ||
| * There is no real status here — Substack never answered — so 408 is synthetic, | ||
| * chosen only so this fits the `SubstackAPIError` shape every tool handler | ||
| * already renders. Without it, an aborted fetch surfaces as a bare | ||
| * `DOMException: The operation was aborted due to timeout`, which says nothing | ||
| * about which request died or how to fix it. | ||
| */ | ||
| export class TimeoutError extends SubstackAPIError { | ||
| constructor(endpoint, timeoutMs) { | ||
| super(408, `Request timed out after ${timeoutMs}ms with no response. The host may be unreachable, ` + | ||
| "or behind a proxy that drops packets instead of refusing the connection. " + | ||
| "Set SUBSTACK_REQUEST_TIMEOUT_MS to raise the limit if the publication is just slow.", endpoint); | ||
| this.name = "TimeoutError"; | ||
| } | ||
| } | ||
| /** | ||
| * True when a rejected `fetch` was aborted rather than failing on its own. | ||
| * | ||
| * Shape varies by runtime: `AbortSignal.timeout()` rejects with a DOMException | ||
| * named `TimeoutError`, older undici builds substitute a generic `AbortError` | ||
| * instead of propagating the signal's reason, and some wrap the abort in a | ||
| * `TypeError` with the real reason on `.cause`. All three mean the same thing, | ||
| * so all three are matched. | ||
| */ | ||
| export function isAbortError(err) { | ||
| const isAbortNamed = (e) => { | ||
| const name = e?.name; | ||
| return name === "TimeoutError" || name === "AbortError"; | ||
| }; | ||
| return (isAbortNamed(err) || | ||
| isAbortNamed(err?.cause)); | ||
| } | ||
| /** | ||
| * Maps an HTTP status code + error detail string to the appropriate typed | ||
@@ -48,0 +82,0 @@ * error. 401/403 route to AuthenticationError with `detail` intentionally |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"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"} | ||
| {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"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"} |
+0
-4
@@ -21,6 +21,2 @@ # substack-mcp — stdio MCP server for Substack | ||
| COPY --from=builder /app/dist ./dist | ||
| # tsconfig has no test exclusion, so tsc emits src/__tests__ into dist. Those | ||
| # files import vitest, which --omit=dev correctly leaves out, so they are dead | ||
| # weight and scanner surface in a published image. | ||
| RUN rm -rf dist/__tests__ | ||
@@ -27,0 +23,0 @@ # Credentials come from the environment and are never baked into the image: |
+2
-2
| { | ||
| "name": "@conorbronsdon/substack-mcp", | ||
| "version": "0.6.1", | ||
| "version": "0.6.2", | ||
| "description": "MCP server for Substack — read posts, manage drafts, publish Notes. Long-form posts are draft-only by design (no publish, no delete); Notes publish immediately.", | ||
@@ -12,3 +12,3 @@ "type": "module", | ||
| "build": "tsc", | ||
| "lint": "tsc --noEmit", | ||
| "lint": "tsc --noEmit -p tsconfig.lint.json", | ||
| "test": "vitest run", | ||
@@ -15,0 +15,0 @@ "start": "node dist/index.js", |
+15
-1
@@ -179,2 +179,14 @@ <div align="center"> | ||
| ## Request timeout | ||
| Every request to Substack is bounded by a 30-second deadline. Node applies no request timeout of its own — only a 10-second *connect* timeout — so a host that accepts the connection and then goes silent (a proxy that drops packets rather than refusing them) would otherwise hang a tool call indefinitely. A request that hits the deadline fails with a `TimeoutError` naming the endpoint and the limit. | ||
| Raise or lower it with `SUBSTACK_REQUEST_TIMEOUT_MS` (milliseconds; a non-numeric or non-positive value is ignored with a warning and the default is used): | ||
| ```json | ||
| "env": { | ||
| "SUBSTACK_REQUEST_TIMEOUT_MS": "60000" | ||
| } | ||
| ``` | ||
| ## Typed errors | ||
@@ -191,2 +203,3 @@ | ||
| | `ServerError` | 5xx | Failure on Substack's side | | ||
| | `TimeoutError` | 408 (synthetic) | The request hit the client's own deadline — no response arrived, so there is no real status to report (see [Request timeout](#request-timeout)) | | ||
| | `SubstackAPIError` | any other status | Fallback for unmapped status codes | | ||
@@ -215,3 +228,4 @@ | ||
| - Session tokens are sent as cookies. Keep your `SUBSTACK_SESSION_TOKEN` secure. | ||
| - The server validates authentication on startup and will fail fast if your token is expired. | ||
| - The server checks your credentials on startup, *after* the MCP handshake completes, and only warns — it never blocks startup on a network call. Tools still error individually if the token is expired, which is where the failure is actionable. | ||
| - `SIGTERM` and `SIGINT` are handled: the server closes its transport and exits 0, so `docker stop` returns promptly instead of waiting out the grace period. | ||
@@ -218,0 +232,0 @@ ## Development |
| export {}; |
| import { describe, it, expect } from "vitest"; | ||
| import { buildAnnotations, TOOL_KINDS } from "../annotations.js"; | ||
| import { createServer } from "../server.js"; | ||
| // The server only touches the client inside tool handlers, which these tests | ||
| // never invoke — a bare object is enough to register everything. | ||
| const server = createServer({}); | ||
| // SDK-private registry; the McpServer API has no public tool introspection. | ||
| const registered = server._registeredTools; | ||
| const READ_TOOLS = [ | ||
| "get_subscriber_count", | ||
| "list_published_posts", | ||
| "list_drafts", | ||
| "get_post", | ||
| "get_draft", | ||
| "get_post_comments", | ||
| "get_sections", | ||
| "get_post_analytics", | ||
| "list_scheduled_posts", | ||
| ]; | ||
| const DRAFT_WRITE_TOOLS = ["create_draft", "update_draft"]; | ||
| const PUBLIC_UPLOAD_TOOLS = ["upload_image"]; | ||
| const PUBLISH_TOOLS = ["create_note", "create_note_with_link"]; | ||
| describe("buildAnnotations mapping", () => { | ||
| it("read -> { readOnlyHint: true } and nothing else", () => { | ||
| const a = buildAnnotations("get_post"); | ||
| expect(a).toEqual({ readOnlyHint: true }); | ||
| }); | ||
| it("draft write -> readOnlyHint:false, explicitly non-destructive and closed-world", () => { | ||
| const a = buildAnnotations("create_draft"); | ||
| expect(a).toEqual({ | ||
| readOnlyHint: false, | ||
| destructiveHint: false, | ||
| openWorldHint: false, | ||
| }); | ||
| }); | ||
| it("public upload -> openWorldHint:true (returns a publicly-fetchable URL), non-destructive", () => { | ||
| const a = buildAnnotations("upload_image"); | ||
| expect(a).toEqual({ | ||
| readOnlyHint: false, | ||
| destructiveHint: false, | ||
| openWorldHint: true, | ||
| }); | ||
| }); | ||
| it("publish (Notes) -> readOnlyHint:false, openWorldHint:true, non-destructive", () => { | ||
| const a = buildAnnotations("create_note"); | ||
| expect(a).toEqual({ | ||
| readOnlyHint: false, | ||
| destructiveHint: false, | ||
| openWorldHint: true, | ||
| }); | ||
| }); | ||
| }); | ||
| describe("tool annotation classifications", () => { | ||
| it("completeness: registered tools and the classification registry match exactly", () => { | ||
| // Every registered tool must be classified, and every classified tool | ||
| // must exist — new tools cannot slip through unannotated, and stale | ||
| // registry entries cannot linger. | ||
| expect(Object.keys(registered).sort()).toEqual(Object.keys(TOOL_KINDS).sort()); | ||
| }); | ||
| it("every registered tool carries the annotations its classification dictates", () => { | ||
| for (const name of Object.keys(TOOL_KINDS)) { | ||
| expect(registered[name].annotations, `${name} annotations should match buildAnnotations`).toEqual(buildAnnotations(name)); | ||
| } | ||
| }); | ||
| it("read tools are readOnlyHint:true", () => { | ||
| for (const name of READ_TOOLS) { | ||
| expect(buildAnnotations(name).readOnlyHint, name).toBe(true); | ||
| } | ||
| }); | ||
| it("draft writes are readOnlyHint:false, explicitly non-destructive and closed-world", () => { | ||
| for (const name of DRAFT_WRITE_TOOLS) { | ||
| const a = buildAnnotations(name); | ||
| expect(a.readOnlyHint, name).toBe(false); | ||
| expect(a.destructiveHint, name).toBe(false); | ||
| expect(a.openWorldHint, name).toBe(false); | ||
| } | ||
| }); | ||
| it("public uploads carry openWorldHint:true (publicly-fetchable URL), non-destructive", () => { | ||
| for (const name of PUBLIC_UPLOAD_TOOLS) { | ||
| const a = buildAnnotations(name); | ||
| expect(a.readOnlyHint, name).toBe(false); | ||
| expect(a.destructiveHint, name).toBe(false); | ||
| expect(a.openWorldHint, name).toBe(true); | ||
| } | ||
| }); | ||
| it("Note tools (immediate public publish) carry openWorldHint:true", () => { | ||
| for (const name of PUBLISH_TOOLS) { | ||
| const a = buildAnnotations(name); | ||
| expect(a.readOnlyHint, name).toBe(false); | ||
| expect(a.openWorldHint, name).toBe(true); | ||
| } | ||
| }); | ||
| it("no write tool is destructive (this server has no deletes by design)", () => { | ||
| // Writes set destructiveHint explicitly to false — never left to MCP's | ||
| // unsafe default of true. Reads omit it (not meaningful for a read). | ||
| for (const name of [ | ||
| ...DRAFT_WRITE_TOOLS, | ||
| ...PUBLIC_UPLOAD_TOOLS, | ||
| ...PUBLISH_TOOLS, | ||
| ]) { | ||
| expect(buildAnnotations(name).destructiveHint, name).toBe(false); | ||
| } | ||
| }); | ||
| it("Note tool descriptions say loudly that they publish immediately", () => { | ||
| for (const name of PUBLISH_TOOLS) { | ||
| expect(registered[name].description, name).toMatch(/publishes immediately/i); | ||
| } | ||
| }); | ||
| it("the classification groups above cover the whole registry", () => { | ||
| const grouped = [ | ||
| ...READ_TOOLS, | ||
| ...DRAFT_WRITE_TOOLS, | ||
| ...PUBLIC_UPLOAD_TOOLS, | ||
| ...PUBLISH_TOOLS, | ||
| ]; | ||
| expect(grouped.sort()).toEqual(Object.keys(TOOL_KINDS).sort()); | ||
| }); | ||
| }); | ||
| //# sourceMappingURL=annotations.test.js.map |
| {"version":3,"file":"annotations.test.js","sourceRoot":"","sources":["../../src/__tests__/annotations.test.ts"],"names":[],"mappings":"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"} |
| export {}; |
| import { describe, it, expect, vi, afterEach } from "vitest"; | ||
| import { SubstackClient, parseMagnitude, MAX_PAGE_SIZE, ANALYTICS_MAX_PAGES, ANALYTICS_SCAN_DEPTH, } from "../api/client.js"; | ||
| import { AuthenticationError, RateLimitError, ValidationError, NotFoundError, ServerError, SubstackAPIError, } from "../utils/errors.js"; | ||
| describe("SubstackClient constructor", () => { | ||
| it("parses valid numeric userId without throwing", () => { | ||
| expect(() => new SubstackClient("https://example.substack.com", "tok123", "42")).not.toThrow(); | ||
| }); | ||
| it("throws with clear message for invalid userId", () => { | ||
| expect(() => new SubstackClient("https://example.substack.com", "tok123", "abc")).toThrow('Invalid SUBSTACK_USER_ID: "abc" — must be a number'); | ||
| }); | ||
| it("strips trailing slash from publication URL", () => { | ||
| // Access the private field via any cast to verify behavior | ||
| const client = new SubstackClient("https://example.substack.com/", "tok", "1"); | ||
| expect(client.publicationUrl).toBe("https://example.substack.com"); | ||
| }); | ||
| it("sets cookie string with both connect.sid and substack.sid", () => { | ||
| const client = new SubstackClient("https://example.substack.com", "mytoken", "1"); | ||
| expect(client.cookie).toContain("connect.sid=mytoken"); | ||
| expect(client.cookie).toContain("substack.sid=mytoken"); | ||
| }); | ||
| }); | ||
| describe("SubstackClient requests", () => { | ||
| afterEach(() => vi.unstubAllGlobals()); | ||
| function stubFetch(jsonBody) { | ||
| const fetchMock = vi.fn(async (..._args) => ({ | ||
| ok: true, | ||
| status: 200, | ||
| json: async () => jsonBody, | ||
| text: async () => JSON.stringify(jsonBody), | ||
| })); | ||
| vi.stubGlobal("fetch", fetchMock); | ||
| return fetchMock; | ||
| } | ||
| it("getDrafts uses post_management/drafts with ordering and unwraps posts[]", async () => { | ||
| const fetchMock = stubFetch({ posts: [{ id: 7, draft_title: "hi" }], total: 1 }); | ||
| const client = new SubstackClient("https://example.substack.com", "tok", "1"); | ||
| const drafts = await client.getDrafts(0, 5); | ||
| expect(drafts).toHaveLength(1); | ||
| expect(drafts[0].id).toBe(7); | ||
| const calledUrl = fetchMock.mock.calls[0][0]; | ||
| expect(calledUrl).toContain("/api/v1/post_management/drafts"); | ||
| expect(calledUrl).toContain("order_by=draft_updated_at"); | ||
| expect(calledUrl).toContain("order_direction=desc"); | ||
| expect(calledUrl).not.toContain("/api/v1/drafts?"); | ||
| }); | ||
| it("getSections matches the publication by hostname and returns its sections", async () => { | ||
| const fetchMock = stubFetch({ | ||
| publications: [ | ||
| { hostname: "other.substack.com", sections: [{ id: 1, name: "Other" }] }, | ||
| { | ||
| hostname: "example.substack.com", | ||
| sections: [ | ||
| { id: 10, name: "Essays" }, | ||
| { id: 11, name: "Notes" }, | ||
| ], | ||
| }, | ||
| ], | ||
| }); | ||
| const client = new SubstackClient("https://example.substack.com", "tok", "1"); | ||
| const sections = await client.getSections(); | ||
| expect(sections).toHaveLength(2); | ||
| expect(sections[0]).toEqual({ id: 10, name: "Essays" }); | ||
| expect(fetchMock.mock.calls[0][0]).toContain("/api/v1/subscriptions"); | ||
| }); | ||
| it("getSections matches on custom_domain when hostname does not match", async () => { | ||
| const fetchMock = stubFetch({ | ||
| publications: [ | ||
| { | ||
| hostname: "example.substack.com", | ||
| custom_domain: "newsletter.example.com", | ||
| sections: [{ id: 7, name: "Custom" }], | ||
| }, | ||
| ], | ||
| }); | ||
| const client = new SubstackClient("https://newsletter.example.com", "tok", "1"); | ||
| const sections = await client.getSections(); | ||
| expect(sections).toEqual([{ id: 7, name: "Custom" }]); | ||
| expect(fetchMock.mock.calls[0][0]).toContain("/api/v1/subscriptions"); | ||
| }); | ||
| it("getSections does not substring-match an unrelated publication host", async () => { | ||
| // `ample.substack.com` is a substring of `example.substack.com`; exact-host | ||
| // matching must skip it and return the real publication's sections. | ||
| stubFetch({ | ||
| publications: [ | ||
| { hostname: "ample.substack.com", sections: [{ id: 1, name: "Wrong" }] }, | ||
| { hostname: "example.substack.com", sections: [{ id: 2, name: "Right" }] }, | ||
| ], | ||
| }); | ||
| const client = new SubstackClient("https://example.substack.com", "tok", "1"); | ||
| const sections = await client.getSections(); | ||
| expect(sections).toEqual([{ id: 2, name: "Right" }]); | ||
| }); | ||
| it("getSections returns [] when no publication matches", async () => { | ||
| stubFetch({ publications: [{ hostname: "nope.substack.com", sections: [{ id: 1, name: "X" }] }] }); | ||
| const client = new SubstackClient("https://example.substack.com", "tok", "1"); | ||
| expect(await client.getSections()).toEqual([]); | ||
| }); | ||
| it("getScheduledPosts uses the scheduled view ordered by trigger_at asc", async () => { | ||
| const fetchMock = stubFetch({ | ||
| posts: [{ id: 5, draft_title: "Queued", audience: "everyone", trigger_at: "2030-01-01T00:00:00Z" }], | ||
| }); | ||
| const client = new SubstackClient("https://example.substack.com", "tok", "1"); | ||
| const posts = await client.getScheduledPosts(0, 10); | ||
| expect(posts).toHaveLength(1); | ||
| expect(posts[0].id).toBe(5); | ||
| const url = fetchMock.mock.calls[0][0]; | ||
| expect(url).toContain("/api/v1/post_management/scheduled"); | ||
| expect(url).toContain("order_by=trigger_at"); | ||
| expect(url).toContain("order_direction=asc"); | ||
| }); | ||
| it("getPostAnalytics finds a post's stats row in the published feed", async () => { | ||
| const fetchMock = stubFetch({ | ||
| posts: [ | ||
| { id: 100, title: "A", stats: { views: 1 } }, | ||
| { id: 200, title: "B", stats: { views: 42, opened: 10 } }, | ||
| ], | ||
| total: 2, | ||
| }); | ||
| const client = new SubstackClient("https://example.substack.com", "tok", "1"); | ||
| const post = await client.getPostAnalytics(200); | ||
| expect(post?.id).toBe(200); | ||
| expect(post?.stats?.views).toBe(42); | ||
| expect(fetchMock.mock.calls[0][0]).toContain("/api/v1/post_management/published"); | ||
| }); | ||
| it("getPostAnalytics returns null and stops paging when the feed is exhausted", async () => { | ||
| const fetchMock = stubFetch({ posts: [{ id: 1, title: "only" }], total: 1 }); | ||
| const client = new SubstackClient("https://example.substack.com", "tok", "1"); | ||
| const post = await client.getPostAnalytics(999); | ||
| expect(post).toBeNull(); | ||
| // A short page (1 < MAX_PAGE_SIZE) means end-of-feed: exactly one request, | ||
| // no over-paging. | ||
| expect(fetchMock).toHaveBeenCalledTimes(1); | ||
| }); | ||
| it("sends a browser User-Agent and a Referer by default", async () => { | ||
| const fetchMock = stubFetch({ posts: [] }); | ||
| const client = new SubstackClient("https://example.substack.com", "tok", "1"); | ||
| await client.getDrafts(); | ||
| const opts = fetchMock.mock.calls[0][1]; | ||
| expect(opts.headers["User-Agent"]).toContain("Mozilla/5.0"); | ||
| expect(opts.headers["Referer"]).toBe("https://example.substack.com/publish/home"); | ||
| }); | ||
| it("honors a custom User-Agent override", async () => { | ||
| const fetchMock = stubFetch({ posts: [] }); | ||
| const client = new SubstackClient("https://example.substack.com", "tok", "1", "MyUA/1.0"); | ||
| await client.getDrafts(); | ||
| const opts = fetchMock.mock.calls[0][1]; | ||
| expect(opts.headers["User-Agent"]).toBe("MyUA/1.0"); | ||
| }); | ||
| it("omits Content-Type on a bodyless GET", async () => { | ||
| const fetchMock = stubFetch({ posts: [] }); | ||
| const client = new SubstackClient("https://example.substack.com", "tok", "1"); | ||
| await client.getDrafts(); | ||
| const opts = fetchMock.mock.calls[0][1]; | ||
| expect(opts.headers["Content-Type"]).toBeUndefined(); | ||
| }); | ||
| it("sends Content-Type on a request with a body", async () => { | ||
| const fetchMock = stubFetch({ id: 1, draft_title: "hi" }); | ||
| const client = new SubstackClient("https://example.substack.com", "tok", "1"); | ||
| await client.createDraft("hi"); | ||
| const opts = fetchMock.mock.calls[0][1]; | ||
| expect(opts.headers["Content-Type"]).toBe("application/json"); | ||
| }); | ||
| }); | ||
| describe("pagination limit cap (regression: #28)", () => { | ||
| afterEach(() => vi.unstubAllGlobals()); | ||
| // Substack 400s on limit=51 and above, so every request this client issues on | ||
| // its own initiative must carry a limit VALUE within the cap. The pre-fix | ||
| // suite passed with limit=100 on the wire because it only asserted that the | ||
| // URL *contained* the endpoint path — a shape assertion cannot see a bad | ||
| // value. These read the query params back off the URLs the client actually | ||
| // built. | ||
| function paramValues(fetchMock, name) { | ||
| return fetchMock.mock.calls | ||
| .map((call) => new URL(String(call[0])).searchParams.get(name)) | ||
| .filter((v) => v !== null) | ||
| .map(Number); | ||
| } | ||
| /** | ||
| * A published feed that honors offset/limit, so paging behavior follows the | ||
| * page size the client asks for rather than a fixture hardcoded around one. | ||
| */ | ||
| function stubPagedFeed(totalPosts) { | ||
| const fetchMock = vi.fn(async (url) => { | ||
| const params = new URL(String(url)).searchParams; | ||
| const offset = Number(params.get("offset") ?? 0); | ||
| const limit = Number(params.get("limit") ?? 0); | ||
| const count = Math.max(0, Math.min(limit, totalPosts - offset)); | ||
| const body = { | ||
| posts: Array.from({ length: count }, (_, i) => ({ | ||
| id: offset + i + 1, | ||
| title: `Post ${offset + i + 1}`, | ||
| stats: { views: 1 }, | ||
| })), | ||
| total: totalPosts, | ||
| }; | ||
| return { | ||
| ok: true, | ||
| status: 200, | ||
| json: async () => body, | ||
| text: async () => JSON.stringify(body), | ||
| }; | ||
| }); | ||
| vi.stubGlobal("fetch", fetchMock); | ||
| return fetchMock; | ||
| } | ||
| it("MAX_PAGE_SIZE is within Substack's observed 50 cap", () => { | ||
| // limit=50 → 200, limit=51 → 400 "Invalid value" (probed 2026-08-02). | ||
| expect(MAX_PAGE_SIZE).toBeLessThanOrEqual(50); | ||
| expect(MAX_PAGE_SIZE).toBeGreaterThan(0); | ||
| }); | ||
| it("preserves the documented 500-post analytics scan depth", () => { | ||
| expect(MAX_PAGE_SIZE * ANALYTICS_MAX_PAGES).toBe(500); | ||
| expect(ANALYTICS_SCAN_DEPTH).toBe(500); | ||
| }); | ||
| it("getPostAnalytics sends a limit value within the cap on every page", async () => { | ||
| const fetchMock = stubPagedFeed(10_000); // feed deeper than the scan bound | ||
| const client = new SubstackClient("https://example.substack.com", "tok", "1"); | ||
| // An id past the scan depth, so the loop runs to its bound. | ||
| const post = await client.getPostAnalytics(9_999); | ||
| expect(post).toBeNull(); | ||
| const limits = paramValues(fetchMock, "limit"); | ||
| // Every request carries a limit — none slipped through unparameterized. | ||
| expect(limits).toHaveLength(fetchMock.mock.calls.length); | ||
| expect(fetchMock).toHaveBeenCalledTimes(ANALYTICS_MAX_PAGES); | ||
| // The value, not the shape: no page may exceed what Substack accepts. | ||
| expect(Math.max(...limits)).toBeLessThanOrEqual(MAX_PAGE_SIZE); | ||
| expect(limits).toEqual(Array(ANALYTICS_MAX_PAGES).fill(MAX_PAGE_SIZE)); | ||
| }); | ||
| it("getPostAnalytics tiles offsets across exactly the scan depth", async () => { | ||
| const fetchMock = stubPagedFeed(10_000); | ||
| const client = new SubstackClient("https://example.substack.com", "tok", "1"); | ||
| await client.getPostAnalytics(9_999); | ||
| const offsets = paramValues(fetchMock, "offset"); | ||
| expect(offsets).toEqual(Array.from({ length: ANALYTICS_MAX_PAGES }, (_, i) => i * MAX_PAGE_SIZE)); | ||
| // Last page starts one page short of the bound, so the scan covers | ||
| // ANALYTICS_SCAN_DEPTH posts with no gap and no overshoot. | ||
| expect(Math.max(...offsets)).toBe(ANALYTICS_SCAN_DEPTH - MAX_PAGE_SIZE); | ||
| expect(Math.max(...offsets) + MAX_PAGE_SIZE).toBe(ANALYTICS_SCAN_DEPTH); | ||
| }); | ||
| it("getPostAnalytics stops as soon as the post is found", async () => { | ||
| const fetchMock = stubPagedFeed(10_000); | ||
| const client = new SubstackClient("https://example.substack.com", "tok", "1"); | ||
| // Post 60 lands on page 1 (offset 50), so page 2 must never be requested. | ||
| const post = await client.getPostAnalytics(60); | ||
| expect(post?.id).toBe(60); | ||
| expect(fetchMock).toHaveBeenCalledTimes(2); | ||
| expect(paramValues(fetchMock, "limit")).toEqual([MAX_PAGE_SIZE, MAX_PAGE_SIZE]); | ||
| }); | ||
| }); | ||
| describe("SubstackClient error mapping (end-to-end through request())", () => { | ||
| afterEach(() => vi.unstubAllGlobals()); | ||
| function stubFetchError(status, body) { | ||
| const fetchMock = vi.fn(async (..._args) => ({ | ||
| ok: false, | ||
| status, | ||
| json: async () => JSON.parse(body), | ||
| text: async () => body, | ||
| })); | ||
| vi.stubGlobal("fetch", fetchMock); | ||
| return fetchMock; | ||
| } | ||
| it("maps a 401 response to AuthenticationError", async () => { | ||
| stubFetchError(401, "Not authorized"); | ||
| const client = new SubstackClient("https://example.substack.com", "tok", "1"); | ||
| await expect(client.getDrafts()).rejects.toBeInstanceOf(AuthenticationError); | ||
| }); | ||
| it("maps a 403 Cloudflare-style block to AuthenticationError", async () => { | ||
| stubFetchError(403, "error code: 1010"); | ||
| const client = new SubstackClient("https://example.substack.com", "tok", "1"); | ||
| await expect(client.getDrafts()).rejects.toBeInstanceOf(AuthenticationError); | ||
| }); | ||
| it("maps a 429 response to RateLimitError with the body as detail", async () => { | ||
| stubFetchError(429, JSON.stringify({ error: "Too many requests" })); | ||
| const client = new SubstackClient("https://example.substack.com", "tok", "1"); | ||
| try { | ||
| await client.getDrafts(); | ||
| throw new Error("should have thrown"); | ||
| } | ||
| catch (err) { | ||
| expect(err).toBeInstanceOf(RateLimitError); | ||
| expect(err.message).toContain("Too many requests"); | ||
| } | ||
| }); | ||
| it("maps a 400 response to ValidationError", async () => { | ||
| stubFetchError(400, JSON.stringify({ error: "draft_title is required" })); | ||
| const client = new SubstackClient("https://example.substack.com", "tok", "1"); | ||
| try { | ||
| await client.createDraft(""); | ||
| throw new Error("should have thrown"); | ||
| } | ||
| catch (err) { | ||
| expect(err).toBeInstanceOf(ValidationError); | ||
| expect(err.message).toContain("draft_title is required"); | ||
| } | ||
| }); | ||
| it("maps a 404 response to NotFoundError", async () => { | ||
| stubFetchError(404, "Draft not found"); | ||
| const client = new SubstackClient("https://example.substack.com", "tok", "1"); | ||
| try { | ||
| await client.getDraft(999); | ||
| throw new Error("should have thrown"); | ||
| } | ||
| catch (err) { | ||
| expect(err).toBeInstanceOf(NotFoundError); | ||
| expect(err.message).toContain("Draft not found"); | ||
| } | ||
| }); | ||
| it("maps a 500 response to ServerError", async () => { | ||
| stubFetchError(500, "Internal Server Error"); | ||
| const client = new SubstackClient("https://example.substack.com", "tok", "1"); | ||
| try { | ||
| await client.getDrafts(); | ||
| throw new Error("should have thrown"); | ||
| } | ||
| catch (err) { | ||
| expect(err).toBeInstanceOf(ServerError); | ||
| expect(err.message).toContain("Internal Server Error"); | ||
| } | ||
| }); | ||
| it("falls back to base SubstackAPIError for an unmapped status", async () => { | ||
| stubFetchError(418, "I'm a teapot"); | ||
| const client = new SubstackClient("https://example.substack.com", "tok", "1"); | ||
| try { | ||
| await client.getDrafts(); | ||
| throw new Error("should have thrown"); | ||
| } | ||
| catch (err) { | ||
| expect(err).toBeInstanceOf(SubstackAPIError); | ||
| expect(err).not.toBeInstanceOf(AuthenticationError); | ||
| expect(err).not.toBeInstanceOf(RateLimitError); | ||
| expect(err).not.toBeInstanceOf(ValidationError); | ||
| expect(err).not.toBeInstanceOf(NotFoundError); | ||
| expect(err).not.toBeInstanceOf(ServerError); | ||
| expect(err.message).toContain("I'm a teapot"); | ||
| } | ||
| }); | ||
| }); | ||
| describe("parseMagnitude", () => { | ||
| it("parses whole-K buckets", () => { | ||
| expect(parseMagnitude("1K+")).toBe(1000); | ||
| expect(parseMagnitude("2K")).toBe(2000); | ||
| }); | ||
| it("parses fractional-K buckets (regression: .replace('K','000') produced NaN)", () => { | ||
| expect(parseMagnitude("2.5K+")).toBe(2500); | ||
| expect(parseMagnitude("1.2K")).toBe(1200); | ||
| }); | ||
| it("parses bare numbers and M buckets", () => { | ||
| expect(parseMagnitude("750+")).toBe(750); | ||
| expect(parseMagnitude("1,500+")).toBe(1500); | ||
| expect(parseMagnitude("1M+")).toBe(1000000); | ||
| }); | ||
| it("returns null for unparseable or zero values", () => { | ||
| expect(parseMagnitude("")).toBeNull(); | ||
| expect(parseMagnitude("lots")).toBeNull(); | ||
| expect(parseMagnitude("0")).toBeNull(); | ||
| expect(parseMagnitude("0K")).toBeNull(); | ||
| }); | ||
| }); | ||
| describe("SubstackClient.getSubscriberCount", () => { | ||
| const client = () => new SubstackClient("https://example.substack.com", "tok123", "42"); | ||
| afterEach(() => { | ||
| vi.restoreAllMocks(); | ||
| }); | ||
| const jsonResponse = (body) => ({ ok: true, status: 200, json: async () => body, text: async () => JSON.stringify(body) }); | ||
| const htmlResponse = (html) => ({ ok: true, status: 200, text: async () => html }); | ||
| it("reports exact when the API returns subscriber_count", async () => { | ||
| vi.spyOn(globalThis, "fetch").mockResolvedValue(jsonResponse({ subscriber_count: 1073 })); | ||
| const r = await client().getSubscriberCount(); | ||
| expect(r).toMatchObject({ count: 1073, precision: "exact" }); | ||
| }); | ||
| it("NEVER counts the paginated subscribers sample as the total", async () => { | ||
| // The regression this guards: an 11-item sample was returned as count: 11 | ||
| // for a publication with >1,000 subscribers. | ||
| const sample = { subscribers: Array.from({ length: 11 }, (_, i) => ({ id: i })) }; | ||
| vi.spyOn(globalThis, "fetch") | ||
| .mockResolvedValueOnce(jsonResponse(sample)) | ||
| .mockResolvedValueOnce(htmlResponse('{"freeSubscriberCount":"1,000"}')); | ||
| const r = await client().getSubscriberCount(); | ||
| expect(r.count).not.toBe(11); | ||
| expect(r).toMatchObject({ count: 1000, precision: "approximate" }); | ||
| }); | ||
| it("falls back to the rounded public value and marks it approximate", async () => { | ||
| vi.spyOn(globalThis, "fetch") | ||
| .mockResolvedValueOnce(jsonResponse({ audience: null })) | ||
| .mockResolvedValueOnce(htmlResponse('{\\"freeSubscriberCount\\":\\"1,000\\"}')); | ||
| const r = await client().getSubscriberCount(); | ||
| expect(r.precision).toBe("approximate"); | ||
| expect(r.count).toBe(1000); | ||
| expect(r.note).toMatch(/or higher/); | ||
| }); | ||
| it("uses the order-of-magnitude bucket when no numeric count is present", async () => { | ||
| vi.spyOn(globalThis, "fetch") | ||
| .mockResolvedValueOnce(jsonResponse({})) | ||
| .mockResolvedValueOnce(htmlResponse('{"freeSubscriberCountOrderOfMagnitude":"2.5K+"}')); | ||
| const r = await client().getSubscriberCount(); | ||
| expect(r).toMatchObject({ count: 2500, precision: "approximate" }); | ||
| }); | ||
| it("returns unavailable rather than a fabricated number when everything fails", async () => { | ||
| vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("network down")); | ||
| const r = await client().getSubscriberCount(); | ||
| expect(r).toMatchObject({ count: -1, precision: "unavailable" }); | ||
| }); | ||
| }); | ||
| //# sourceMappingURL=client.test.js.map |
| {"version":3,"file":"client.test.js","sourceRoot":"","sources":["../../src/__tests__/client.test.ts"],"names":[],"mappings":"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"} |
| export {}; |
| import { describe, it, expect } from "vitest"; | ||
| import { SubstackAPIError, AuthenticationError, RateLimitError, ValidationError, NotFoundError, ServerError, mapHttpStatusToError, extractErrorDetail, } from "../utils/errors.js"; | ||
| describe("SubstackAPIError", () => { | ||
| it("formats message with status code and endpoint", () => { | ||
| const err = new SubstackAPIError(404, "not found", "/api/v1/posts/99"); | ||
| expect(err.message).toBe("Substack API error (404) at /api/v1/posts/99: not found"); | ||
| expect(err.statusCode).toBe(404); | ||
| expect(err.endpoint).toBe("/api/v1/posts/99"); | ||
| }); | ||
| it("has correct name property", () => { | ||
| const err = new SubstackAPIError(500, "oops", "/api"); | ||
| expect(err.name).toBe("SubstackAPIError"); | ||
| }); | ||
| it("is an instance of Error", () => { | ||
| const err = new SubstackAPIError(400, "bad", "/api"); | ||
| expect(err).toBeInstanceOf(Error); | ||
| }); | ||
| }); | ||
| describe("AuthenticationError", () => { | ||
| it("is a 401 with cookie guidance message", () => { | ||
| const err = new AuthenticationError("/api/v1/drafts"); | ||
| expect(err.statusCode).toBe(401); | ||
| expect(err.message).toContain("connect.sid"); | ||
| expect(err.message).toContain("substack.sid"); | ||
| expect(err.endpoint).toBe("/api/v1/drafts"); | ||
| }); | ||
| it("has correct name property", () => { | ||
| const err = new AuthenticationError("/api"); | ||
| expect(err.name).toBe("AuthenticationError"); | ||
| }); | ||
| it("is an instance of SubstackAPIError", () => { | ||
| const err = new AuthenticationError("/api"); | ||
| expect(err).toBeInstanceOf(SubstackAPIError); | ||
| }); | ||
| }); | ||
| describe("RateLimitError", () => { | ||
| it("is a 429 with rate-limit guidance", () => { | ||
| const err = new RateLimitError("/api/v1/drafts", "too many requests"); | ||
| expect(err.statusCode).toBe(429); | ||
| expect(err.endpoint).toBe("/api/v1/drafts"); | ||
| expect(err.message).toContain("too many requests"); | ||
| expect(err.message).toContain("Slow down"); | ||
| }); | ||
| it("has correct name property and extends SubstackAPIError", () => { | ||
| const err = new RateLimitError("/api", "x"); | ||
| expect(err.name).toBe("RateLimitError"); | ||
| expect(err).toBeInstanceOf(SubstackAPIError); | ||
| }); | ||
| }); | ||
| describe("ValidationError", () => { | ||
| it("is a 400 with argument-checking guidance", () => { | ||
| const err = new ValidationError("/api/v1/drafts", "missing draft_title"); | ||
| expect(err.statusCode).toBe(400); | ||
| expect(err.message).toContain("missing draft_title"); | ||
| expect(err.message).toContain("Check the arguments passed to this tool"); | ||
| }); | ||
| it("has correct name property and extends SubstackAPIError", () => { | ||
| const err = new ValidationError("/api", "x"); | ||
| expect(err.name).toBe("ValidationError"); | ||
| expect(err).toBeInstanceOf(SubstackAPIError); | ||
| }); | ||
| }); | ||
| describe("NotFoundError", () => { | ||
| it("is a 404 mentioning the missing resource", () => { | ||
| const err = new NotFoundError("/api/v1/drafts/99", "no such draft"); | ||
| expect(err.statusCode).toBe(404); | ||
| expect(err.message).toContain("no such draft"); | ||
| expect(err.message).toContain("draft, post, or note"); | ||
| }); | ||
| it("has correct name property and extends SubstackAPIError", () => { | ||
| const err = new NotFoundError("/api", "x"); | ||
| expect(err.name).toBe("NotFoundError"); | ||
| expect(err).toBeInstanceOf(SubstackAPIError); | ||
| }); | ||
| }); | ||
| describe("ServerError", () => { | ||
| it("is a 5xx with retry-later guidance", () => { | ||
| const err = new ServerError("/api/v1/drafts", "internal error"); | ||
| expect(err.statusCode).toBe(500); | ||
| expect(err.message).toContain("internal error"); | ||
| expect(err.message).toContain("try again later"); | ||
| }); | ||
| it("has correct name property and extends SubstackAPIError", () => { | ||
| const err = new ServerError("/api", "x"); | ||
| expect(err.name).toBe("ServerError"); | ||
| expect(err).toBeInstanceOf(SubstackAPIError); | ||
| }); | ||
| }); | ||
| describe("mapHttpStatusToError", () => { | ||
| it("maps 401 and 403 to AuthenticationError, discarding detail", () => { | ||
| const err401 = mapHttpStatusToError(401, "some raw body that should be ignored", "/api/v1/drafts"); | ||
| const err403 = mapHttpStatusToError(403, "some raw body that should be ignored", "/api/v1/drafts"); | ||
| expect(err401).toBeInstanceOf(AuthenticationError); | ||
| expect(err403).toBeInstanceOf(AuthenticationError); | ||
| // Message must match AuthenticationError's own hardcoded guidance exactly, | ||
| // not the discarded detail string — this is the 401/403 tradeoff. | ||
| expect(err401.message).toBe(new AuthenticationError("/api/v1/drafts").message); | ||
| expect(err401.message).not.toContain("some raw body"); | ||
| }); | ||
| it("maps 429 to RateLimitError", () => { | ||
| const err = mapHttpStatusToError(429, "slow down", "/api"); | ||
| expect(err).toBeInstanceOf(RateLimitError); | ||
| expect(err.message).toContain("slow down"); | ||
| }); | ||
| it("maps 400 to ValidationError", () => { | ||
| const err = mapHttpStatusToError(400, "bad params", "/api"); | ||
| expect(err).toBeInstanceOf(ValidationError); | ||
| expect(err.message).toContain("bad params"); | ||
| }); | ||
| it("maps 404 to NotFoundError", () => { | ||
| const err = mapHttpStatusToError(404, "no such post", "/api"); | ||
| expect(err).toBeInstanceOf(NotFoundError); | ||
| expect(err.message).toContain("no such post"); | ||
| }); | ||
| it("maps 500-599 to ServerError", () => { | ||
| expect(mapHttpStatusToError(500, "boom", "/api")).toBeInstanceOf(ServerError); | ||
| expect(mapHttpStatusToError(502, "bad gateway", "/api")).toBeInstanceOf(ServerError); | ||
| expect(mapHttpStatusToError(503, "unavailable", "/api")).toBeInstanceOf(ServerError); | ||
| }); | ||
| it("falls back to base SubstackAPIError for unmapped status codes", () => { | ||
| const err = mapHttpStatusToError(418, "teapot", "/api"); | ||
| expect(err).toBeInstanceOf(SubstackAPIError); | ||
| expect(err).not.toBeInstanceOf(AuthenticationError); | ||
| expect(err).not.toBeInstanceOf(RateLimitError); | ||
| expect(err).not.toBeInstanceOf(ValidationError); | ||
| expect(err).not.toBeInstanceOf(NotFoundError); | ||
| expect(err).not.toBeInstanceOf(ServerError); | ||
| expect(err.message).toContain("Substack API error (418)"); | ||
| expect(err.message).toContain("teapot"); | ||
| }); | ||
| }); | ||
| describe("extractErrorDetail", () => { | ||
| it("returns the .error field from a JSON object body", () => { | ||
| expect(extractErrorDetail('{"error": "Invalid parameters"}', "fallback")).toBe("Invalid parameters"); | ||
| }); | ||
| it("returns the .message field from a JSON object body when .error is absent", () => { | ||
| expect(extractErrorDetail('{"message": "Draft is locked"}', "fallback")).toBe("Draft is locked"); | ||
| }); | ||
| it("joins a JSON .errors array into a single string", () => { | ||
| const result = extractErrorDetail('{"errors": ["title is required", "body is too long"]}', "fallback"); | ||
| expect(result).toContain("title is required"); | ||
| expect(result).toContain("body is too long"); | ||
| }); | ||
| it("returns a plain-text response body as-is", () => { | ||
| expect(extractErrorDetail("Not authorized", "fallback")).toBe("Not authorized"); | ||
| }); | ||
| it("falls back when response data is an empty string", () => { | ||
| expect(extractErrorDetail("", "fallback")).toBe("fallback"); | ||
| }); | ||
| it("trims and caps a Cloudflare-style HTML block page", () => { | ||
| const htmlPage = "\n\n <!DOCTYPE html><html><head><title>Attention Required! | Cloudflare</title></head>" + | ||
| "<body>" + | ||
| "error code: 1010".repeat(100) + | ||
| "</body></html>\n\n"; | ||
| const result = extractErrorDetail(htmlPage, "fallback"); | ||
| expect(result.length).toBeLessThanOrEqual(503); // 500 chars + "..." | ||
| expect(result.startsWith("<!DOCTYPE html>")).toBe(true); | ||
| expect(result.endsWith("...")).toBe(true); | ||
| }); | ||
| }); | ||
| //# sourceMappingURL=errors.test.js.map |
| {"version":3,"file":"errors.test.js","sourceRoot":"","sources":["../../src/__tests__/errors.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,aAAa,EACb,WAAW,EACX,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,oBAAoB,CAAC;AAE5B,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,GAAG,GAAG,IAAI,gBAAgB,CAAC,GAAG,EAAE,WAAW,EAAE,kBAAkB,CAAC,CAAC;QACvE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CACtB,yDAAyD,CAC1D,CAAC;QACF,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,GAAG,GAAG,IAAI,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;QACtD,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACjC,MAAM,GAAG,GAAG,IAAI,gBAAgB,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QACrD,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;IACpC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;IACnC,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,GAAG,GAAG,IAAI,mBAAmB,CAAC,gBAAgB,CAAC,CAAC;QACtD,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;QAC7C,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QAC9C,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,GAAG,GAAG,IAAI,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAC5C,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,GAAG,GAAG,IAAI,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAC5C,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,mCAAmC,EAAE,GAAG,EAAE;QAC3C,MAAM,GAAG,GAAG,IAAI,cAAc,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,CAAC;QACtE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAC5C,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;QACnD,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,MAAM,GAAG,GAAG,IAAI,cAAc,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC5C,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;QACxC,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAC/B,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,gBAAgB,EAAE,qBAAqB,CAAC,CAAC;QACzE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,qBAAqB,CAAC,CAAC;QACrD,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,yCAAyC,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC7C,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QACzC,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,GAAG,GAAG,IAAI,aAAa,CAAC,mBAAmB,EAAE,eAAe,CAAC,CAAC;QACpE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;QAC/C,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,MAAM,GAAG,GAAG,IAAI,aAAa,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAC3C,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QACvC,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,oCAAoC,EAAE,GAAG,EAAE;QAC5C,MAAM,GAAG,GAAG,IAAI,WAAW,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAC;QAChE,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;QAChD,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,GAAG,EAAE;QAChE,MAAM,GAAG,GAAG,IAAI,WAAW,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QACzC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACrC,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,4DAA4D,EAAE,GAAG,EAAE;QACpE,MAAM,MAAM,GAAG,oBAAoB,CAAC,GAAG,EAAE,sCAAsC,EAAE,gBAAgB,CAAC,CAAC;QACnG,MAAM,MAAM,GAAG,oBAAoB,CAAC,GAAG,EAAE,sCAAsC,EAAE,gBAAgB,CAAC,CAAC;QACnG,MAAM,CAAC,MAAM,CAAC,CAAC,cAAc,CAAC,mBAAmB,CAAC,CAAC;QACnD,MAAM,CAAC,MAAM,CAAC,CAAC,cAAc,CAAC,mBAAmB,CAAC,CAAC;QACnD,2EAA2E;QAC3E,kEAAkE;QAClE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,mBAAmB,CAAC,gBAAgB,CAAC,CAAC,OAAO,CAAC,CAAC;QAC/E,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,eAAe,CAAC,CAAC;IACxD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,GAAG,GAAG,oBAAoB,CAAC,GAAG,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;QAC3D,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC;QAC3C,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,GAAG,GAAG,oBAAoB,CAAC,GAAG,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC;QAC5D,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC,eAAe,CAAC,CAAC;QAC5C,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,GAAG,GAAG,oBAAoB,CAAC,GAAG,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC;QAC9D,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;QAC1C,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,CAAC,oBAAoB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;QAC9E,MAAM,CAAC,oBAAoB,CAAC,GAAG,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;QACrF,MAAM,CAAC,oBAAoB,CAAC,GAAG,EAAE,aAAa,EAAE,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;IACvF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+DAA+D,EAAE,GAAG,EAAE;QACvE,MAAM,GAAG,GAAG,oBAAoB,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;QACxD,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,CAAC,gBAAgB,CAAC,CAAC;QAC7C,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,mBAAmB,CAAC,CAAC;QACpD,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,cAAc,CAAC,CAAC;QAC/C,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,eAAe,CAAC,CAAC;QAChD,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;QAC9C,MAAM,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;QAC5C,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,0BAA0B,CAAC,CAAC;QAC1D,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,oBAAoB,EAAE,GAAG,EAAE;IAClC,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,CAAC,kBAAkB,CAAC,iCAAiC,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;IACvG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0EAA0E,EAAE,GAAG,EAAE;QAClF,MAAM,CAAC,kBAAkB,CAAC,gCAAgC,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IACnG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,MAAM,GAAG,kBAAkB,CAAC,uDAAuD,EAAE,UAAU,CAAC,CAAC;QACvG,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;QAC9C,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,kBAAkB,CAAC,gBAAgB,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAClF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,CAAC,kBAAkB,CAAC,EAAE,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,GAAG,EAAE;QAC3D,MAAM,QAAQ,GACZ,yFAAyF;YACzF,QAAQ;YACR,kBAAkB,CAAC,MAAM,CAAC,GAAG,CAAC;YAC9B,oBAAoB,CAAC;QACvB,MAAM,MAAM,GAAG,kBAAkB,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;QACxD,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,mBAAmB,CAAC,GAAG,CAAC,CAAC,CAAC,oBAAoB;QACpE,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxD,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"} |
| export {}; |
| import { describe, it, expect, beforeAll, afterAll } from "vitest"; | ||
| import { writeFile, mkdtemp, rm } from "node:fs/promises"; | ||
| import { tmpdir } from "node:os"; | ||
| import { join } from "node:path"; | ||
| import { fileToDataUri, SUPPORTED_IMAGE_EXTENSIONS } from "../utils/image.js"; | ||
| describe("fileToDataUri", () => { | ||
| let dir; | ||
| const pngBytes = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); | ||
| beforeAll(async () => { | ||
| dir = await mkdtemp(join(tmpdir(), "substack-image-")); | ||
| }); | ||
| afterAll(async () => { | ||
| await rm(dir, { recursive: true, force: true }); | ||
| }); | ||
| it("encodes a png file as a data URI with the right MIME type", async () => { | ||
| const path = join(dir, "pic.png"); | ||
| await writeFile(path, pngBytes); | ||
| const uri = await fileToDataUri(path); | ||
| expect(uri).toBe(`data:image/png;base64,${pngBytes.toString("base64")}`); | ||
| }); | ||
| it("maps .jpg and .jpeg to image/jpeg", async () => { | ||
| const jpg = join(dir, "a.jpg"); | ||
| const jpeg = join(dir, "b.jpeg"); | ||
| await writeFile(jpg, pngBytes); | ||
| await writeFile(jpeg, pngBytes); | ||
| expect(await fileToDataUri(jpg)).toMatch(/^data:image\/jpeg;base64,/); | ||
| expect(await fileToDataUri(jpeg)).toMatch(/^data:image\/jpeg;base64,/); | ||
| }); | ||
| it("is case-insensitive on the extension", async () => { | ||
| const path = join(dir, "PIC.PNG"); | ||
| await writeFile(path, pngBytes); | ||
| expect(await fileToDataUri(path)).toMatch(/^data:image\/png;base64,/); | ||
| }); | ||
| it("throws on an unsupported extension", async () => { | ||
| const path = join(dir, "notes.txt"); | ||
| await writeFile(path, "hello"); | ||
| await expect(fileToDataUri(path)).rejects.toThrow(/Unsupported image file extension/); | ||
| }); | ||
| it("throws when the file does not exist", async () => { | ||
| await expect(fileToDataUri(join(dir, "missing.png"))).rejects.toThrow(); | ||
| }); | ||
| it("exposes the supported extension list", () => { | ||
| expect(SUPPORTED_IMAGE_EXTENSIONS).toContain(".png"); | ||
| expect(SUPPORTED_IMAGE_EXTENSIONS).toContain(".webp"); | ||
| }); | ||
| }); | ||
| //# sourceMappingURL=image.test.js.map |
| {"version":3,"file":"image.test.js","sourceRoot":"","sources":["../../src/__tests__/image.test.ts"],"names":[],"mappings":"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"} |
| export {}; |
| import { describe, it, expect } from "vitest"; | ||
| import { markdownToProseMirror, markdownToProseMirrorContent, parseInline, } from "../utils/markdown-to-prosemirror.js"; | ||
| /** Helper: parse markdown and return the doc object */ | ||
| function parse(md) { | ||
| return JSON.parse(markdownToProseMirror(md)); | ||
| } | ||
| describe("markdownToProseMirror", () => { | ||
| describe("paragraphs", () => { | ||
| it("converts plain text to a paragraph node", () => { | ||
| const doc = parse("Hello world"); | ||
| expect(doc.type).toBe("doc"); | ||
| expect(doc.content).toHaveLength(1); | ||
| expect(doc.content[0].type).toBe("paragraph"); | ||
| expect(doc.content[0].content[0]).toEqual({ | ||
| type: "text", | ||
| text: "Hello world", | ||
| }); | ||
| }); | ||
| it("returns an empty paragraph for blank input", () => { | ||
| const doc = parse(""); | ||
| expect(doc.content).toEqual([{ type: "paragraph" }]); | ||
| }); | ||
| }); | ||
| describe("headings", () => { | ||
| it.each([1, 2, 3, 4, 5, 6])("converts h%i heading", (level) => { | ||
| const hashes = "#".repeat(level); | ||
| const doc = parse(`${hashes} Heading ${level}`); | ||
| const node = doc.content[0]; | ||
| expect(node.type).toBe("heading"); | ||
| expect(node.attrs.level).toBe(level); | ||
| expect(node.content[0].text).toBe(`Heading ${level}`); | ||
| }); | ||
| }); | ||
| describe("code blocks", () => { | ||
| it("converts fenced code block with language", () => { | ||
| const md = "```typescript\nconst x = 1;\n```"; | ||
| const doc = parse(md); | ||
| const node = doc.content[0]; | ||
| expect(node.type).toBe("code_block"); | ||
| expect(node.attrs).toEqual({ lang: "typescript" }); | ||
| expect(node.content[0].text).toBe("const x = 1;"); | ||
| }); | ||
| it("converts fenced code block without language", () => { | ||
| const md = "```\nsome code\n```"; | ||
| const doc = parse(md); | ||
| const node = doc.content[0]; | ||
| expect(node.type).toBe("code_block"); | ||
| expect(node.attrs).toBeUndefined(); | ||
| expect(node.content[0].text).toBe("some code"); | ||
| }); | ||
| }); | ||
| describe("blockquotes", () => { | ||
| it("converts > lines to a blockquote node", () => { | ||
| const doc = parse("> This is a quote"); | ||
| const node = doc.content[0]; | ||
| expect(node.type).toBe("blockquote"); | ||
| expect(node.content[0].type).toBe("paragraph"); | ||
| expect(node.content[0].content[0].text).toBe("This is a quote"); | ||
| }); | ||
| it("joins consecutive > lines", () => { | ||
| const doc = parse("> Line one\n> Line two"); | ||
| const text = doc.content[0].content[0].content[0].text; | ||
| expect(text).toBe("Line one Line two"); | ||
| }); | ||
| }); | ||
| describe("ordered lists", () => { | ||
| it("converts numbered items to ordered_list with list_items", () => { | ||
| const md = "1. First\n2. Second\n3. Third"; | ||
| const doc = parse(md); | ||
| const node = doc.content[0]; | ||
| expect(node.type).toBe("ordered_list"); | ||
| expect(node.content).toHaveLength(3); | ||
| node.content.forEach((item) => { | ||
| expect(item.type).toBe("list_item"); | ||
| expect(item.content[0].type).toBe("paragraph"); | ||
| }); | ||
| expect(node.content[0].content[0].content[0].text).toBe("First"); | ||
| expect(node.content[2].content[0].content[0].text).toBe("Third"); | ||
| }); | ||
| }); | ||
| describe("unordered lists", () => { | ||
| it("converts - items to bullet_list with list_items", () => { | ||
| const md = "- Alpha\n- Beta"; | ||
| const doc = parse(md); | ||
| const node = doc.content[0]; | ||
| expect(node.type).toBe("bullet_list"); | ||
| expect(node.content).toHaveLength(2); | ||
| expect(node.content[0].type).toBe("list_item"); | ||
| expect(node.content[0].content[0].content[0].text).toBe("Alpha"); | ||
| }); | ||
| }); | ||
| describe("nested lists", () => { | ||
| it("nests a deeper-indented bullet inside the parent list_item", () => { | ||
| const md = "- Parent\n - Child\n- Sibling"; | ||
| const doc = parse(md); | ||
| const list = doc.content[0]; | ||
| expect(doc.content).toHaveLength(1); | ||
| expect(list.type).toBe("bullet_list"); | ||
| expect(list.content).toHaveLength(2); // Parent, Sibling | ||
| const parent = list.content[0]; | ||
| expect(parent.content[0].content[0].text).toBe("Parent"); | ||
| // Parent's second child is the nested list | ||
| const nested = parent.content[1]; | ||
| expect(nested.type).toBe("bullet_list"); | ||
| expect(nested.content).toHaveLength(1); | ||
| expect(nested.content[0].content[0].content[0].text).toBe("Child"); | ||
| // Sibling stays at the top level, no nested list | ||
| expect(list.content[1].content[0].content[0].text).toBe("Sibling"); | ||
| expect(list.content[1].content).toHaveLength(1); | ||
| }); | ||
| it("nests to arbitrary depth", () => { | ||
| const md = "- L1\n - L2\n - L3"; | ||
| const doc = parse(md); | ||
| const l1Item = doc.content[0].content[0]; | ||
| const l2 = l1Item.content[1]; | ||
| expect(l2.type).toBe("bullet_list"); | ||
| const l2Item = l2.content[0]; | ||
| const l3 = l2Item.content[1]; | ||
| expect(l3.type).toBe("bullet_list"); | ||
| expect(l3.content[0].content[0].content[0].text).toBe("L3"); | ||
| }); | ||
| it("nests an ordered list inside a bullet item (mixed types)", () => { | ||
| const md = "- Bullet\n 1. One\n 2. Two"; | ||
| const doc = parse(md); | ||
| const bulletItem = doc.content[0].content[0]; | ||
| const nested = bulletItem.content[1]; | ||
| expect(nested.type).toBe("ordered_list"); | ||
| expect(nested.content).toHaveLength(2); | ||
| expect(nested.content[1].content[0].content[0].text).toBe("Two"); | ||
| }); | ||
| it("starts a new sibling list when the marker type flips at the same level", () => { | ||
| const md = "- Bullet\n1. Number"; | ||
| const doc = parse(md); | ||
| expect(doc.content).toHaveLength(2); | ||
| expect(doc.content[0].type).toBe("bullet_list"); | ||
| expect(doc.content[1].type).toBe("ordered_list"); | ||
| }); | ||
| it("treats 4-space indentation as one nesting level", () => { | ||
| const md = "- Parent\n - Child"; | ||
| const doc = parse(md); | ||
| const parent = doc.content[0].content[0]; | ||
| expect(parent.content[1].type).toBe("bullet_list"); | ||
| expect(parent.content[1].content[0].content[0].content[0].text).toBe("Child"); | ||
| }); | ||
| }); | ||
| describe("tables (Substack has no table node — code_block fallback)", () => { | ||
| it("preserves a GFM table verbatim inside a code_block", () => { | ||
| const md = "| A | B |\n| --- | --- |\n| 1 | 2 |"; | ||
| const doc = parse(md); | ||
| expect(doc.content).toHaveLength(1); | ||
| const node = doc.content[0]; | ||
| expect(node.type).toBe("code_block"); | ||
| expect(node.content[0].text).toBe(md); | ||
| }); | ||
| it("supports alignment colons in the delimiter row", () => { | ||
| const md = "| L | R |\n|:---|---:|\n| a | b |"; | ||
| const doc = parse(md); | ||
| expect(doc.content[0].type).toBe("code_block"); | ||
| expect(doc.content[0].content[0].text).toContain(":---"); | ||
| }); | ||
| it("separates a preceding paragraph from the table", () => { | ||
| const doc = parse("Intro line\n| A | B |\n| --- | --- |\n| 1 | 2 |"); | ||
| expect(doc.content).toHaveLength(2); | ||
| expect(doc.content[0].type).toBe("paragraph"); | ||
| expect(doc.content[0].content[0].text).toBe("Intro line"); | ||
| expect(doc.content[1].type).toBe("code_block"); | ||
| }); | ||
| it("does not treat a paragraph containing a lone pipe as a table", () => { | ||
| const doc = parse("a | b is just prose"); | ||
| expect(doc.content[0].type).toBe("paragraph"); | ||
| }); | ||
| }); | ||
| describe("nested list robustness (no data loss)", () => { | ||
| it("keeps a leading over-indented item as its own top-level list", () => { | ||
| // Regression: the old min-indent recursion silently dropped a leading | ||
| // deeper-indented item. Nothing may be lost. | ||
| const doc = parse(" - deep first\n- shallow"); | ||
| const json = JSON.stringify(doc); | ||
| expect(json).toContain("deep first"); | ||
| expect(json).toContain("shallow"); | ||
| }); | ||
| it("preserves every item when indentation jumps 0 -> 4 -> 2", () => { | ||
| const doc = parse("- a\n - b\n - c"); | ||
| const json = JSON.stringify(doc); | ||
| expect(json).toContain('"a"'); | ||
| expect(json).toContain('"b"'); | ||
| expect(json).toContain('"c"'); | ||
| }); | ||
| }); | ||
| describe("paragraph termination", () => { | ||
| it("does not swallow a following h4-h6 heading into the paragraph", () => { | ||
| const doc = parse("Some text\n#### Deep heading"); | ||
| expect(doc.content).toHaveLength(2); | ||
| expect(doc.content[0].type).toBe("paragraph"); | ||
| expect(doc.content[0].content[0].text).toBe("Some text"); | ||
| expect(doc.content[1].type).toBe("heading"); | ||
| expect(doc.content[1].attrs.level).toBe(4); | ||
| }); | ||
| it("consumes an indented heading (no hang) and emits a heading node", () => { | ||
| const doc = parse(" # Indented heading"); | ||
| expect(doc.content).toHaveLength(1); | ||
| expect(doc.content[0].type).toBe("heading"); | ||
| expect(doc.content[0].attrs.level).toBe(1); | ||
| expect(doc.content[0].content[0].text).toBe("Indented heading"); | ||
| }); | ||
| it("consumes an indented image line (no hang) and emits captionedImage", () => { | ||
| const doc = parse(" "); | ||
| expect(doc.content).toHaveLength(1); | ||
| expect(doc.content[0].type).toBe("captionedImage"); | ||
| expect(doc.content[0].content[0].attrs.src).toBe("https://example.com/y.png"); | ||
| }); | ||
| it("separates a paragraph from a following indented heading", () => { | ||
| const doc = parse("intro\n ## Sub"); | ||
| expect(doc.content).toHaveLength(2); | ||
| expect(doc.content[0].type).toBe("paragraph"); | ||
| expect(doc.content[1].type).toBe("heading"); | ||
| expect(doc.content[1].attrs.level).toBe(2); | ||
| }); | ||
| }); | ||
| describe("horizontal rules", () => { | ||
| it("converts --- to horizontal_rule", () => { | ||
| const doc = parse("---"); | ||
| expect(doc.content[0]).toEqual({ type: "horizontal_rule" }); | ||
| }); | ||
| }); | ||
| describe("images", () => { | ||
| it("wraps a standalone image in captionedImage > image2 with a caption", () => { | ||
| const doc = parse(""); | ||
| const node = doc.content[0]; | ||
| expect(node.type).toBe("captionedImage"); | ||
| const [image, caption] = node.content; | ||
| expect(image.type).toBe("image2"); | ||
| expect(image.attrs.src).toBe("https://example.com/img.png"); | ||
| expect(image.attrs.alt).toBe("Alt text"); | ||
| expect(caption.type).toBe("caption"); | ||
| expect(caption.content[0].text).toBe("Alt text"); | ||
| }); | ||
| it("omits the caption node and nulls alt for empty alt text", () => { | ||
| const doc = parse(""); | ||
| const node = doc.content[0]; | ||
| expect(node.type).toBe("captionedImage"); | ||
| expect(node.content).toHaveLength(1); | ||
| expect(node.content[0].type).toBe("image2"); | ||
| expect(node.content[0].attrs.alt).toBeNull(); | ||
| }); | ||
| it("parses width/height and MIME from a Substack CDN filename", () => { | ||
| const doc = parse(""); | ||
| const image = doc.content[0].content[0]; | ||
| expect(image.attrs.width).toBe(1265); | ||
| expect(image.attrs.height).toBe(5808); | ||
| expect(image.attrs.resizeWidth).toBe(1265); | ||
| expect(image.attrs.type).toBe("image/png"); | ||
| }); | ||
| it("falls back to null dimensions for a non-CDN URL", () => { | ||
| const doc = parse(""); | ||
| const image = doc.content[0].content[0]; | ||
| expect(image.attrs.width).toBeNull(); | ||
| expect(image.attrs.height).toBeNull(); | ||
| expect(image.attrs.type).toBeNull(); | ||
| }); | ||
| it("does not read an aspect-ratio filename on a non-CDN URL as dimensions", () => { | ||
| // `hero_16x9.jpg` matches the `_WxH_` shape but is an aspect-ratio label, | ||
| // not pixel dimensions — only Substack-hosted URLs use it for real dims. | ||
| const doc = parse(""); | ||
| const image = doc.content[0].content[0]; | ||
| expect(image.attrs.width).toBeNull(); | ||
| expect(image.attrs.height).toBeNull(); | ||
| expect(image.attrs.type).toBeNull(); | ||
| }); | ||
| it("parses dimensions from a substackcdn.com fetch URL", () => { | ||
| const doc = parse(""); | ||
| const image = doc.content[0].content[0]; | ||
| expect(image.attrs.width).toBe(800); | ||
| expect(image.attrs.height).toBe(600); | ||
| expect(image.attrs.type).toBe("image/png"); | ||
| }); | ||
| }); | ||
| }); | ||
| describe("parseInline", () => { | ||
| it("parses bold text", () => { | ||
| const nodes = parseInline("**bold**"); | ||
| expect(nodes).toEqual([ | ||
| { type: "text", text: "bold", marks: [{ type: "bold" }] }, | ||
| ]); | ||
| }); | ||
| it("parses italic text", () => { | ||
| const nodes = parseInline("*italic*"); | ||
| expect(nodes).toEqual([ | ||
| { type: "text", text: "italic", marks: [{ type: "italic" }] }, | ||
| ]); | ||
| }); | ||
| it("parses inline code", () => { | ||
| const nodes = parseInline("`code`"); | ||
| expect(nodes).toEqual([ | ||
| { type: "text", text: "code", marks: [{ type: "code" }] }, | ||
| ]); | ||
| }); | ||
| it("parses links", () => { | ||
| const nodes = parseInline("[click](https://example.com)"); | ||
| expect(nodes).toEqual([ | ||
| { | ||
| type: "text", | ||
| text: "click", | ||
| marks: [{ type: "link", attrs: { href: "https://example.com" } }], | ||
| }, | ||
| ]); | ||
| }); | ||
| it("parses mixed inline: bold + italic + link", () => { | ||
| const nodes = parseInline("Hello **bold** and *italic* and [link](https://x.com)"); | ||
| expect(nodes[0]).toEqual({ type: "text", text: "Hello " }); | ||
| expect(nodes[1]).toEqual({ | ||
| type: "text", | ||
| text: "bold", | ||
| marks: [{ type: "bold" }], | ||
| }); | ||
| expect(nodes[2]).toEqual({ type: "text", text: " and " }); | ||
| expect(nodes[3]).toEqual({ | ||
| type: "text", | ||
| text: "italic", | ||
| marks: [{ type: "italic" }], | ||
| }); | ||
| expect(nodes[4]).toEqual({ type: "text", text: " and " }); | ||
| expect(nodes[5]).toEqual({ | ||
| type: "text", | ||
| text: "link", | ||
| marks: [{ type: "link", attrs: { href: "https://x.com" } }], | ||
| }); | ||
| }); | ||
| it("returns plain text node for text with no formatting", () => { | ||
| const nodes = parseInline("just plain text"); | ||
| expect(nodes).toEqual([{ type: "text", text: "just plain text" }]); | ||
| }); | ||
| }); | ||
| describe("markdownToProseMirrorContent", () => { | ||
| it("returns just the content array without doc wrapper", () => { | ||
| const content = markdownToProseMirrorContent("Hello world"); | ||
| expect(Array.isArray(content)).toBe(true); | ||
| expect(content).toHaveLength(1); | ||
| expect(content[0].type).toBe("paragraph"); | ||
| }); | ||
| }); | ||
| //# sourceMappingURL=markdown-to-prosemirror.test.js.map |
| {"version":3,"file":"markdown-to-prosemirror.test.js","sourceRoot":"","sources":["../../src/__tests__/markdown-to-prosemirror.test.ts"],"names":[],"mappings":"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"} |
| export {}; |
| import { describe, it, expect } from "vitest"; | ||
| import { resolveCredentials } from "../auth/resolve-credentials.js"; | ||
| const stored = { | ||
| publicationUrl: "https://stored.substack.com", | ||
| sessionToken: "stored-tok", | ||
| userId: "99", | ||
| savedAt: "2026-01-01T00:00:00Z", | ||
| }; | ||
| describe("resolveCredentials", () => { | ||
| it("prefers env vars when present (source=env)", () => { | ||
| const env = { | ||
| SUBSTACK_PUBLICATION_URL: "https://env.substack.com", | ||
| SUBSTACK_SESSION_TOKEN: "env-tok", | ||
| SUBSTACK_USER_ID: "1", | ||
| }; | ||
| const r = resolveCredentials(env, () => stored); | ||
| expect(r.publicationUrl).toBe("https://env.substack.com"); | ||
| expect(r.sessionToken).toBe("env-tok"); | ||
| expect(r.userId).toBe("1"); | ||
| expect(r.source).toBe("env"); | ||
| expect(r.missing).toEqual([]); | ||
| }); | ||
| it("falls back to the stored session when env vars are absent (source=stored)", () => { | ||
| const r = resolveCredentials({}, () => stored); | ||
| expect(r.publicationUrl).toBe("https://stored.substack.com"); | ||
| expect(r.sessionToken).toBe("stored-tok"); | ||
| expect(r.userId).toBe("99"); | ||
| expect(r.source).toBe("stored"); | ||
| expect(r.missing).toEqual([]); | ||
| }); | ||
| it("mixes per field, env winning each present field (source=mixed)", () => { | ||
| const env = { SUBSTACK_SESSION_TOKEN: "env-tok" }; | ||
| const r = resolveCredentials(env, () => stored); | ||
| expect(r.sessionToken).toBe("env-tok"); // env | ||
| expect(r.publicationUrl).toBe("https://stored.substack.com"); // store | ||
| expect(r.userId).toBe("99"); // store | ||
| expect(r.source).toBe("mixed"); | ||
| expect(r.missing).toEqual([]); | ||
| }); | ||
| it("reports all missing when neither env nor store supplies them (source=none)", () => { | ||
| const r = resolveCredentials({}, () => null); | ||
| expect(r.missing).toEqual([ | ||
| "SUBSTACK_PUBLICATION_URL", | ||
| "SUBSTACK_SESSION_TOKEN", | ||
| "SUBSTACK_USER_ID", | ||
| ]); | ||
| expect(r.source).toBe("none"); | ||
| expect(r.publicationUrl).toBe(""); | ||
| }); | ||
| it("reports only still-missing fields with a partial store", () => { | ||
| const partial = { ...stored, userId: "" }; | ||
| const r = resolveCredentials({}, () => partial); | ||
| expect(r.missing).toEqual(["SUBSTACK_USER_ID"]); | ||
| expect(r.source).toBe("stored"); | ||
| }); | ||
| }); | ||
| //# sourceMappingURL=resolve-credentials.test.js.map |
| {"version":3,"file":"resolve-credentials.test.js","sourceRoot":"","sources":["../../src/__tests__/resolve-credentials.test.ts"],"names":[],"mappings":"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"} |
| export {}; |
| import { describe, it, expect, vi, afterEach } from "vitest"; | ||
| import { Client } from "@modelcontextprotocol/sdk/client/index.js"; | ||
| import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js"; | ||
| import { createServer } from "../server.js"; | ||
| import { SubstackClient, MAX_PAGE_SIZE, ANALYTICS_SCAN_DEPTH, } from "../api/client.js"; | ||
| /** | ||
| * Tool-layer regressions for #28. | ||
| * | ||
| * The clamp and the `describe` strings live in server.ts, so a client-only test | ||
| * cannot reach them — and the description is the load-bearing half: it is what | ||
| * tells a model that limit=100 is a legal argument. These drive the real | ||
| * registered tools over an in-memory MCP transport and assert the limit VALUE | ||
| * that reaches the wire. | ||
| */ | ||
| describe("tool pagination limits (regression: #28)", () => { | ||
| afterEach(() => vi.unstubAllGlobals()); | ||
| function stubFetch() { | ||
| const body = { posts: [], total: 0 }; | ||
| const fetchMock = vi.fn(async (..._args) => ({ | ||
| ok: true, | ||
| status: 200, | ||
| json: async () => body, | ||
| text: async () => JSON.stringify(body), | ||
| })); | ||
| vi.stubGlobal("fetch", fetchMock); | ||
| return fetchMock; | ||
| } | ||
| async function connect() { | ||
| const server = createServer(new SubstackClient("https://example.substack.com", "tok", "1")); | ||
| const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); | ||
| const client = new Client({ name: "test", version: "0.0.0" }); | ||
| await Promise.all([ | ||
| client.connect(clientTransport), | ||
| server.connect(serverTransport), | ||
| ]); | ||
| return client; | ||
| } | ||
| function limitOf(fetchMock) { | ||
| const url = new URL(String(fetchMock.mock.calls[0][0])); | ||
| return Number(url.searchParams.get("limit")); | ||
| } | ||
| const paginatedTools = [ | ||
| "list_published_posts", | ||
| "list_drafts", | ||
| "list_scheduled_posts", | ||
| ]; | ||
| for (const name of paginatedTools) { | ||
| it(`${name} clamps an over-cap limit instead of sending it`, async () => { | ||
| const fetchMock = stubFetch(); | ||
| const client = await connect(); | ||
| // A caller (or a model reading the old "1-100" description) passing 100 | ||
| // must not error — it gets clamped, not rejected. | ||
| const result = await client.callTool({ | ||
| name, | ||
| arguments: { limit: 100 }, | ||
| }); | ||
| expect(result.isError).toBeFalsy(); | ||
| expect(limitOf(fetchMock)).toBe(MAX_PAGE_SIZE); | ||
| expect(limitOf(fetchMock)).toBeLessThanOrEqual(MAX_PAGE_SIZE); | ||
| }); | ||
| it(`${name} passes an under-cap limit through unchanged`, async () => { | ||
| const fetchMock = stubFetch(); | ||
| const client = await connect(); | ||
| await client.callTool({ name, arguments: { limit: 7 } }); | ||
| expect(limitOf(fetchMock)).toBe(7); | ||
| }); | ||
| it(`${name} advertises the real cap, not 100`, async () => { | ||
| const client = await connect(); | ||
| const { tools } = await client.listTools(); | ||
| const tool = tools.find((t) => t.name === name); | ||
| expect(tool).toBeDefined(); | ||
| const described = String(tool.inputSchema.properties?.limit?.description ?? ""); | ||
| expect(described).toContain(`1-${MAX_PAGE_SIZE}`); | ||
| expect(described).not.toContain("1-100"); | ||
| }); | ||
| } | ||
| it("get_post_analytics derives its scan depth rather than restating 500", async () => { | ||
| const client = await connect(); | ||
| const { tools } = await client.listTools(); | ||
| const tool = tools.find((t) => t.name === "get_post_analytics"); | ||
| expect(tool?.description).toContain(`${ANALYTICS_SCAN_DEPTH} most recent published posts`); | ||
| }); | ||
| it("get_post_analytics reports the same depth in its not-found note", async () => { | ||
| stubFetch(); // empty feed: the id is never found | ||
| const client = await connect(); | ||
| const result = await client.callTool({ | ||
| name: "get_post_analytics", | ||
| arguments: { post_id: 12345 }, | ||
| }); | ||
| const payload = JSON.parse(result.content[0].text); | ||
| expect(payload.found).toBe(false); | ||
| expect(payload.note).toContain(`${ANALYTICS_SCAN_DEPTH} most recent published posts`); | ||
| }); | ||
| }); | ||
| //# sourceMappingURL=server.test.js.map |
| {"version":3,"file":"server.test.js","sourceRoot":"","sources":["../../src/__tests__/server.test.ts"],"names":[],"mappings":"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"} |
| export {}; |
| import { describe, it, expect, beforeEach, afterEach } from "vitest"; | ||
| import { mkdtempSync, rmSync, writeFileSync, readFileSync, existsSync, } from "node:fs"; | ||
| import { tmpdir } from "node:os"; | ||
| import { join } from "node:path"; | ||
| import { saveSession, loadSession, clearSession, sessionDir, } from "../auth/session-store.js"; | ||
| let dir; | ||
| const original = process.env.SUBSTACK_MCP_HOME; | ||
| beforeEach(() => { | ||
| dir = mkdtempSync(join(tmpdir(), "substack-mcp-test-")); | ||
| process.env.SUBSTACK_MCP_HOME = dir; | ||
| }); | ||
| afterEach(() => { | ||
| if (original === undefined) | ||
| delete process.env.SUBSTACK_MCP_HOME; | ||
| else | ||
| process.env.SUBSTACK_MCP_HOME = original; | ||
| rmSync(dir, { recursive: true, force: true }); | ||
| }); | ||
| describe("session store", () => { | ||
| it("round-trips a saved session", () => { | ||
| saveSession({ | ||
| publicationUrl: "https://x.substack.com", | ||
| sessionToken: "tok", | ||
| userId: "42", | ||
| }); | ||
| const loaded = loadSession(); | ||
| expect(loaded?.publicationUrl).toBe("https://x.substack.com"); | ||
| expect(loaded?.sessionToken).toBe("tok"); | ||
| expect(loaded?.userId).toBe("42"); | ||
| expect(typeof loaded?.savedAt).toBe("string"); | ||
| }); | ||
| it("returns null when no session file exists", () => { | ||
| expect(loadSession()).toBeNull(); | ||
| }); | ||
| it("does not write the token or URL in plaintext", () => { | ||
| saveSession({ | ||
| publicationUrl: "https://secret.substack.com", | ||
| sessionToken: "SUPERSECRET", | ||
| userId: "1", | ||
| }); | ||
| const raw = readFileSync(join(dir, "session.json"), "utf8"); | ||
| expect(raw).not.toContain("SUPERSECRET"); | ||
| expect(raw).not.toContain("secret.substack.com"); | ||
| }); | ||
| it("returns null when the ciphertext is tampered (GCM auth fails)", () => { | ||
| saveSession({ | ||
| publicationUrl: "https://x.substack.com", | ||
| sessionToken: "tok", | ||
| userId: "1", | ||
| }); | ||
| const file = join(dir, "session.json"); | ||
| const env = JSON.parse(readFileSync(file, "utf8")); | ||
| const buf = Buffer.from(env.data, "base64"); | ||
| buf[0] ^= 0xff; // flip a byte in the ciphertext | ||
| env.data = buf.toString("base64"); | ||
| writeFileSync(file, JSON.stringify(env)); | ||
| expect(loadSession()).toBeNull(); | ||
| }); | ||
| it("returns null for a corrupt, non-JSON file", () => { | ||
| writeFileSync(join(dir, "session.json"), "not json at all"); | ||
| expect(loadSession()).toBeNull(); | ||
| }); | ||
| it("returns null for an unknown envelope version", () => { | ||
| writeFileSync(join(dir, "session.json"), JSON.stringify({ v: 2, salt: "", iv: "", tag: "", data: "" })); | ||
| expect(loadSession()).toBeNull(); | ||
| }); | ||
| it("clearSession removes the file", () => { | ||
| saveSession({ publicationUrl: "u", sessionToken: "t", userId: "1" }); | ||
| expect(existsSync(join(dir, "session.json"))).toBe(true); | ||
| clearSession(); | ||
| expect(existsSync(join(dir, "session.json"))).toBe(false); | ||
| expect(loadSession()).toBeNull(); | ||
| }); | ||
| it("clearSession is a no-op when nothing is stored", () => { | ||
| expect(() => clearSession()).not.toThrow(); | ||
| }); | ||
| it("sessionDir honors SUBSTACK_MCP_HOME", () => { | ||
| expect(sessionDir()).toBe(dir); | ||
| }); | ||
| }); | ||
| //# sourceMappingURL=session-store.test.js.map |
| {"version":3,"file":"session-store.test.js","sourceRoot":"","sources":["../../src/__tests__/session-store.test.ts"],"names":[],"mappings":"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"} |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
259
5.71%7
-41.67%265760
-29.62%50
-32.43%2285
-33.44%