From 86037899098b736ef43d3d487887034c949dde78 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 20:57:23 +0000 Subject: [PATCH 1/4] feat: add User-Agent header to all HTTP requests (DIS-41) Add User-Agent: opensea-cli/ header to all requests made by OpenSeaClient. Version is dynamically read from package.json using createRequire. Updates both get() and post() methods and corresponding test assertions. Co-Authored-By: Chris K --- src/client.ts | 7 +++++++ test/client.test.ts | 15 +++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/client.ts b/src/client.ts index 41eba54..4c8bebb 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,7 +1,12 @@ +import { createRequire } from "node:module" import type { OpenSeaClientConfig } from "./types/index.js" +const require = createRequire(import.meta.url) +const { version } = require("../package.json") as { version: string } + const DEFAULT_BASE_URL = "https://api.opensea.io" const DEFAULT_TIMEOUT_MS = 30_000 +const USER_AGENT = `opensea-cli/${version}` export class OpenSeaClient { private apiKey: string @@ -37,6 +42,7 @@ export class OpenSeaClient { method: "GET", headers: { Accept: "application/json", + "User-Agent": USER_AGENT, "x-api-key": this.apiKey, }, signal: AbortSignal.timeout(this.timeoutMs), @@ -71,6 +77,7 @@ export class OpenSeaClient { const headers: Record = { Accept: "application/json", + "User-Agent": USER_AGENT, "x-api-key": this.apiKey, } diff --git a/test/client.test.ts b/test/client.test.ts index d068dde..dd7cc0f 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -39,10 +39,11 @@ describe("OpenSeaClient", () => { "https://api.opensea.io/api/v2/test", expect.objectContaining({ method: "GET", - headers: { + headers: expect.objectContaining({ Accept: "application/json", + "User-Agent": expect.stringMatching(/^opensea-cli\/\d+/), "x-api-key": "test-key", - }, + }), }), ) expect(result).toEqual(mockResponse) @@ -93,10 +94,11 @@ describe("OpenSeaClient", () => { "https://api.opensea.io/api/v2/refresh", expect.objectContaining({ method: "POST", - headers: { + headers: expect.objectContaining({ Accept: "application/json", + "User-Agent": expect.stringMatching(/^opensea-cli\/\d+/), "x-api-key": "test-key", - }, + }), }), ) expect(result).toEqual(mockResponse) @@ -111,11 +113,12 @@ describe("OpenSeaClient", () => { "https://api.opensea.io/api/v2/create", expect.objectContaining({ method: "POST", - headers: { + headers: expect.objectContaining({ Accept: "application/json", "Content-Type": "application/json", + "User-Agent": expect.stringMatching(/^opensea-cli\/\d+/), "x-api-key": "test-key", - }, + }), body: JSON.stringify({ name: "test" }), }), ) From 4f09f4d8b7d131d92c64baf15e8fcb2468618601 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 21:02:36 +0000 Subject: [PATCH 2/4] fix: use stricter semver regex in User-Agent test assertions Co-Authored-By: Chris K --- test/client.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/client.test.ts b/test/client.test.ts index dd7cc0f..5fb3912 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -41,7 +41,7 @@ describe("OpenSeaClient", () => { method: "GET", headers: expect.objectContaining({ Accept: "application/json", - "User-Agent": expect.stringMatching(/^opensea-cli\/\d+/), + "User-Agent": expect.stringMatching(/^opensea-cli\/\d+\.\d+\.\d+/), "x-api-key": "test-key", }), }), @@ -96,7 +96,7 @@ describe("OpenSeaClient", () => { method: "POST", headers: expect.objectContaining({ Accept: "application/json", - "User-Agent": expect.stringMatching(/^opensea-cli\/\d+/), + "User-Agent": expect.stringMatching(/^opensea-cli\/\d+\.\d+\.\d+/), "x-api-key": "test-key", }), }), @@ -116,7 +116,7 @@ describe("OpenSeaClient", () => { headers: expect.objectContaining({ Accept: "application/json", "Content-Type": "application/json", - "User-Agent": expect.stringMatching(/^opensea-cli\/\d+/), + "User-Agent": expect.stringMatching(/^opensea-cli\/\d+\.\d+\.\d+/), "x-api-key": "test-key", }), body: JSON.stringify({ name: "test" }), From f0956075d0069df27fab36140637b32c16fc04e2 Mon Sep 17 00:00:00 2001 From: Chris Korhonen Date: Wed, 4 Mar 2026 11:25:07 -0500 Subject: [PATCH 3/4] refactor: use build-time version injection instead of createRequire Replace the runtime createRequire/package.json pattern with tsup's define option for compile-time version injection. Also anchors the test regex with $ to prevent false-positive matches. Co-Authored-By: Claude Opus 4.6 --- src/client.ts | 6 ++---- test/client.test.ts | 6 +++--- tsup.config.ts | 11 +++++++++++ vitest.config.ts | 8 ++++++++ 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/client.ts b/src/client.ts index 4c8bebb..c1da546 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,12 +1,10 @@ -import { createRequire } from "node:module" import type { OpenSeaClientConfig } from "./types/index.js" -const require = createRequire(import.meta.url) -const { version } = require("../package.json") as { version: string } +declare const __VERSION__: string const DEFAULT_BASE_URL = "https://api.opensea.io" const DEFAULT_TIMEOUT_MS = 30_000 -const USER_AGENT = `opensea-cli/${version}` +const USER_AGENT = `opensea-cli/${__VERSION__}` export class OpenSeaClient { private apiKey: string diff --git a/test/client.test.ts b/test/client.test.ts index 5fb3912..a4bf53b 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -41,7 +41,7 @@ describe("OpenSeaClient", () => { method: "GET", headers: expect.objectContaining({ Accept: "application/json", - "User-Agent": expect.stringMatching(/^opensea-cli\/\d+\.\d+\.\d+/), + "User-Agent": expect.stringMatching(/^opensea-cli\/\d+\.\d+\.\d+$/), "x-api-key": "test-key", }), }), @@ -96,7 +96,7 @@ describe("OpenSeaClient", () => { method: "POST", headers: expect.objectContaining({ Accept: "application/json", - "User-Agent": expect.stringMatching(/^opensea-cli\/\d+\.\d+\.\d+/), + "User-Agent": expect.stringMatching(/^opensea-cli\/\d+\.\d+\.\d+$/), "x-api-key": "test-key", }), }), @@ -116,7 +116,7 @@ describe("OpenSeaClient", () => { headers: expect.objectContaining({ Accept: "application/json", "Content-Type": "application/json", - "User-Agent": expect.stringMatching(/^opensea-cli\/\d+\.\d+\.\d+/), + "User-Agent": expect.stringMatching(/^opensea-cli\/\d+\.\d+\.\d+$/), "x-api-key": "test-key", }), body: JSON.stringify({ name: "test" }), diff --git a/tsup.config.ts b/tsup.config.ts index 2b0a0ea..aaf3f77 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -1,5 +1,10 @@ +import { readFileSync } from "node:fs" import { defineConfig } from "tsup" +const pkg = JSON.parse(readFileSync("./package.json", "utf-8")) as { + version: string +} + export default defineConfig([ { entry: { cli: "src/cli.ts" }, @@ -7,6 +12,9 @@ export default defineConfig([ clean: true, sourcemap: true, target: "node18", + define: { + __VERSION__: JSON.stringify(pkg.version), + }, banner: { js: "#!/usr/bin/env node", }, @@ -17,5 +25,8 @@ export default defineConfig([ dts: true, sourcemap: true, target: "node18", + define: { + __VERSION__: JSON.stringify(pkg.version), + }, }, ]) diff --git a/vitest.config.ts b/vitest.config.ts index 0de54ef..4bbf5f6 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,6 +1,14 @@ +import { readFileSync } from "node:fs" import { defineConfig } from "vitest/config" +const pkg = JSON.parse(readFileSync("./package.json", "utf-8")) as { + version: string +} + export default defineConfig({ + define: { + __VERSION__: JSON.stringify(pkg.version), + }, test: { coverage: { provider: "v8", From 48815aca00ef6aea00c96bfe49f44147d2d47955 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 16:26:06 +0000 Subject: [PATCH 4/4] refactor: address review feedback (DIS-41) - Replace createRequire with build-time __VERSION__ via tsup define - Add __VERSION__ define to vitest config for test compatibility - Anchor test regex with $ to prevent matching malformed versions - Extract private defaultHeaders getter to reduce header duplication Co-Authored-By: Chris K --- src/client.ts | 26 ++++++++++++-------------- test/client.test.ts | 6 +++--- tsup.config.ts | 9 +++++++++ vitest.config.ts | 8 ++++++++ 4 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/client.ts b/src/client.ts index 4c8bebb..270d799 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,12 +1,10 @@ -import { createRequire } from "node:module" import type { OpenSeaClientConfig } from "./types/index.js" -const require = createRequire(import.meta.url) -const { version } = require("../package.json") as { version: string } +declare const __VERSION__: string const DEFAULT_BASE_URL = "https://api.opensea.io" const DEFAULT_TIMEOUT_MS = 30_000 -const USER_AGENT = `opensea-cli/${version}` +const USER_AGENT = `opensea-cli/${__VERSION__}` export class OpenSeaClient { private apiKey: string @@ -23,6 +21,14 @@ export class OpenSeaClient { this.verbose = config.verbose ?? false } + private get defaultHeaders(): Record { + return { + Accept: "application/json", + "User-Agent": USER_AGENT, + "x-api-key": this.apiKey, + } + } + async get(path: string, params?: Record): Promise { const url = new URL(`${this.baseUrl}${path}`) @@ -40,11 +46,7 @@ export class OpenSeaClient { const response = await fetch(url.toString(), { method: "GET", - headers: { - Accept: "application/json", - "User-Agent": USER_AGENT, - "x-api-key": this.apiKey, - }, + headers: this.defaultHeaders, signal: AbortSignal.timeout(this.timeoutMs), }) @@ -75,11 +77,7 @@ export class OpenSeaClient { } } - const headers: Record = { - Accept: "application/json", - "User-Agent": USER_AGENT, - "x-api-key": this.apiKey, - } + const headers: Record = { ...this.defaultHeaders } if (body) { headers["Content-Type"] = "application/json" diff --git a/test/client.test.ts b/test/client.test.ts index 5fb3912..a4bf53b 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -41,7 +41,7 @@ describe("OpenSeaClient", () => { method: "GET", headers: expect.objectContaining({ Accept: "application/json", - "User-Agent": expect.stringMatching(/^opensea-cli\/\d+\.\d+\.\d+/), + "User-Agent": expect.stringMatching(/^opensea-cli\/\d+\.\d+\.\d+$/), "x-api-key": "test-key", }), }), @@ -96,7 +96,7 @@ describe("OpenSeaClient", () => { method: "POST", headers: expect.objectContaining({ Accept: "application/json", - "User-Agent": expect.stringMatching(/^opensea-cli\/\d+\.\d+\.\d+/), + "User-Agent": expect.stringMatching(/^opensea-cli\/\d+\.\d+\.\d+$/), "x-api-key": "test-key", }), }), @@ -116,7 +116,7 @@ describe("OpenSeaClient", () => { headers: expect.objectContaining({ Accept: "application/json", "Content-Type": "application/json", - "User-Agent": expect.stringMatching(/^opensea-cli\/\d+\.\d+\.\d+/), + "User-Agent": expect.stringMatching(/^opensea-cli\/\d+\.\d+\.\d+$/), "x-api-key": "test-key", }), body: JSON.stringify({ name: "test" }), diff --git a/tsup.config.ts b/tsup.config.ts index 2b0a0ea..18ed603 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -1,5 +1,12 @@ +import { readFileSync } from "node:fs" import { defineConfig } from "tsup" +const { version } = JSON.parse(readFileSync("package.json", "utf-8")) as { + version: string +} + +const define = { __VERSION__: JSON.stringify(version) } + export default defineConfig([ { entry: { cli: "src/cli.ts" }, @@ -7,6 +14,7 @@ export default defineConfig([ clean: true, sourcemap: true, target: "node18", + define, banner: { js: "#!/usr/bin/env node", }, @@ -17,5 +25,6 @@ export default defineConfig([ dts: true, sourcemap: true, target: "node18", + define, }, ]) diff --git a/vitest.config.ts b/vitest.config.ts index 0de54ef..0c62c20 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,6 +1,14 @@ +import { readFileSync } from "node:fs" import { defineConfig } from "vitest/config" +const { version } = JSON.parse(readFileSync("package.json", "utf-8")) as { + version: string +} + export default defineConfig({ + define: { + __VERSION__: JSON.stringify(version), + }, test: { coverage: { provider: "v8",