From b8d45bdef002f0eb87e57f8a69509058a0b30aea Mon Sep 17 00:00:00 2001 From: Nelson Wittwer Date: Tue, 9 Jun 2026 14:21:05 -0400 Subject: [PATCH 1/2] Refactor `shopify search` to query the dev-assistant vector store as JSON MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `search` command opened a browser at shopify.dev?search=, which now redirects to the docs SPA and silently drops the query — so it has been broken for users while still being invoked ~70-100x/month. Repurpose it as an agent-facing JSON tool: it queries the dev-assistant vector store (GET https://shopify.dev/assistant/search) and prints the matching documentation chunks as JSON to stdout. No browser. This complements `fetch-doc` (verbatim full-document retrieval) as the "chunked discovery" half. - `query` is now required. - Adds `--api-name` and `--api-version` filters, passed through to the server. - 400 responses surface the server's error message (e.g. valid api_version list). Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/search-vector-store-json.md | 5 ++ .../commands/interfaces/search.interface.ts | 22 ++++++ .../generated/generated_docs_data_v2.json | 41 +++++++++- packages/cli/README.md | 27 +++++-- packages/cli/oclif.manifest.json | 40 +++++++++- packages/cli/src/cli/commands/search.ts | 40 +++++++--- .../src/cli/services/commands/search.test.ts | 77 ++++++++++++++++--- .../cli/src/cli/services/commands/search.ts | 35 +++++++-- 8 files changed, 248 insertions(+), 39 deletions(-) create mode 100644 .changeset/search-vector-store-json.md diff --git a/.changeset/search-vector-store-json.md b/.changeset/search-vector-store-json.md new file mode 100644 index 00000000000..8f6a863f117 --- /dev/null +++ b/.changeset/search-vector-store-json.md @@ -0,0 +1,5 @@ +--- +'@shopify/cli': minor +--- + +`shopify search` now queries the shopify.dev vector store and prints the most relevant documentation chunks as JSON to stdout, instead of opening a browser. This makes it usable for programmatic and agent-driven discovery. The `query` argument is now required, and two new optional filters are available: `--api-name` (for example `admin`, `storefront`, `hydrogen`) and `--api-version` (for example `2025-10`, `latest`, `current`). To download a full document verbatim, use `fetch-doc`. diff --git a/docs-shopify.dev/commands/interfaces/search.interface.ts b/docs-shopify.dev/commands/interfaces/search.interface.ts index 041775f0570..fbb0c579205 100644 --- a/docs-shopify.dev/commands/interfaces/search.interface.ts +++ b/docs-shopify.dev/commands/interfaces/search.interface.ts @@ -4,5 +4,27 @@ * @publicDocs */ export interface search { + /** + * Limit results to a specific API (for example: admin, storefront, hydrogen, functions). Unrecognized values are ignored. + * @environment SHOPIFY_FLAG_API_NAME + */ + '--api-name '?: string + /** + * Limit results to a specific API version (for example: 2025-10, latest, current). + * @environment SHOPIFY_FLAG_API_VERSION + */ + '--api-version '?: string + + /** + * Disable color output. + * @environment SHOPIFY_FLAG_NO_COLOR + */ + '--no-color'?: '' + + /** + * Increase the verbosity of the output. + * @environment SHOPIFY_FLAG_VERBOSE + */ + '--verbose'?: '' } diff --git a/docs-shopify.dev/generated/generated_docs_data_v2.json b/docs-shopify.dev/generated/generated_docs_data_v2.json index 782fa48b708..813f1bdcc04 100644 --- a/docs-shopify.dev/generated/generated_docs_data_v2.json +++ b/docs-shopify.dev/generated/generated_docs_data_v2.json @@ -4126,8 +4126,45 @@ "name": "search", "description": "The following flags are available for the `search` command:", "isPublicDocs": true, - "members": [], - "value": "export interface search {\n\n}" + "members": [ + { + "filePath": "docs-shopify.dev/commands/interfaces/search.interface.ts", + "syntaxKind": "PropertySignature", + "name": "--api-name ", + "value": "string", + "description": "Limit results to a specific API (for example: admin, storefront, hydrogen, functions). Unrecognized values are ignored.", + "isOptional": true, + "environmentValue": "SHOPIFY_FLAG_API_NAME" + }, + { + "filePath": "docs-shopify.dev/commands/interfaces/search.interface.ts", + "syntaxKind": "PropertySignature", + "name": "--api-version ", + "value": "string", + "description": "Limit results to a specific API version (for example: 2025-10, latest, current).", + "isOptional": true, + "environmentValue": "SHOPIFY_FLAG_API_VERSION" + }, + { + "filePath": "docs-shopify.dev/commands/interfaces/search.interface.ts", + "syntaxKind": "PropertySignature", + "name": "--no-color", + "value": "''", + "description": "Disable color output.", + "isOptional": true, + "environmentValue": "SHOPIFY_FLAG_NO_COLOR" + }, + { + "filePath": "docs-shopify.dev/commands/interfaces/search.interface.ts", + "syntaxKind": "PropertySignature", + "name": "--verbose", + "value": "''", + "description": "Increase the verbosity of the output.", + "isOptional": true, + "environmentValue": "SHOPIFY_FLAG_VERBOSE" + } + ], + "value": "export interface search {\n /**\n * Limit results to a specific API (for example: admin, storefront, hydrogen, functions). Unrecognized values are ignored.\n * @environment SHOPIFY_FLAG_API_NAME\n */\n '--api-name '?: string\n\n /**\n * Limit results to a specific API version (for example: 2025-10, latest, current).\n * @environment SHOPIFY_FLAG_API_VERSION\n */\n '--api-version '?: string\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}" } }, "storeauth": { diff --git a/packages/cli/README.md b/packages/cli/README.md index f7d6f3620d6..59e72528d70 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -2083,22 +2083,33 @@ DESCRIPTION ## `shopify search [query]` -Starts a search on shopify.dev. +Query the shopify.dev vector store and print the most relevant documentation chunks as JSON. Best for programmatic discovery — surfacing the relevant pieces of documentation for a topic, rather than retrieving a whole document. To download a full document verbatim, use `fetch-doc`. ``` USAGE $ shopify search [query] +ARGUMENTS + QUERY The search query. + +FLAGS + --api-name= [env: SHOPIFY_FLAG_API_NAME] Limit results to a specific API (for example: admin, storefront, + hydrogen, functions). Unrecognized values are ignored. + --api-version= [env: SHOPIFY_FLAG_API_VERSION] Limit results to a specific API version (for example: 2025-10, + latest, current). + --no-color [env: SHOPIFY_FLAG_NO_COLOR] Disable color output. + --verbose [env: SHOPIFY_FLAG_VERBOSE] Increase the verbosity of the output. + DESCRIPTION - Starts a search on shopify.dev. + Query the shopify.dev vector store and print the most relevant documentation chunks as JSON. Best for programmatic + discovery — surfacing the relevant pieces of documentation for a topic, rather than retrieving a whole document. To + download a full document verbatim, use `fetch-doc`. EXAMPLES - # open the search modal on Shopify.dev - shopify search - # search for a term on Shopify.dev - shopify search - # search for a phrase on Shopify.dev - shopify search "" + # search shopify.dev for a topic + shopify search "subscribe to webhooks" + # narrow the search to a specific API and version + shopify search "create a product" --api-name admin --api-version latest ``` ## `shopify store auth` diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index eeacfe2b751..33c0dcbfa33 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -5649,15 +5649,49 @@ ], "args": { "query": { - "name": "query" + "description": "The search query.", + "name": "query", + "required": true } }, - "description": "Starts a search on shopify.dev.", + "description": "Query the shopify.dev vector store and print the most relevant documentation chunks as JSON. Best for programmatic discovery — surfacing the relevant pieces of documentation for a topic, rather than retrieving a whole document. To download a full document verbatim, use `fetch-doc`.", "enableJsonFlag": false, "examples": [ - "# open the search modal on Shopify.dev\n shopify search\n\n # search for a term on Shopify.dev\n shopify search \n\n # search for a phrase on Shopify.dev\n shopify search \"\"\n " + "# search shopify.dev for a topic\n shopify search \"subscribe to webhooks\"\n\n # narrow the search to a specific API and version\n shopify search \"create a product\" --api-name admin --api-version latest\n " ], "flags": { + "api-name": { + "description": "Limit results to a specific API (for example: admin, storefront, hydrogen, functions). Unrecognized values are ignored.", + "env": "SHOPIFY_FLAG_API_NAME", + "hasDynamicHelp": false, + "multiple": false, + "name": "api-name", + "type": "option" + }, + "api-version": { + "description": "Limit results to a specific API version (for example: 2025-10, latest, current).", + "env": "SHOPIFY_FLAG_API_VERSION", + "hasDynamicHelp": false, + "multiple": false, + "name": "api-version", + "type": "option" + }, + "no-color": { + "allowNo": false, + "description": "Disable color output.", + "env": "SHOPIFY_FLAG_NO_COLOR", + "hidden": false, + "name": "no-color", + "type": "boolean" + }, + "verbose": { + "allowNo": false, + "description": "Increase the verbosity of the output.", + "env": "SHOPIFY_FLAG_VERBOSE", + "hidden": false, + "name": "verbose", + "type": "boolean" + } }, "hasDynamicHelp": false, "hiddenAliases": [ diff --git a/packages/cli/src/cli/commands/search.ts b/packages/cli/src/cli/commands/search.ts index bb1346b8341..ee5a0ee4cf3 100644 --- a/packages/cli/src/cli/commands/search.ts +++ b/packages/cli/src/cli/commands/search.ts @@ -1,30 +1,46 @@ import {searchService} from '../services/commands/search.js' import Command from '@shopify/cli-kit/node/base-command' -import {Args} from '@oclif/core' +import {globalFlags} from '@shopify/cli-kit/node/cli' +import {Args, Flags} from '@oclif/core' export default class Search extends Command { - static description = 'Starts a search on shopify.dev.' + static description = + 'Query the shopify.dev vector store and print the most relevant documentation chunks as JSON. Best for programmatic discovery — surfacing the relevant pieces of documentation for a topic, rather than retrieving a whole document. To download a full document verbatim, use `fetch-doc`.' static usage = `search [query]` static examples = [ - `# open the search modal on Shopify.dev - shopify search + `# search shopify.dev for a topic + shopify search "subscribe to webhooks" - # search for a term on Shopify.dev - shopify search - - # search for a phrase on Shopify.dev - shopify search "" + # narrow the search to a specific API and version + shopify search "create a product" --api-name admin --api-version latest `, ] static args = { - query: Args.string(), + query: Args.string({ + name: 'query', + required: true, + description: 'The search query.', + }), + } + + static flags = { + ...globalFlags, + 'api-name': Flags.string({ + description: + 'Limit results to a specific API (for example: admin, storefront, hydrogen, functions). Unrecognized values are ignored.', + env: 'SHOPIFY_FLAG_API_NAME', + }), + 'api-version': Flags.string({ + description: 'Limit results to a specific API version (for example: 2025-10, latest, current).', + env: 'SHOPIFY_FLAG_API_VERSION', + }), } async run(): Promise { - const {args} = await this.parse(Search) - await searchService(args.query) + const {args, flags} = await this.parse(Search) + await searchService(args.query, flags['api-name'], flags['api-version']) } } diff --git a/packages/cli/src/cli/services/commands/search.test.ts b/packages/cli/src/cli/services/commands/search.test.ts index 31c2ca30fd1..16a529e13ff 100644 --- a/packages/cli/src/cli/services/commands/search.test.ts +++ b/packages/cli/src/cli/services/commands/search.test.ts @@ -1,19 +1,78 @@ import {searchService} from './search.js' -import {describe, expect, test, vi} from 'vitest' -import {openURL} from '@shopify/cli-kit/node/system' +import {describe, expect, test, vi, beforeEach} from 'vitest' +import {fetch} from '@shopify/cli-kit/node/http' +import {outputResult} from '@shopify/cli-kit/node/output' +import {AbortError} from '@shopify/cli-kit/node/error' -vi.mock('@shopify/cli-kit/node/system') +vi.mock('@shopify/cli-kit/node/http') +// Only stub `outputResult`; keep the rest of the module real. Blanket-mocking it +// would also mock `stringifyMessage`, which `AbortError`'s constructor relies on — +// that would silently empty out every thrown error message. +vi.mock('@shopify/cli-kit/node/output', async (importOriginal) => ({ + ...(await importOriginal()), + outputResult: vi.fn(), +})) + +const okResponse = (body: string) => + ({ok: true, status: 200, statusText: 'OK', text: () => Promise.resolve(body)}) as any + +const errorResponse = (status: number, statusText: string, body: string) => + ({ok: false, status, statusText, text: () => Promise.resolve(body)}) as any + +const resultsBody = + '[{"score":0.99,"content":"About webhooks","url":"https://shopify.dev/x","title":"Webhooks","domain":null}]' + +beforeEach(() => { + vi.mocked(fetch).mockResolvedValue(okResponse(resultsBody)) +}) describe('searchService', () => { - test('the right URL is open in the system when a query is passed', async () => { - await searchService('deploy app') + test('requests the search endpoint with the query and prints the raw JSON body', async () => { + await searchService('webhooks') + + expect(fetch).toHaveBeenCalledWith('https://shopify.dev/assistant/search?query=webhooks', { + headers: {Accept: 'application/json'}, + }) + expect(outputResult).toHaveBeenCalledWith(resultsBody) + }) + + test('includes api_name and api_version params when provided', async () => { + await searchService('create a product', 'admin', 'latest') + + expect(fetch).toHaveBeenCalledWith( + 'https://shopify.dev/assistant/search?query=create+a+product&api_name=admin&api_version=latest', + {headers: {Accept: 'application/json'}}, + ) + }) + + test('URL-encodes queries with spaces and special characters', async () => { + await searchService('a & b?') + + expect(fetch).toHaveBeenCalledWith('https://shopify.dev/assistant/search?query=a+%26+b%3F', { + headers: {Accept: 'application/json'}, + }) + }) + + test('surfaces the server error message from a non-ok JSON response', async () => { + vi.mocked(fetch).mockResolvedValue( + errorResponse( + 400, + 'Bad Request', + '{"error":"Invalid api_version \'2025-01\' for api_name \'admin\'. Available versions: 2026-07"}', + ), + ) - expect(openURL).toBeCalledWith('https://shopify.dev?search=deploy+app') + await expect(searchService('products', 'admin', '2025-01')).rejects.toThrowError( + /Invalid api_version '2025-01' for api_name 'admin'\. Available versions: 2026-07/, + ) + expect(outputResult).not.toHaveBeenCalled() }) - test('the right URL is open in the system when a query is not passed', async () => { - await searchService() + test('falls back to the status line when a non-ok response is not JSON', async () => { + vi.mocked(fetch).mockResolvedValue(errorResponse(500, 'Internal Server Error', 'nope')) - expect(openURL).toBeCalledWith('https://shopify.dev?search=') + await expect(searchService('products')).rejects.toThrowError(AbortError) + await expect(searchService('products')).rejects.toThrowError(/500 Internal Server Error/) + expect(outputResult).not.toHaveBeenCalled() }) }) diff --git a/packages/cli/src/cli/services/commands/search.ts b/packages/cli/src/cli/services/commands/search.ts index 23109da229b..056e50081cf 100644 --- a/packages/cli/src/cli/services/commands/search.ts +++ b/packages/cli/src/cli/services/commands/search.ts @@ -1,7 +1,32 @@ -import {openURL} from '@shopify/cli-kit/node/system' +import {fetch} from '@shopify/cli-kit/node/http' +import {outputResult} from '@shopify/cli-kit/node/output' +import {AbortError} from '@shopify/cli-kit/node/error' -export async function searchService(query?: string) { - const searchParams = new URLSearchParams() - searchParams.append('search', query ?? '') - await openURL(`https://shopify.dev?${searchParams.toString()}`) +// The dev-assistant search endpoint queries the shopify.dev vector store and +// returns an array of matching documentation chunks as JSON. +const SEARCH_URL = 'https://shopify.dev/assistant/search' + +export async function searchService(query: string, apiName?: string, apiVersion?: string) { + const params = new URLSearchParams({query}) + if (apiName) params.append('api_name', apiName) + if (apiVersion) params.append('api_version', apiVersion) + + const response = await fetch(`${SEARCH_URL}?${params.toString()}`, {headers: {Accept: 'application/json'}}) + const body = await response.text() + + if (!response.ok) { + // The endpoint returns a JSON `{error}` body for 400s (e.g. an invalid api_version + // lists the valid versions) — surface it directly instead of a bare status code. + let message = `${response.status} ${response.statusText}` + try { + const parsed = JSON.parse(body) + if (parsed?.error) message = parsed.error + } catch (parseError) { + // Body wasn't JSON; fall back to the status line. Rethrow anything unexpected. + if (!(parseError instanceof SyntaxError)) throw parseError + } + throw new AbortError(`Search failed: ${message}`) + } + + outputResult(body) } From f0656f4c3fdbdbffd45048ae213fe0e0be6e96e9 Mon Sep 17 00:00:00 2001 From: Nelson Wittwer Date: Wed, 10 Jun 2026 14:47:00 -0400 Subject: [PATCH 2/2] Move JSON search to `doc search`; restore top-level `search` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per the doc-namespace plan, the JSON vector-store search lives at `doc search` instead of repurposing the top-level `search` command: - Add commands/doc/search.ts (class DocSearch), services/commands/doc/search.ts (docSearchService), and the colocated test; register as `doc:search`. - Add a `doc` topic description. - Revert the breaking change to top-level `search` — it goes back to the original browser behavior (handled/deprecated separately in #7778). Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/add-doc-search-command.md | 5 + .changeset/search-vector-store-json.md | 5 - .../interfaces/doc-search.interface.ts | 30 ++++++ .../commands/interfaces/search.interface.ts | 22 ----- .../generated/generated_docs_data_v2.json | 88 +++++++++-------- packages/cli/README.md | 59 +++++++---- packages/cli/oclif.manifest.json | 99 ++++++++++++------- packages/cli/package.json | 3 + packages/cli/src/cli/commands/doc/search.ts | 46 +++++++++ packages/cli/src/cli/commands/search.ts | 40 +++----- .../cli/services/commands/doc/search.test.ts | 78 +++++++++++++++ .../src/cli/services/commands/doc/search.ts | 32 ++++++ .../src/cli/services/commands/search.test.ts | 77 ++------------- .../cli/src/cli/services/commands/search.ts | 35 +------ packages/cli/src/index.ts | 2 + packages/e2e/data/snapshots/commands.txt | 2 + 16 files changed, 375 insertions(+), 248 deletions(-) create mode 100644 .changeset/add-doc-search-command.md delete mode 100644 .changeset/search-vector-store-json.md create mode 100644 docs-shopify.dev/commands/interfaces/doc-search.interface.ts create mode 100644 packages/cli/src/cli/commands/doc/search.ts create mode 100644 packages/cli/src/cli/services/commands/doc/search.test.ts create mode 100644 packages/cli/src/cli/services/commands/doc/search.ts diff --git a/.changeset/add-doc-search-command.md b/.changeset/add-doc-search-command.md new file mode 100644 index 00000000000..23544132171 --- /dev/null +++ b/.changeset/add-doc-search-command.md @@ -0,0 +1,5 @@ +--- +'@shopify/cli': minor +--- + +Add `shopify doc search`, which queries the shopify.dev vector store and prints the most relevant documentation chunks as JSON to stdout. This makes it usable for programmatic and agent-driven discovery. The `query` argument is required, and two optional filters are available: `--api-name` (for example `admin`, `storefront`, `hydrogen`) and `--api-version` (for example `2025-10`, `latest`, `current`). To download a full document verbatim, use `doc fetch`. diff --git a/.changeset/search-vector-store-json.md b/.changeset/search-vector-store-json.md deleted file mode 100644 index 8f6a863f117..00000000000 --- a/.changeset/search-vector-store-json.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@shopify/cli': minor ---- - -`shopify search` now queries the shopify.dev vector store and prints the most relevant documentation chunks as JSON to stdout, instead of opening a browser. This makes it usable for programmatic and agent-driven discovery. The `query` argument is now required, and two new optional filters are available: `--api-name` (for example `admin`, `storefront`, `hydrogen`) and `--api-version` (for example `2025-10`, `latest`, `current`). To download a full document verbatim, use `fetch-doc`. diff --git a/docs-shopify.dev/commands/interfaces/doc-search.interface.ts b/docs-shopify.dev/commands/interfaces/doc-search.interface.ts new file mode 100644 index 00000000000..1b74a9c043e --- /dev/null +++ b/docs-shopify.dev/commands/interfaces/doc-search.interface.ts @@ -0,0 +1,30 @@ +// This is an autogenerated file. Don't edit this file manually. +/** + * The following flags are available for the `doc search` command: + * @publicDocs + */ +export interface docsearch { + /** + * Limit results to a specific API (for example: admin, storefront, hydrogen, functions). Unrecognized values are ignored. + * @environment SHOPIFY_FLAG_API_NAME + */ + '--api-name '?: string + + /** + * Limit results to a specific API version (for example: 2025-10, latest, current). + * @environment SHOPIFY_FLAG_API_VERSION + */ + '--api-version '?: string + + /** + * Disable color output. + * @environment SHOPIFY_FLAG_NO_COLOR + */ + '--no-color'?: '' + + /** + * Increase the verbosity of the output. + * @environment SHOPIFY_FLAG_VERBOSE + */ + '--verbose'?: '' +} diff --git a/docs-shopify.dev/commands/interfaces/search.interface.ts b/docs-shopify.dev/commands/interfaces/search.interface.ts index fbb0c579205..041775f0570 100644 --- a/docs-shopify.dev/commands/interfaces/search.interface.ts +++ b/docs-shopify.dev/commands/interfaces/search.interface.ts @@ -4,27 +4,5 @@ * @publicDocs */ export interface search { - /** - * Limit results to a specific API (for example: admin, storefront, hydrogen, functions). Unrecognized values are ignored. - * @environment SHOPIFY_FLAG_API_NAME - */ - '--api-name '?: string - /** - * Limit results to a specific API version (for example: 2025-10, latest, current). - * @environment SHOPIFY_FLAG_API_VERSION - */ - '--api-version '?: string - - /** - * Disable color output. - * @environment SHOPIFY_FLAG_NO_COLOR - */ - '--no-color'?: '' - - /** - * Increase the verbosity of the output. - * @environment SHOPIFY_FLAG_VERBOSE - */ - '--verbose'?: '' } diff --git a/docs-shopify.dev/generated/generated_docs_data_v2.json b/docs-shopify.dev/generated/generated_docs_data_v2.json index 813f1bdcc04..0dc9a4814b7 100644 --- a/docs-shopify.dev/generated/generated_docs_data_v2.json +++ b/docs-shopify.dev/generated/generated_docs_data_v2.json @@ -2742,6 +2742,53 @@ "value": "export interface configautoupgradestatus {\n\n}" } }, + "docsearch": { + "docs-shopify.dev/commands/interfaces/doc-search.interface.ts": { + "filePath": "docs-shopify.dev/commands/interfaces/doc-search.interface.ts", + "name": "docsearch", + "description": "The following flags are available for the `doc search` command:", + "isPublicDocs": true, + "members": [ + { + "filePath": "docs-shopify.dev/commands/interfaces/doc-search.interface.ts", + "syntaxKind": "PropertySignature", + "name": "--api-name ", + "value": "string", + "description": "Limit results to a specific API (for example: admin, storefront, hydrogen, functions). Unrecognized values are ignored.", + "isOptional": true, + "environmentValue": "SHOPIFY_FLAG_API_NAME" + }, + { + "filePath": "docs-shopify.dev/commands/interfaces/doc-search.interface.ts", + "syntaxKind": "PropertySignature", + "name": "--api-version ", + "value": "string", + "description": "Limit results to a specific API version (for example: 2025-10, latest, current).", + "isOptional": true, + "environmentValue": "SHOPIFY_FLAG_API_VERSION" + }, + { + "filePath": "docs-shopify.dev/commands/interfaces/doc-search.interface.ts", + "syntaxKind": "PropertySignature", + "name": "--no-color", + "value": "''", + "description": "Disable color output.", + "isOptional": true, + "environmentValue": "SHOPIFY_FLAG_NO_COLOR" + }, + { + "filePath": "docs-shopify.dev/commands/interfaces/doc-search.interface.ts", + "syntaxKind": "PropertySignature", + "name": "--verbose", + "value": "''", + "description": "Increase the verbosity of the output.", + "isOptional": true, + "environmentValue": "SHOPIFY_FLAG_VERBOSE" + } + ], + "value": "export interface docsearch {\n /**\n * Limit results to a specific API (for example: admin, storefront, hydrogen, functions). Unrecognized values are ignored.\n * @environment SHOPIFY_FLAG_API_NAME\n */\n '--api-name '?: string\n\n /**\n * Limit results to a specific API version (for example: 2025-10, latest, current).\n * @environment SHOPIFY_FLAG_API_VERSION\n */\n '--api-version '?: string\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}" + } + }, "help": { "docs-shopify.dev/commands/interfaces/help.interface.ts": { "filePath": "docs-shopify.dev/commands/interfaces/help.interface.ts", @@ -4126,45 +4173,8 @@ "name": "search", "description": "The following flags are available for the `search` command:", "isPublicDocs": true, - "members": [ - { - "filePath": "docs-shopify.dev/commands/interfaces/search.interface.ts", - "syntaxKind": "PropertySignature", - "name": "--api-name ", - "value": "string", - "description": "Limit results to a specific API (for example: admin, storefront, hydrogen, functions). Unrecognized values are ignored.", - "isOptional": true, - "environmentValue": "SHOPIFY_FLAG_API_NAME" - }, - { - "filePath": "docs-shopify.dev/commands/interfaces/search.interface.ts", - "syntaxKind": "PropertySignature", - "name": "--api-version ", - "value": "string", - "description": "Limit results to a specific API version (for example: 2025-10, latest, current).", - "isOptional": true, - "environmentValue": "SHOPIFY_FLAG_API_VERSION" - }, - { - "filePath": "docs-shopify.dev/commands/interfaces/search.interface.ts", - "syntaxKind": "PropertySignature", - "name": "--no-color", - "value": "''", - "description": "Disable color output.", - "isOptional": true, - "environmentValue": "SHOPIFY_FLAG_NO_COLOR" - }, - { - "filePath": "docs-shopify.dev/commands/interfaces/search.interface.ts", - "syntaxKind": "PropertySignature", - "name": "--verbose", - "value": "''", - "description": "Increase the verbosity of the output.", - "isOptional": true, - "environmentValue": "SHOPIFY_FLAG_VERBOSE" - } - ], - "value": "export interface search {\n /**\n * Limit results to a specific API (for example: admin, storefront, hydrogen, functions). Unrecognized values are ignored.\n * @environment SHOPIFY_FLAG_API_NAME\n */\n '--api-name '?: string\n\n /**\n * Limit results to a specific API version (for example: 2025-10, latest, current).\n * @environment SHOPIFY_FLAG_API_VERSION\n */\n '--api-version '?: string\n\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}" + "members": [], + "value": "export interface search {\n\n}" } }, "storeauth": { diff --git a/packages/cli/README.md b/packages/cli/README.md index 59e72528d70..f22936aca08 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -39,6 +39,7 @@ * [`shopify config autoupgrade off`](#shopify-config-autoupgrade-off) * [`shopify config autoupgrade on`](#shopify-config-autoupgrade-on) * [`shopify config autoupgrade status`](#shopify-config-autoupgrade-status) +* [`shopify doc search [query]`](#shopify-doc-search-query) * [`shopify help [command] [flags]`](#shopify-help-command-flags) * [`shopify hydrogen build`](#shopify-hydrogen-build) * [`shopify hydrogen check RESOURCE`](#shopify-hydrogen-check-resource) @@ -1212,6 +1213,37 @@ DESCRIPTION Run `shopify config autoupgrade on` or `shopify config autoupgrade off` to configure it. ``` +## `shopify doc search [query]` + +Query the shopify.dev vector store and print the most relevant documentation chunks as JSON. Best for programmatic discovery — surfacing the relevant pieces of documentation for a topic, rather than retrieving a whole document. To download a full document verbatim, use `doc fetch`. + +``` +USAGE + $ shopify doc search [query] + +ARGUMENTS + QUERY The search query. + +FLAGS + --api-name= [env: SHOPIFY_FLAG_API_NAME] Limit results to a specific API (for example: admin, storefront, + hydrogen, functions). Unrecognized values are ignored. + --api-version= [env: SHOPIFY_FLAG_API_VERSION] Limit results to a specific API version (for example: 2025-10, + latest, current). + --no-color [env: SHOPIFY_FLAG_NO_COLOR] Disable color output. + --verbose [env: SHOPIFY_FLAG_VERBOSE] Increase the verbosity of the output. + +DESCRIPTION + Query the shopify.dev vector store and print the most relevant documentation chunks as JSON. Best for programmatic + discovery — surfacing the relevant pieces of documentation for a topic, rather than retrieving a whole document. To + download a full document verbatim, use `doc fetch`. + +EXAMPLES + # search shopify.dev for a topic + shopify doc search "subscribe to webhooks" + # narrow the search to a specific API and version + shopify doc search "create a product" --api-name admin --api-version latest +``` + ## `shopify help [command] [flags]` Display help for Shopify CLI @@ -2083,33 +2115,22 @@ DESCRIPTION ## `shopify search [query]` -Query the shopify.dev vector store and print the most relevant documentation chunks as JSON. Best for programmatic discovery — surfacing the relevant pieces of documentation for a topic, rather than retrieving a whole document. To download a full document verbatim, use `fetch-doc`. +Starts a search on shopify.dev. ``` USAGE $ shopify search [query] -ARGUMENTS - QUERY The search query. - -FLAGS - --api-name= [env: SHOPIFY_FLAG_API_NAME] Limit results to a specific API (for example: admin, storefront, - hydrogen, functions). Unrecognized values are ignored. - --api-version= [env: SHOPIFY_FLAG_API_VERSION] Limit results to a specific API version (for example: 2025-10, - latest, current). - --no-color [env: SHOPIFY_FLAG_NO_COLOR] Disable color output. - --verbose [env: SHOPIFY_FLAG_VERBOSE] Increase the verbosity of the output. - DESCRIPTION - Query the shopify.dev vector store and print the most relevant documentation chunks as JSON. Best for programmatic - discovery — surfacing the relevant pieces of documentation for a topic, rather than retrieving a whole document. To - download a full document verbatim, use `fetch-doc`. + Starts a search on shopify.dev. EXAMPLES - # search shopify.dev for a topic - shopify search "subscribe to webhooks" - # narrow the search to a specific API and version - shopify search "create a product" --api-name admin --api-version latest + # open the search modal on Shopify.dev + shopify search + # search for a term on Shopify.dev + shopify search + # search for a phrase on Shopify.dev + shopify search "" ``` ## `shopify store auth` diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index 33c0dcbfa33..d0e8add091d 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -3424,6 +3424,65 @@ "strict": true, "summary": "Watch and prints out changes to an app." }, + "doc:search": { + "aliases": [ + ], + "args": { + "query": { + "description": "The search query.", + "name": "query", + "required": true + } + }, + "description": "Query the shopify.dev vector store and print the most relevant documentation chunks as JSON. Best for programmatic discovery — surfacing the relevant pieces of documentation for a topic, rather than retrieving a whole document. To download a full document verbatim, use `doc fetch`.", + "enableJsonFlag": false, + "examples": [ + "# search shopify.dev for a topic\n shopify doc search \"subscribe to webhooks\"\n\n # narrow the search to a specific API and version\n shopify doc search \"create a product\" --api-name admin --api-version latest\n " + ], + "flags": { + "api-name": { + "description": "Limit results to a specific API (for example: admin, storefront, hydrogen, functions). Unrecognized values are ignored.", + "env": "SHOPIFY_FLAG_API_NAME", + "hasDynamicHelp": false, + "multiple": false, + "name": "api-name", + "type": "option" + }, + "api-version": { + "description": "Limit results to a specific API version (for example: 2025-10, latest, current).", + "env": "SHOPIFY_FLAG_API_VERSION", + "hasDynamicHelp": false, + "multiple": false, + "name": "api-version", + "type": "option" + }, + "no-color": { + "allowNo": false, + "description": "Disable color output.", + "env": "SHOPIFY_FLAG_NO_COLOR", + "hidden": false, + "name": "no-color", + "type": "boolean" + }, + "verbose": { + "allowNo": false, + "description": "Increase the verbosity of the output.", + "env": "SHOPIFY_FLAG_VERBOSE", + "hidden": false, + "name": "verbose", + "type": "boolean" + } + }, + "hasDynamicHelp": false, + "hiddenAliases": [ + ], + "id": "doc:search", + "pluginAlias": "@shopify/cli", + "pluginName": "@shopify/cli", + "pluginType": "core", + "strict": true, + "usage": "doc search [query]" + }, "docs:generate": { "aliases": [ ], @@ -5649,49 +5708,15 @@ ], "args": { "query": { - "description": "The search query.", - "name": "query", - "required": true + "name": "query" } }, - "description": "Query the shopify.dev vector store and print the most relevant documentation chunks as JSON. Best for programmatic discovery — surfacing the relevant pieces of documentation for a topic, rather than retrieving a whole document. To download a full document verbatim, use `fetch-doc`.", + "description": "Starts a search on shopify.dev.", "enableJsonFlag": false, "examples": [ - "# search shopify.dev for a topic\n shopify search \"subscribe to webhooks\"\n\n # narrow the search to a specific API and version\n shopify search \"create a product\" --api-name admin --api-version latest\n " + "# open the search modal on Shopify.dev\n shopify search\n\n # search for a term on Shopify.dev\n shopify search \n\n # search for a phrase on Shopify.dev\n shopify search \"\"\n " ], "flags": { - "api-name": { - "description": "Limit results to a specific API (for example: admin, storefront, hydrogen, functions). Unrecognized values are ignored.", - "env": "SHOPIFY_FLAG_API_NAME", - "hasDynamicHelp": false, - "multiple": false, - "name": "api-name", - "type": "option" - }, - "api-version": { - "description": "Limit results to a specific API version (for example: 2025-10, latest, current).", - "env": "SHOPIFY_FLAG_API_VERSION", - "hasDynamicHelp": false, - "multiple": false, - "name": "api-version", - "type": "option" - }, - "no-color": { - "allowNo": false, - "description": "Disable color output.", - "env": "SHOPIFY_FLAG_NO_COLOR", - "hidden": false, - "name": "no-color", - "type": "boolean" - }, - "verbose": { - "allowNo": false, - "description": "Increase the verbosity of the output.", - "env": "SHOPIFY_FLAG_VERBOSE", - "hidden": false, - "name": "verbose", - "type": "boolean" - } }, "hasDynamicHelp": false, "hiddenAliases": [ diff --git a/packages/cli/package.json b/packages/cli/package.json index 715e12c3c6f..536d106982b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -94,6 +94,9 @@ "scope": "shopify", "topicSeparator": " ", "topics": { + "doc": { + "description": "Search and fetch documentation from shopify.dev." + }, "hydrogen": { "description": "Build Hydrogen storefronts." }, diff --git a/packages/cli/src/cli/commands/doc/search.ts b/packages/cli/src/cli/commands/doc/search.ts new file mode 100644 index 00000000000..33aceb0875e --- /dev/null +++ b/packages/cli/src/cli/commands/doc/search.ts @@ -0,0 +1,46 @@ +import {docSearchService} from '../../services/commands/doc/search.js' +import Command from '@shopify/cli-kit/node/base-command' +import {globalFlags} from '@shopify/cli-kit/node/cli' +import {Args, Flags} from '@oclif/core' + +export default class DocSearch extends Command { + static description = + 'Query the shopify.dev vector store and print the most relevant documentation chunks as JSON. Best for programmatic discovery — surfacing the relevant pieces of documentation for a topic, rather than retrieving a whole document. To download a full document verbatim, use `doc fetch`.' + + static usage = `doc search [query]` + + static examples = [ + `# search shopify.dev for a topic + shopify doc search "subscribe to webhooks" + + # narrow the search to a specific API and version + shopify doc search "create a product" --api-name admin --api-version latest + `, + ] + + static args = { + query: Args.string({ + name: 'query', + required: true, + description: 'The search query.', + }), + } + + static flags = { + ...globalFlags, + 'api-name': Flags.string({ + description: + 'Limit results to a specific API (for example: admin, storefront, hydrogen, functions). Unrecognized values are ignored.', + env: 'SHOPIFY_FLAG_API_NAME', + }), + 'api-version': Flags.string({ + description: 'Limit results to a specific API version (for example: 2025-10, latest, current).', + env: 'SHOPIFY_FLAG_API_VERSION', + }), + } + + async run(): Promise { + const {args, flags} = await this.parse(DocSearch) + await docSearchService(args.query, flags['api-name'], flags['api-version']) + } +} diff --git a/packages/cli/src/cli/commands/search.ts b/packages/cli/src/cli/commands/search.ts index ee5a0ee4cf3..bb1346b8341 100644 --- a/packages/cli/src/cli/commands/search.ts +++ b/packages/cli/src/cli/commands/search.ts @@ -1,46 +1,30 @@ import {searchService} from '../services/commands/search.js' import Command from '@shopify/cli-kit/node/base-command' -import {globalFlags} from '@shopify/cli-kit/node/cli' -import {Args, Flags} from '@oclif/core' +import {Args} from '@oclif/core' export default class Search extends Command { - static description = - 'Query the shopify.dev vector store and print the most relevant documentation chunks as JSON. Best for programmatic discovery — surfacing the relevant pieces of documentation for a topic, rather than retrieving a whole document. To download a full document verbatim, use `fetch-doc`.' + static description = 'Starts a search on shopify.dev.' static usage = `search [query]` static examples = [ - `# search shopify.dev for a topic - shopify search "subscribe to webhooks" + `# open the search modal on Shopify.dev + shopify search - # narrow the search to a specific API and version - shopify search "create a product" --api-name admin --api-version latest + # search for a term on Shopify.dev + shopify search + + # search for a phrase on Shopify.dev + shopify search "" `, ] static args = { - query: Args.string({ - name: 'query', - required: true, - description: 'The search query.', - }), - } - - static flags = { - ...globalFlags, - 'api-name': Flags.string({ - description: - 'Limit results to a specific API (for example: admin, storefront, hydrogen, functions). Unrecognized values are ignored.', - env: 'SHOPIFY_FLAG_API_NAME', - }), - 'api-version': Flags.string({ - description: 'Limit results to a specific API version (for example: 2025-10, latest, current).', - env: 'SHOPIFY_FLAG_API_VERSION', - }), + query: Args.string(), } async run(): Promise { - const {args, flags} = await this.parse(Search) - await searchService(args.query, flags['api-name'], flags['api-version']) + const {args} = await this.parse(Search) + await searchService(args.query) } } diff --git a/packages/cli/src/cli/services/commands/doc/search.test.ts b/packages/cli/src/cli/services/commands/doc/search.test.ts new file mode 100644 index 00000000000..2b86109956f --- /dev/null +++ b/packages/cli/src/cli/services/commands/doc/search.test.ts @@ -0,0 +1,78 @@ +import {docSearchService} from './search.js' +import {describe, expect, test, vi, beforeEach} from 'vitest' +import {fetch} from '@shopify/cli-kit/node/http' +import {outputResult} from '@shopify/cli-kit/node/output' +import {AbortError} from '@shopify/cli-kit/node/error' + +vi.mock('@shopify/cli-kit/node/http') +// Only stub `outputResult`; keep the rest of the module real. Blanket-mocking it +// would also mock `stringifyMessage`, which `AbortError`'s constructor relies on — +// that would silently empty out every thrown error message. +vi.mock('@shopify/cli-kit/node/output', async (importOriginal) => ({ + ...(await importOriginal()), + outputResult: vi.fn(), +})) + +const okResponse = (body: string) => + ({ok: true, status: 200, statusText: 'OK', text: () => Promise.resolve(body)}) as any + +const errorResponse = (status: number, statusText: string, body: string) => + ({ok: false, status, statusText, text: () => Promise.resolve(body)}) as any + +const resultsBody = + '[{"score":0.99,"content":"About webhooks","url":"https://shopify.dev/x","title":"Webhooks","domain":null}]' + +beforeEach(() => { + vi.mocked(fetch).mockResolvedValue(okResponse(resultsBody)) +}) + +describe('docSearchService', () => { + test('requests the search endpoint with the query and prints the raw JSON body', async () => { + await docSearchService('webhooks') + + expect(fetch).toHaveBeenCalledWith('https://shopify.dev/assistant/search?query=webhooks', { + headers: {Accept: 'application/json'}, + }) + expect(outputResult).toHaveBeenCalledWith(resultsBody) + }) + + test('includes api_name and api_version params when provided', async () => { + await docSearchService('create a product', 'admin', 'latest') + + expect(fetch).toHaveBeenCalledWith( + 'https://shopify.dev/assistant/search?query=create+a+product&api_name=admin&api_version=latest', + {headers: {Accept: 'application/json'}}, + ) + }) + + test('URL-encodes queries with spaces and special characters', async () => { + await docSearchService('a & b?') + + expect(fetch).toHaveBeenCalledWith('https://shopify.dev/assistant/search?query=a+%26+b%3F', { + headers: {Accept: 'application/json'}, + }) + }) + + test('surfaces the server error message from a non-ok JSON response', async () => { + vi.mocked(fetch).mockResolvedValue( + errorResponse( + 400, + 'Bad Request', + '{"error":"Invalid api_version \'2025-01\' for api_name \'admin\'. Available versions: 2026-07"}', + ), + ) + + await expect(docSearchService('products', 'admin', '2025-01')).rejects.toThrowError( + /Invalid api_version '2025-01' for api_name 'admin'\. Available versions: 2026-07/, + ) + expect(outputResult).not.toHaveBeenCalled() + }) + + test('falls back to the status line when a non-ok response is not JSON', async () => { + vi.mocked(fetch).mockResolvedValue(errorResponse(500, 'Internal Server Error', 'nope')) + + await expect(docSearchService('products')).rejects.toThrowError(AbortError) + await expect(docSearchService('products')).rejects.toThrowError(/500 Internal Server Error/) + expect(outputResult).not.toHaveBeenCalled() + }) +}) diff --git a/packages/cli/src/cli/services/commands/doc/search.ts b/packages/cli/src/cli/services/commands/doc/search.ts new file mode 100644 index 00000000000..c249e6a4c8b --- /dev/null +++ b/packages/cli/src/cli/services/commands/doc/search.ts @@ -0,0 +1,32 @@ +import {fetch} from '@shopify/cli-kit/node/http' +import {outputResult} from '@shopify/cli-kit/node/output' +import {AbortError} from '@shopify/cli-kit/node/error' + +// The dev-assistant search endpoint queries the shopify.dev vector store and +// returns an array of matching documentation chunks as JSON. +const SEARCH_URL = 'https://shopify.dev/assistant/search' + +export async function docSearchService(query: string, apiName?: string, apiVersion?: string) { + const params = new URLSearchParams({query}) + if (apiName) params.append('api_name', apiName) + if (apiVersion) params.append('api_version', apiVersion) + + const response = await fetch(`${SEARCH_URL}?${params.toString()}`, {headers: {Accept: 'application/json'}}) + const body = await response.text() + + if (!response.ok) { + // The endpoint returns a JSON `{error}` body for 400s (e.g. an invalid api_version + // lists the valid versions) — surface it directly instead of a bare status code. + let message = `${response.status} ${response.statusText}` + try { + const parsed = JSON.parse(body) + if (parsed?.error) message = parsed.error + } catch (parseError) { + // Body wasn't JSON; fall back to the status line. Rethrow anything unexpected. + if (!(parseError instanceof SyntaxError)) throw parseError + } + throw new AbortError(`Search failed: ${message}`) + } + + outputResult(body) +} diff --git a/packages/cli/src/cli/services/commands/search.test.ts b/packages/cli/src/cli/services/commands/search.test.ts index 16a529e13ff..31c2ca30fd1 100644 --- a/packages/cli/src/cli/services/commands/search.test.ts +++ b/packages/cli/src/cli/services/commands/search.test.ts @@ -1,78 +1,19 @@ import {searchService} from './search.js' -import {describe, expect, test, vi, beforeEach} from 'vitest' -import {fetch} from '@shopify/cli-kit/node/http' -import {outputResult} from '@shopify/cli-kit/node/output' -import {AbortError} from '@shopify/cli-kit/node/error' +import {describe, expect, test, vi} from 'vitest' +import {openURL} from '@shopify/cli-kit/node/system' -vi.mock('@shopify/cli-kit/node/http') -// Only stub `outputResult`; keep the rest of the module real. Blanket-mocking it -// would also mock `stringifyMessage`, which `AbortError`'s constructor relies on — -// that would silently empty out every thrown error message. -vi.mock('@shopify/cli-kit/node/output', async (importOriginal) => ({ - ...(await importOriginal()), - outputResult: vi.fn(), -})) - -const okResponse = (body: string) => - ({ok: true, status: 200, statusText: 'OK', text: () => Promise.resolve(body)}) as any - -const errorResponse = (status: number, statusText: string, body: string) => - ({ok: false, status, statusText, text: () => Promise.resolve(body)}) as any - -const resultsBody = - '[{"score":0.99,"content":"About webhooks","url":"https://shopify.dev/x","title":"Webhooks","domain":null}]' - -beforeEach(() => { - vi.mocked(fetch).mockResolvedValue(okResponse(resultsBody)) -}) +vi.mock('@shopify/cli-kit/node/system') describe('searchService', () => { - test('requests the search endpoint with the query and prints the raw JSON body', async () => { - await searchService('webhooks') - - expect(fetch).toHaveBeenCalledWith('https://shopify.dev/assistant/search?query=webhooks', { - headers: {Accept: 'application/json'}, - }) - expect(outputResult).toHaveBeenCalledWith(resultsBody) - }) - - test('includes api_name and api_version params when provided', async () => { - await searchService('create a product', 'admin', 'latest') - - expect(fetch).toHaveBeenCalledWith( - 'https://shopify.dev/assistant/search?query=create+a+product&api_name=admin&api_version=latest', - {headers: {Accept: 'application/json'}}, - ) - }) - - test('URL-encodes queries with spaces and special characters', async () => { - await searchService('a & b?') - - expect(fetch).toHaveBeenCalledWith('https://shopify.dev/assistant/search?query=a+%26+b%3F', { - headers: {Accept: 'application/json'}, - }) - }) - - test('surfaces the server error message from a non-ok JSON response', async () => { - vi.mocked(fetch).mockResolvedValue( - errorResponse( - 400, - 'Bad Request', - '{"error":"Invalid api_version \'2025-01\' for api_name \'admin\'. Available versions: 2026-07"}', - ), - ) + test('the right URL is open in the system when a query is passed', async () => { + await searchService('deploy app') - await expect(searchService('products', 'admin', '2025-01')).rejects.toThrowError( - /Invalid api_version '2025-01' for api_name 'admin'\. Available versions: 2026-07/, - ) - expect(outputResult).not.toHaveBeenCalled() + expect(openURL).toBeCalledWith('https://shopify.dev?search=deploy+app') }) - test('falls back to the status line when a non-ok response is not JSON', async () => { - vi.mocked(fetch).mockResolvedValue(errorResponse(500, 'Internal Server Error', 'nope')) + test('the right URL is open in the system when a query is not passed', async () => { + await searchService() - await expect(searchService('products')).rejects.toThrowError(AbortError) - await expect(searchService('products')).rejects.toThrowError(/500 Internal Server Error/) - expect(outputResult).not.toHaveBeenCalled() + expect(openURL).toBeCalledWith('https://shopify.dev?search=') }) }) diff --git a/packages/cli/src/cli/services/commands/search.ts b/packages/cli/src/cli/services/commands/search.ts index 056e50081cf..23109da229b 100644 --- a/packages/cli/src/cli/services/commands/search.ts +++ b/packages/cli/src/cli/services/commands/search.ts @@ -1,32 +1,7 @@ -import {fetch} from '@shopify/cli-kit/node/http' -import {outputResult} from '@shopify/cli-kit/node/output' -import {AbortError} from '@shopify/cli-kit/node/error' +import {openURL} from '@shopify/cli-kit/node/system' -// The dev-assistant search endpoint queries the shopify.dev vector store and -// returns an array of matching documentation chunks as JSON. -const SEARCH_URL = 'https://shopify.dev/assistant/search' - -export async function searchService(query: string, apiName?: string, apiVersion?: string) { - const params = new URLSearchParams({query}) - if (apiName) params.append('api_name', apiName) - if (apiVersion) params.append('api_version', apiVersion) - - const response = await fetch(`${SEARCH_URL}?${params.toString()}`, {headers: {Accept: 'application/json'}}) - const body = await response.text() - - if (!response.ok) { - // The endpoint returns a JSON `{error}` body for 400s (e.g. an invalid api_version - // lists the valid versions) — surface it directly instead of a bare status code. - let message = `${response.status} ${response.statusText}` - try { - const parsed = JSON.parse(body) - if (parsed?.error) message = parsed.error - } catch (parseError) { - // Body wasn't JSON; fall back to the status line. Rethrow anything unexpected. - if (!(parseError instanceof SyntaxError)) throw parseError - } - throw new AbortError(`Search failed: ${message}`) - } - - outputResult(body) +export async function searchService(query?: string) { + const searchParams = new URLSearchParams() + searchParams.append('search', query ?? '') + await openURL(`https://shopify.dev?${searchParams.toString()}`) } diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index ff506419c63..efa05b36422 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,5 +1,6 @@ import VersionCommand from './cli/commands/version.js' import Search from './cli/commands/search.js' +import DocSearch from './cli/commands/doc/search.js' import Upgrade from './cli/commands/upgrade.js' import Logout from './cli/commands/auth/logout.js' import Login from './cli/commands/auth/login.js' @@ -145,6 +146,7 @@ export const COMMANDS: any = { ...HydrogenCommands, ...StoreCommands, search: Search, + 'doc:search': DocSearch, upgrade: Upgrade, version: VersionCommand, help: HelpCommand, diff --git a/packages/e2e/data/snapshots/commands.txt b/packages/e2e/data/snapshots/commands.txt index 9e9afb6c946..8ce280cc296 100644 --- a/packages/e2e/data/snapshots/commands.txt +++ b/packages/e2e/data/snapshots/commands.txt @@ -49,6 +49,8 @@ │ ├─ off │ ├─ on │ └─ status +├─ doc +│ └─ search ├─ help ├─ hydrogen │ ├─ build