From ae91b32496c3d0f80d195e558621748d28103a2b Mon Sep 17 00:00:00 2001 From: ian nuttall <6681919+iannuttall@users.noreply.github.com> Date: Tue, 18 Aug 2026 12:55:06 +0100 Subject: [PATCH] fix(providers): accept npm maintainer strings Accept both abbreviated string entries and full object entries from npm package metadata. Keep the maintainer text as the displayed publisher when no author or npm user is present.\n\nRelease seo 0.2.37.\n\nFixes #62 --- .claude-plugin/plugin.json | 2 +- package.json | 2 +- .../src/provider-extensions/installer.test.ts | 20 +++++++++++++++++-- .../core/src/provider-extensions/installer.ts | 10 ++++++++-- 4 files changed, 28 insertions(+), 6 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 7ab71f21..5f9cb2aa 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "name": "seo", "displayName": "seo", "description": "Local-first SEO and AI-search diagnostics. Bundles the seo MCP server plus one SEO skill that gives an agent 50+ audit and report tools without filling its context window.", - "version": "0.2.36", + "version": "0.2.37", "author": { "name": "Ian Nuttall" }, diff --git a/package.json b/package.json index 9b8249c2..b1ebb743 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "seo", - "version": "0.2.36", + "version": "0.2.37", "description": "The SEO command for AI agents. Audit sites and research search opportunities with local, evidence-backed reports.", "type": "module", "license": "Apache-2.0", diff --git a/packages/core/src/provider-extensions/installer.test.ts b/packages/core/src/provider-extensions/installer.test.ts index a7419442..684f1d49 100644 --- a/packages/core/src/provider-extensions/installer.test.ts +++ b/packages/core/src/provider-extensions/installer.test.ts @@ -34,8 +34,9 @@ function command( input: { dependencies?: Record installedIntegrity?: string + maintainers?: Array metadataIntegrities?: string[] - npmUser?: string | { name: string } + npmUser?: string | { name: string } | null registerSecondProvider?: boolean unpackedSize?: number version?: string @@ -61,7 +62,9 @@ function command( type: 'git', url: 'git+https://github.com/example/fixture.git', }, - _npmUser: input.npmUser ?? 'example', + _npmUser: + input.npmUser === null ? undefined : (input.npmUser ?? 'example'), + maintainers: input.maintainers, dependencies: input.dependencies, dist: { integrity, @@ -188,6 +191,19 @@ test('direct install accepts an exact version and rejects ranges or URLs', async assert.equal(fake.calls.length, 1) }) +test('package inspection accepts string maintainers from npm metadata', async () => { + const fake = command({ + maintainers: ['Ian Nuttall '], + npmUser: null, + }) + const release = await inspectProviderPackage('@example/fixture', { + packagesDir, + run: fake.run, + }) + + assert.equal(release.publisher, 'Ian Nuttall ') +}) + test('package inspection rejects dependency trees before installation', async () => { const fake = command({ dependencies: { surprise: '^1.0.0' } }) await assert.rejects( diff --git a/packages/core/src/provider-extensions/installer.ts b/packages/core/src/provider-extensions/installer.ts index 3d5ecf6c..f36387e7 100644 --- a/packages/core/src/provider-extensions/installer.ts +++ b/packages/core/src/provider-extensions/installer.ts @@ -52,7 +52,12 @@ const npmPackageMetadataSchema = z ]) .nullish(), maintainers: z - .array(z.object({ name: z.string().trim().max(200) }).passthrough()) + .array( + z.union([ + z.string().trim().max(200), + z.object({ name: z.string().trim().max(200) }).passthrough(), + ]), + ) .max(100) .nullish(), _npmUser: z @@ -233,7 +238,8 @@ function publisher( if (metadata._npmUser?.name) return metadata._npmUser.name if (typeof metadata.author === 'string') return metadata.author if (metadata.author?.name) return metadata.author.name - return metadata.maintainers?.[0]?.name + const maintainer = metadata.maintainers?.[0] + return typeof maintainer === 'string' ? maintainer : maintainer?.name } async function packageMetadata(