diff --git a/.changeset/bright-cats-explain.md b/.changeset/bright-cats-explain.md new file mode 100644 index 00000000..b5db273c --- /dev/null +++ b/.changeset/bright-cats-explain.md @@ -0,0 +1,5 @@ +--- +'onsystem': patch +--- + +Explain when a project has no design-system components available. diff --git a/src/server/index.ts b/src/server/index.ts index 93609249..50e379d3 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -17,6 +17,7 @@ import { capDiagnostics, importStatement, listComponents, + noComponentsMessage, type Listing, renderAmbiguous, renderCatalogList, @@ -484,6 +485,9 @@ export function createServer({ getDesignSystem, getProjects }: CreateServerOptio const matches = catalog.find(name); const [entry] = matches; if (!entry) { + if (!catalog.roots().length) { + return errorResult(noComponentsMessage(catalog.root, undefined, true)); + } const guess = catalog.suggest(name); const names = [...new Set(catalog.roots().map((e) => e.component.name))]; return errorResult( @@ -515,6 +519,12 @@ export function createServer({ getDesignSystem, getProjects }: CreateServerOptio const { ds } = found; const component = ds.getComponent(name); if (!component) { + if (!ds.roots().length) { + return { + isError: true, + content: [{ type: 'text', text: noComponentsMessage(ds.root, found.project?.dir) }], + }; + } const guess = ds.suggestComponent(name); return { isError: true, diff --git a/src/server/render.ts b/src/server/render.ts index 999af410..407a1b79 100644 --- a/src/server/render.ts +++ b/src/server/render.ts @@ -172,6 +172,15 @@ function listTail(listed: Listing): string[] { return lines; } +/** The shared explanation when a project has no design-system components to show. */ +export function noComponentsMessage(root: string, project?: string, monorepo = false): string { + if (monorepo) { + return `No design-system components found in any project of this monorepo (root: ${root}). Check the "components" globs in the config. Run \`onsystem inspect\` there to see what was looked at.`; + } + const where = project ? ` in ${project}` : ''; + return `No design-system components found${where}. Check the "components" globs in the config (root: ${root}).`; +} + /** `project`: the folder of a workspace root's project the list is for. */ export function renderComponentList( ds: DesignSystem, @@ -181,7 +190,7 @@ export function renderComponentList( const roots = ds.roots(); const where = project ? ` in ${project}` : ''; if (!roots.length) { - return `No components found${where}. Check the "components" globs in the config (root: ${ds.root}).`; + return noComponentsMessage(ds.root, project); } const listed = listComponents(roots, (c) => c, filter); if (!listed.matched) { @@ -240,7 +249,7 @@ const PICK_PROJECT = export function renderCatalogList(catalog: Catalog, filter: ListFilter = {}): string { const roots = catalog.roots(); if (!roots.length) { - return `No components found in any project of this monorepo (root: ${catalog.root}). Run \`onsystem inspect\` there to see what was looked at.`; + return noComponentsMessage(catalog.root, undefined, true); } const listed = listComponents( roots, diff --git a/test/design-system-packages-docs.test.ts b/test/design-system-packages-docs.test.ts index bed11c58..a5a9ae9b 100644 --- a/test/design-system-packages-docs.test.ts +++ b/test/design-system-packages-docs.test.ts @@ -305,12 +305,12 @@ describe('what apps get from a design-system package (docs/design-system-package ); expect(whatAppsGet).toContain(`\`check\` says \`${notice}\`\n`); // Agents find no component. - expect(await tool(app, 'list_components', {})).toMatch(/^No components found/); + expect(await tool(app, 'list_components', {})).toMatch(/^No design-system components found/); expect(await tool(app, 'search_components', { query: 'button' })).toMatch( /^No components match/, ); expect(await tool(app, 'get_component', { name: 'Button' })).toMatch( - /^No component named "Button"/, + /^No design-system components found/, ); // check_ui reports from the same rules. const checked = await tool(app, 'check_ui', { path: 'app/page.tsx' }); diff --git a/test/server.test.ts b/test/server.test.ts index 4191a64a..e49c1ac5 100644 --- a/test/server.test.ts +++ b/test/server.test.ts @@ -121,6 +121,25 @@ describe('MCP server over the in-memory transport', () => { expect(text(result)).toContain('Did you mean "Button"?'); }); + it('get_component says when the project has no components at all', async () => { + const ds = await load(fixture({ 'package.json': '{ "name": "plain" }' })); + const server = createServer({ getDesignSystem: () => Promise.resolve(ds) }); + const other = new Client({ name: 'empty', version: '1.0.0' }); + const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); + await Promise.all([server.connect(serverTransport), other.connect(clientTransport)]); + + const result = await other.callTool({ name: 'get_component', arguments: { name: 'Button' } }); + const listing = await other.callTool({ name: 'list_components', arguments: {} }); + + expect(result.isError).toBe(true); + expect(text(result)).toContain('No design-system components found'); + expect(text(result)).toContain('Check the "components" globs in the config'); + expect(text(result)).toContain(`(root: ${ds.root})`); + expect(text(result)).not.toContain('Available: .'); + expect(text(result)).toBe(text(listing)); + await other.close(); + }); + it('search_components', async () => { const result = await client.callTool({ name: 'search_components',