From 153f7c07b9df4fee5d84d9d0264ff0b412f567f2 Mon Sep 17 00:00:00 2001 From: Nelson Wittwer Date: Tue, 9 Jun 2026 10:15:23 -0400 Subject: [PATCH 1/8] Add `shopify fetch` command to download docs from shopify.dev Adds a top-level `fetch` command that downloads a shopify.dev page and prints it to stdout. It requests the Markdown representation by default (via the `Accept` header) and accepts a `--content-type` flag to override it. URLs are restricted to shopify.dev. This gives agents a way to pull instructional content from the centralized docs repo. Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/add-fetch-doc-command.md | 5 ++ .../commands/interfaces/fetch.interface.ts | 12 ++++ .../generated/generated_docs_data_v2.json | 20 +++++++ packages/cli/README.md | 26 +++++++++ packages/cli/oclif.manifest.json | 35 ++++++++++++ packages/cli/src/cli/commands/fetch.ts | 38 +++++++++++++ .../src/cli/services/commands/fetch.test.ts | 57 +++++++++++++++++++ .../cli/src/cli/services/commands/fetch.ts | 28 +++++++++ packages/cli/src/index.ts | 2 + 9 files changed, 223 insertions(+) create mode 100644 .changeset/add-fetch-doc-command.md create mode 100644 docs-shopify.dev/commands/interfaces/fetch.interface.ts create mode 100644 packages/cli/src/cli/commands/fetch.ts create mode 100644 packages/cli/src/cli/services/commands/fetch.test.ts create mode 100644 packages/cli/src/cli/services/commands/fetch.ts diff --git a/.changeset/add-fetch-doc-command.md b/.changeset/add-fetch-doc-command.md new file mode 100644 index 00000000000..3d7eb478f3b --- /dev/null +++ b/.changeset/add-fetch-doc-command.md @@ -0,0 +1,5 @@ +--- +'@shopify/cli': minor +--- + +Add a `shopify fetch` command that downloads a document from shopify.dev and prints it to stdout. It requests the Markdown representation by default (overridable with `--content-type`), giving agents an easy way to pull instructional content from the centralized docs. diff --git a/docs-shopify.dev/commands/interfaces/fetch.interface.ts b/docs-shopify.dev/commands/interfaces/fetch.interface.ts new file mode 100644 index 00000000000..9113ac73bc7 --- /dev/null +++ b/docs-shopify.dev/commands/interfaces/fetch.interface.ts @@ -0,0 +1,12 @@ +// This is an autogenerated file. Don't edit this file manually. +/** + * The following flags are available for the `fetch` command: + * @publicDocs + */ +export interface fetch { + /** + * The Accept content type to request (defaults to text/markdown). + * @environment SHOPIFY_FLAG_CONTENT_TYPE + */ + '--content-type '?: string +} diff --git a/docs-shopify.dev/generated/generated_docs_data_v2.json b/docs-shopify.dev/generated/generated_docs_data_v2.json index 782fa48b708..9742bd711db 100644 --- a/docs-shopify.dev/generated/generated_docs_data_v2.json +++ b/docs-shopify.dev/generated/generated_docs_data_v2.json @@ -2742,6 +2742,26 @@ "value": "export interface configautoupgradestatus {\n\n}" } }, + "fetch": { + "docs-shopify.dev/commands/interfaces/fetch.interface.ts": { + "filePath": "docs-shopify.dev/commands/interfaces/fetch.interface.ts", + "name": "fetch", + "description": "The following flags are available for the `fetch` command:", + "isPublicDocs": true, + "members": [ + { + "filePath": "docs-shopify.dev/commands/interfaces/fetch.interface.ts", + "syntaxKind": "PropertySignature", + "name": "--content-type ", + "value": "string", + "description": "The Accept content type to request (defaults to text/markdown).", + "isOptional": true, + "environmentValue": "SHOPIFY_FLAG_CONTENT_TYPE" + } + ], + "value": "export interface fetch {\n /**\n * The Accept content type to request (defaults to text/markdown).\n * @environment SHOPIFY_FLAG_CONTENT_TYPE\n */\n '--content-type '?: string\n}" + } + }, "help": { "docs-shopify.dev/commands/interfaces/help.interface.ts": { "filePath": "docs-shopify.dev/commands/interfaces/help.interface.ts", diff --git a/packages/cli/README.md b/packages/cli/README.md index f7d6f3620d6..009820c4665 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 fetch [URL]`](#shopify-fetch-url) * [`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,31 @@ DESCRIPTION Run `shopify config autoupgrade on` or `shopify config autoupgrade off` to configure it. ``` +## `shopify fetch [URL]` + +Fetch a document from shopify.dev. Defaults to Markdown. + +``` +USAGE + $ shopify fetch [URL] + +ARGUMENTS + URL The shopify.dev URL to fetch. + +FLAGS + --content-type= [env: SHOPIFY_FLAG_CONTENT_TYPE] The Accept content type to request (defaults to + text/markdown). + +DESCRIPTION + Fetch a document from shopify.dev. Defaults to Markdown. + +EXAMPLES + # fetch the Markdown version of a Shopify.dev page + shopify fetch https://shopify.dev/docs/api/shopify-cli + # fetch the HTML version of a Shopify.dev page + shopify fetch https://shopify.dev/docs/api/shopify-cli --content-type text/html +``` + ## `shopify help [command] [flags]` Display help for Shopify CLI diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index eeacfe2b751..d94ce4d09e8 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -3534,6 +3534,41 @@ "pluginType": "core", "strict": true }, + "fetch": { + "aliases": [ + ], + "args": { + "url": { + "description": "The shopify.dev URL to fetch.", + "name": "url", + "required": true + } + }, + "description": "Fetch a document from shopify.dev. Defaults to Markdown.", + "enableJsonFlag": false, + "examples": [ + "# fetch the Markdown version of a Shopify.dev page\n shopify fetch https://shopify.dev/docs/api/shopify-cli\n\n # fetch the HTML version of a Shopify.dev page\n shopify fetch https://shopify.dev/docs/api/shopify-cli --content-type text/html\n " + ], + "flags": { + "content-type": { + "description": "The Accept content type to request (defaults to text/markdown).", + "env": "SHOPIFY_FLAG_CONTENT_TYPE", + "hasDynamicHelp": false, + "multiple": false, + "name": "content-type", + "type": "option" + } + }, + "hasDynamicHelp": false, + "hiddenAliases": [ + ], + "id": "fetch", + "pluginAlias": "@shopify/cli", + "pluginName": "@shopify/cli", + "pluginType": "core", + "strict": true, + "usage": "fetch [URL]" + }, "help": { "aliases": [ ], diff --git a/packages/cli/src/cli/commands/fetch.ts b/packages/cli/src/cli/commands/fetch.ts new file mode 100644 index 00000000000..05c49c13f52 --- /dev/null +++ b/packages/cli/src/cli/commands/fetch.ts @@ -0,0 +1,38 @@ +import {fetchService} from '../services/commands/fetch.js' +import Command from '@shopify/cli-kit/node/base-command' +import {Args, Flags} from '@oclif/core' + +export default class Fetch extends Command { + static description = 'Fetch a document from shopify.dev. Defaults to Markdown.' + + static usage = `fetch [URL]` + + static examples = [ + `# fetch the Markdown version of a Shopify.dev page + shopify fetch https://shopify.dev/docs/api/shopify-cli + + # fetch the HTML version of a Shopify.dev page + shopify fetch https://shopify.dev/docs/api/shopify-cli --content-type text/html + `, + ] + + static args = { + url: Args.string({ + name: 'url', + required: true, + description: 'The shopify.dev URL to fetch.', + }), + } + + static flags = { + 'content-type': Flags.string({ + description: 'The Accept content type to request (defaults to text/markdown).', + env: 'SHOPIFY_FLAG_CONTENT_TYPE', + }), + } + + async run(): Promise { + const {args, flags} = await this.parse(Fetch) + await fetchService(args.url, flags['content-type']) + } +} diff --git a/packages/cli/src/cli/services/commands/fetch.test.ts b/packages/cli/src/cli/services/commands/fetch.test.ts new file mode 100644 index 00000000000..9826ac13486 --- /dev/null +++ b/packages/cli/src/cli/services/commands/fetch.test.ts @@ -0,0 +1,57 @@ +import {fetchService} from './fetch.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') +vi.mock('@shopify/cli-kit/node/output') + +const okResponse = (body: string) => + ({ok: true, status: 200, statusText: 'OK', text: () => Promise.resolve(body)}) as any + +beforeEach(() => { + vi.mocked(fetch).mockResolvedValue(okResponse('# Doc')) +}) + +describe('fetchService', () => { + test('requests Markdown by default and prints the body to stdout', async () => { + await fetchService('https://shopify.dev/docs/api/shopify-cli') + + expect(fetch).toHaveBeenCalledWith('https://shopify.dev/docs/api/shopify-cli', { + headers: {Accept: 'text/markdown'}, + }) + expect(outputResult).toHaveBeenCalledWith('# Doc') + }) + + test('passes a custom content type through as the Accept header', async () => { + await fetchService('https://shopify.dev/docs/api/shopify-cli', 'text/html') + + expect(fetch).toHaveBeenCalledWith('https://shopify.dev/docs/api/shopify-cli', { + headers: {Accept: 'text/html'}, + }) + }) + + test('accepts shopify.dev subdomains', async () => { + await fetchService('https://www.shopify.dev/docs') + + expect(fetch).toHaveBeenCalledOnce() + }) + + test('rejects non-shopify.dev URLs without fetching', async () => { + await expect(fetchService('https://example.com/docs')).rejects.toThrowError(AbortError) + expect(fetch).not.toHaveBeenCalled() + }) + + test('rejects malformed URLs without fetching', async () => { + await expect(fetchService('not a url')).rejects.toThrowError(AbortError) + expect(fetch).not.toHaveBeenCalled() + }) + + test('throws when the response is not ok', async () => { + vi.mocked(fetch).mockResolvedValue({ok: false, status: 404, statusText: 'Not Found'} as any) + + await expect(fetchService('https://shopify.dev/missing')).rejects.toThrowError(AbortError) + expect(outputResult).not.toHaveBeenCalled() + }) +}) diff --git a/packages/cli/src/cli/services/commands/fetch.ts b/packages/cli/src/cli/services/commands/fetch.ts new file mode 100644 index 00000000000..0f574fcf7f7 --- /dev/null +++ b/packages/cli/src/cli/services/commands/fetch.ts @@ -0,0 +1,28 @@ +import {fetch} from '@shopify/cli-kit/node/http' +import {outputResult} from '@shopify/cli-kit/node/output' +import {AbortError} from '@shopify/cli-kit/node/error' + +const DEFAULT_CONTENT_TYPE = 'text/markdown' + +export async function fetchService(url: string, contentType?: string) { + let parsedURL: URL + try { + parsedURL = new URL(url) + } catch { + throw new AbortError(`Invalid URL: ${url}`) + } + + const {hostname} = parsedURL + if (hostname !== 'shopify.dev' && !hostname.endsWith('.shopify.dev')) { + throw new AbortError('Only shopify.dev URLs can be fetched.') + } + + const accept = contentType ?? DEFAULT_CONTENT_TYPE + const response = await fetch(url, {headers: {Accept: accept}}) + + if (!response.ok) { + throw new AbortError(`Failed to fetch ${url}: ${response.status} ${response.statusText}`) + } + + outputResult(await response.text()) +} diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index ff506419c63..fe85e5561ba 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 Fetch from './cli/commands/fetch.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, + fetch: Fetch, upgrade: Upgrade, version: VersionCommand, help: HelpCommand, From 7f90212f8ebd2783acd2114770125ce9317d0666 Mon Sep 17 00:00:00 2001 From: Nelson Wittwer Date: Tue, 9 Jun 2026 10:21:19 -0400 Subject: [PATCH 2/8] Address review: rename command to fetch-doc, use allowed-hosts list - Rename command/class from `fetch`/`Fetch` to `fetch-doc`/`FetchDoc` (and the service to `fetchDocService`) to avoid an overly generic name. - Replace the hardcoded shopify.dev host check with an extensible `ALLOWED_HOSTS` array constant. - Regenerate oclif manifest, README, and shopify.dev docs. Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/add-fetch-doc-command.md | 2 +- ...tch.interface.ts => fetch-doc.interface.ts} | 4 ++-- .../generated/generated_docs_data_v2.json | 14 +++++++------- packages/cli/README.md | 10 +++++----- packages/cli/oclif.manifest.json | 8 ++++---- .../cli/commands/{fetch.ts => fetch-doc.ts} | 14 +++++++------- .../{fetch.test.ts => fetch-doc.test.ts} | 18 +++++++++--------- .../commands/{fetch.ts => fetch-doc.ts} | 11 ++++++++--- packages/cli/src/index.ts | 4 ++-- 9 files changed, 45 insertions(+), 40 deletions(-) rename docs-shopify.dev/commands/interfaces/{fetch.interface.ts => fetch-doc.interface.ts} (72%) rename packages/cli/src/cli/commands/{fetch.ts => fetch-doc.ts} (62%) rename packages/cli/src/cli/services/commands/{fetch.test.ts => fetch-doc.test.ts} (71%) rename packages/cli/src/cli/services/commands/{fetch.ts => fetch-doc.ts} (57%) diff --git a/.changeset/add-fetch-doc-command.md b/.changeset/add-fetch-doc-command.md index 3d7eb478f3b..f933a29d7f7 100644 --- a/.changeset/add-fetch-doc-command.md +++ b/.changeset/add-fetch-doc-command.md @@ -2,4 +2,4 @@ '@shopify/cli': minor --- -Add a `shopify fetch` command that downloads a document from shopify.dev and prints it to stdout. It requests the Markdown representation by default (overridable with `--content-type`), giving agents an easy way to pull instructional content from the centralized docs. +Add a `shopify fetch-doc` command that downloads a document from shopify.dev and prints it to stdout. It requests the Markdown representation by default (overridable with `--content-type`), giving agents an easy way to pull instructional content from the centralized docs. diff --git a/docs-shopify.dev/commands/interfaces/fetch.interface.ts b/docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts similarity index 72% rename from docs-shopify.dev/commands/interfaces/fetch.interface.ts rename to docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts index 9113ac73bc7..8052c1a44ee 100644 --- a/docs-shopify.dev/commands/interfaces/fetch.interface.ts +++ b/docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts @@ -1,9 +1,9 @@ // This is an autogenerated file. Don't edit this file manually. /** - * The following flags are available for the `fetch` command: + * The following flags are available for the `fetch-doc` command: * @publicDocs */ -export interface fetch { +export interface fetchdoc { /** * The Accept content type to request (defaults to text/markdown). * @environment SHOPIFY_FLAG_CONTENT_TYPE diff --git a/docs-shopify.dev/generated/generated_docs_data_v2.json b/docs-shopify.dev/generated/generated_docs_data_v2.json index 9742bd711db..be1d9b2c807 100644 --- a/docs-shopify.dev/generated/generated_docs_data_v2.json +++ b/docs-shopify.dev/generated/generated_docs_data_v2.json @@ -2742,15 +2742,15 @@ "value": "export interface configautoupgradestatus {\n\n}" } }, - "fetch": { - "docs-shopify.dev/commands/interfaces/fetch.interface.ts": { - "filePath": "docs-shopify.dev/commands/interfaces/fetch.interface.ts", - "name": "fetch", - "description": "The following flags are available for the `fetch` command:", + "fetchdoc": { + "docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts": { + "filePath": "docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts", + "name": "fetchdoc", + "description": "The following flags are available for the `fetch-doc` command:", "isPublicDocs": true, "members": [ { - "filePath": "docs-shopify.dev/commands/interfaces/fetch.interface.ts", + "filePath": "docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts", "syntaxKind": "PropertySignature", "name": "--content-type ", "value": "string", @@ -2759,7 +2759,7 @@ "environmentValue": "SHOPIFY_FLAG_CONTENT_TYPE" } ], - "value": "export interface fetch {\n /**\n * The Accept content type to request (defaults to text/markdown).\n * @environment SHOPIFY_FLAG_CONTENT_TYPE\n */\n '--content-type '?: string\n}" + "value": "export interface fetchdoc {\n /**\n * The Accept content type to request (defaults to text/markdown).\n * @environment SHOPIFY_FLAG_CONTENT_TYPE\n */\n '--content-type '?: string\n}" } }, "help": { diff --git a/packages/cli/README.md b/packages/cli/README.md index 009820c4665..fece05ede1d 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -39,7 +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 fetch [URL]`](#shopify-fetch-url) +* [`shopify fetch-doc [URL]`](#shopify-fetch-doc-url) * [`shopify help [command] [flags]`](#shopify-help-command-flags) * [`shopify hydrogen build`](#shopify-hydrogen-build) * [`shopify hydrogen check RESOURCE`](#shopify-hydrogen-check-resource) @@ -1213,13 +1213,13 @@ DESCRIPTION Run `shopify config autoupgrade on` or `shopify config autoupgrade off` to configure it. ``` -## `shopify fetch [URL]` +## `shopify fetch-doc [URL]` Fetch a document from shopify.dev. Defaults to Markdown. ``` USAGE - $ shopify fetch [URL] + $ shopify fetch-doc [URL] ARGUMENTS URL The shopify.dev URL to fetch. @@ -1233,9 +1233,9 @@ DESCRIPTION EXAMPLES # fetch the Markdown version of a Shopify.dev page - shopify fetch https://shopify.dev/docs/api/shopify-cli + shopify fetch-doc https://shopify.dev/docs/api/shopify-cli # fetch the HTML version of a Shopify.dev page - shopify fetch https://shopify.dev/docs/api/shopify-cli --content-type text/html + shopify fetch-doc https://shopify.dev/docs/api/shopify-cli --content-type text/html ``` ## `shopify help [command] [flags]` diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index d94ce4d09e8..1c4d0c5e117 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -3534,7 +3534,7 @@ "pluginType": "core", "strict": true }, - "fetch": { + "fetch-doc": { "aliases": [ ], "args": { @@ -3547,7 +3547,7 @@ "description": "Fetch a document from shopify.dev. Defaults to Markdown.", "enableJsonFlag": false, "examples": [ - "# fetch the Markdown version of a Shopify.dev page\n shopify fetch https://shopify.dev/docs/api/shopify-cli\n\n # fetch the HTML version of a Shopify.dev page\n shopify fetch https://shopify.dev/docs/api/shopify-cli --content-type text/html\n " + "# fetch the Markdown version of a Shopify.dev page\n shopify fetch-doc https://shopify.dev/docs/api/shopify-cli\n\n # fetch the HTML version of a Shopify.dev page\n shopify fetch-doc https://shopify.dev/docs/api/shopify-cli --content-type text/html\n " ], "flags": { "content-type": { @@ -3562,12 +3562,12 @@ "hasDynamicHelp": false, "hiddenAliases": [ ], - "id": "fetch", + "id": "fetch-doc", "pluginAlias": "@shopify/cli", "pluginName": "@shopify/cli", "pluginType": "core", "strict": true, - "usage": "fetch [URL]" + "usage": "fetch-doc [URL]" }, "help": { "aliases": [ diff --git a/packages/cli/src/cli/commands/fetch.ts b/packages/cli/src/cli/commands/fetch-doc.ts similarity index 62% rename from packages/cli/src/cli/commands/fetch.ts rename to packages/cli/src/cli/commands/fetch-doc.ts index 05c49c13f52..dc12431adfd 100644 --- a/packages/cli/src/cli/commands/fetch.ts +++ b/packages/cli/src/cli/commands/fetch-doc.ts @@ -1,18 +1,18 @@ -import {fetchService} from '../services/commands/fetch.js' +import {fetchDocService} from '../services/commands/fetch-doc.js' import Command from '@shopify/cli-kit/node/base-command' import {Args, Flags} from '@oclif/core' -export default class Fetch extends Command { +export default class FetchDoc extends Command { static description = 'Fetch a document from shopify.dev. Defaults to Markdown.' - static usage = `fetch [URL]` + static usage = `fetch-doc [URL]` static examples = [ `# fetch the Markdown version of a Shopify.dev page - shopify fetch https://shopify.dev/docs/api/shopify-cli + shopify fetch-doc https://shopify.dev/docs/api/shopify-cli # fetch the HTML version of a Shopify.dev page - shopify fetch https://shopify.dev/docs/api/shopify-cli --content-type text/html + shopify fetch-doc https://shopify.dev/docs/api/shopify-cli --content-type text/html `, ] @@ -32,7 +32,7 @@ export default class Fetch extends Command { } async run(): Promise { - const {args, flags} = await this.parse(Fetch) - await fetchService(args.url, flags['content-type']) + const {args, flags} = await this.parse(FetchDoc) + await fetchDocService(args.url, flags['content-type']) } } diff --git a/packages/cli/src/cli/services/commands/fetch.test.ts b/packages/cli/src/cli/services/commands/fetch-doc.test.ts similarity index 71% rename from packages/cli/src/cli/services/commands/fetch.test.ts rename to packages/cli/src/cli/services/commands/fetch-doc.test.ts index 9826ac13486..49b6d8994b5 100644 --- a/packages/cli/src/cli/services/commands/fetch.test.ts +++ b/packages/cli/src/cli/services/commands/fetch-doc.test.ts @@ -1,4 +1,4 @@ -import {fetchService} from './fetch.js' +import {fetchDocService} from './fetch-doc.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' @@ -14,9 +14,9 @@ beforeEach(() => { vi.mocked(fetch).mockResolvedValue(okResponse('# Doc')) }) -describe('fetchService', () => { +describe('fetchDocService', () => { test('requests Markdown by default and prints the body to stdout', async () => { - await fetchService('https://shopify.dev/docs/api/shopify-cli') + await fetchDocService('https://shopify.dev/docs/api/shopify-cli') expect(fetch).toHaveBeenCalledWith('https://shopify.dev/docs/api/shopify-cli', { headers: {Accept: 'text/markdown'}, @@ -25,7 +25,7 @@ describe('fetchService', () => { }) test('passes a custom content type through as the Accept header', async () => { - await fetchService('https://shopify.dev/docs/api/shopify-cli', 'text/html') + await fetchDocService('https://shopify.dev/docs/api/shopify-cli', 'text/html') expect(fetch).toHaveBeenCalledWith('https://shopify.dev/docs/api/shopify-cli', { headers: {Accept: 'text/html'}, @@ -33,25 +33,25 @@ describe('fetchService', () => { }) test('accepts shopify.dev subdomains', async () => { - await fetchService('https://www.shopify.dev/docs') + await fetchDocService('https://www.shopify.dev/docs') expect(fetch).toHaveBeenCalledOnce() }) - test('rejects non-shopify.dev URLs without fetching', async () => { - await expect(fetchService('https://example.com/docs')).rejects.toThrowError(AbortError) + test('rejects URLs from disallowed hosts without fetching', async () => { + await expect(fetchDocService('https://example.com/docs')).rejects.toThrowError(AbortError) expect(fetch).not.toHaveBeenCalled() }) test('rejects malformed URLs without fetching', async () => { - await expect(fetchService('not a url')).rejects.toThrowError(AbortError) + await expect(fetchDocService('not a url')).rejects.toThrowError(AbortError) expect(fetch).not.toHaveBeenCalled() }) test('throws when the response is not ok', async () => { vi.mocked(fetch).mockResolvedValue({ok: false, status: 404, statusText: 'Not Found'} as any) - await expect(fetchService('https://shopify.dev/missing')).rejects.toThrowError(AbortError) + await expect(fetchDocService('https://shopify.dev/missing')).rejects.toThrowError(AbortError) expect(outputResult).not.toHaveBeenCalled() }) }) diff --git a/packages/cli/src/cli/services/commands/fetch.ts b/packages/cli/src/cli/services/commands/fetch-doc.ts similarity index 57% rename from packages/cli/src/cli/services/commands/fetch.ts rename to packages/cli/src/cli/services/commands/fetch-doc.ts index 0f574fcf7f7..b912a89cad1 100644 --- a/packages/cli/src/cli/services/commands/fetch.ts +++ b/packages/cli/src/cli/services/commands/fetch-doc.ts @@ -4,7 +4,11 @@ import {AbortError} from '@shopify/cli-kit/node/error' const DEFAULT_CONTENT_TYPE = 'text/markdown' -export async function fetchService(url: string, contentType?: string) { +// Hosts whose documents are allowed to be fetched. A URL matches when its +// hostname is one of these or a subdomain of one of these. +const ALLOWED_HOSTS = ['shopify.dev'] + +export async function fetchDocService(url: string, contentType?: string) { let parsedURL: URL try { parsedURL = new URL(url) @@ -13,8 +17,9 @@ export async function fetchService(url: string, contentType?: string) { } const {hostname} = parsedURL - if (hostname !== 'shopify.dev' && !hostname.endsWith('.shopify.dev')) { - throw new AbortError('Only shopify.dev URLs can be fetched.') + const isAllowed = ALLOWED_HOSTS.some((host) => hostname === host || hostname.endsWith(`.${host}`)) + if (!isAllowed) { + throw new AbortError(`Only documents from the following hosts can be fetched: ${ALLOWED_HOSTS.join(', ')}.`) } const accept = contentType ?? DEFAULT_CONTENT_TYPE diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index fe85e5561ba..e006a551047 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,6 +1,6 @@ import VersionCommand from './cli/commands/version.js' import Search from './cli/commands/search.js' -import Fetch from './cli/commands/fetch.js' +import FetchDoc from './cli/commands/fetch-doc.js' import Upgrade from './cli/commands/upgrade.js' import Logout from './cli/commands/auth/logout.js' import Login from './cli/commands/auth/login.js' @@ -146,7 +146,7 @@ export const COMMANDS: any = { ...HydrogenCommands, ...StoreCommands, search: Search, - fetch: Fetch, + 'fetch-doc': FetchDoc, upgrade: Upgrade, version: VersionCommand, help: HelpCommand, From 173917d505d23792e7e3c5cc53870dac09ce5182 Mon Sep 17 00:00:00 2001 From: Nelson Wittwer Date: Tue, 9 Jun 2026 10:32:44 -0400 Subject: [PATCH 3/8] Update e2e command-tree snapshot for fetch-doc Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/e2e/data/snapshots/commands.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/e2e/data/snapshots/commands.txt b/packages/e2e/data/snapshots/commands.txt index 9e9afb6c946..3fcfd6c99c3 100644 --- a/packages/e2e/data/snapshots/commands.txt +++ b/packages/e2e/data/snapshots/commands.txt @@ -49,6 +49,7 @@ │ ├─ off │ ├─ on │ └─ status +├─ fetch-doc ├─ help ├─ hydrogen │ ├─ build From 587ae574092ddd22247c748a5358a4d855c1538f Mon Sep 17 00:00:00 2001 From: Nelson Wittwer Date: Tue, 9 Jun 2026 11:16:00 -0400 Subject: [PATCH 4/8] Clarify search and fetch-doc command descriptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Distinguish the two docs commands by intent: - search: discovery — surface the most relevant pieces of content. - fetch-doc: retrieve a complete document verbatim, like a centrally-served skill an agent follows in its entirety. Each description cross-references the other so agents pick the right tool. Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/cli/README.md | 12 ++++++++---- packages/cli/oclif.manifest.json | 4 ++-- packages/cli/src/cli/commands/fetch-doc.ts | 3 ++- packages/cli/src/cli/commands/search.ts | 3 ++- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index fece05ede1d..8eb1f9ca9b8 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -1215,7 +1215,7 @@ DESCRIPTION ## `shopify fetch-doc [URL]` -Fetch a document from shopify.dev. Defaults to Markdown. +Download a complete document from shopify.dev, returned as Markdown by default. Use this to pull an entire document verbatim — for example, a set of instructions an agent follows like a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `search`. ``` USAGE @@ -1229,7 +1229,9 @@ FLAGS text/markdown). DESCRIPTION - Fetch a document from shopify.dev. Defaults to Markdown. + Download a complete document from shopify.dev, returned as Markdown by default. Use this to pull an entire document + verbatim — for example, a set of instructions an agent follows like a centrally-served skill. For finding the relevant + pieces of content across shopify.dev instead, use `search`. EXAMPLES # fetch the Markdown version of a Shopify.dev page @@ -2109,14 +2111,16 @@ DESCRIPTION ## `shopify search [query]` -Starts a search on shopify.dev. +Search shopify.dev for the most relevant content matching a query. Best for 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] DESCRIPTION - Starts a search on shopify.dev. + Search shopify.dev for the most relevant content matching a query. Best for 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 diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index 1c4d0c5e117..cef8bc85328 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -3544,7 +3544,7 @@ "required": true } }, - "description": "Fetch a document from shopify.dev. Defaults to Markdown.", + "description": "Download a complete document from shopify.dev, returned as Markdown by default. Use this to pull an entire document verbatim — for example, a set of instructions an agent follows like a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `search`.", "enableJsonFlag": false, "examples": [ "# fetch the Markdown version of a Shopify.dev page\n shopify fetch-doc https://shopify.dev/docs/api/shopify-cli\n\n # fetch the HTML version of a Shopify.dev page\n shopify fetch-doc https://shopify.dev/docs/api/shopify-cli --content-type text/html\n " @@ -5687,7 +5687,7 @@ "name": "query" } }, - "description": "Starts a search on shopify.dev.", + "description": "Search shopify.dev for the most relevant content matching a query. Best for 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 " diff --git a/packages/cli/src/cli/commands/fetch-doc.ts b/packages/cli/src/cli/commands/fetch-doc.ts index dc12431adfd..566c7efbdd3 100644 --- a/packages/cli/src/cli/commands/fetch-doc.ts +++ b/packages/cli/src/cli/commands/fetch-doc.ts @@ -3,7 +3,8 @@ import Command from '@shopify/cli-kit/node/base-command' import {Args, Flags} from '@oclif/core' export default class FetchDoc extends Command { - static description = 'Fetch a document from shopify.dev. Defaults to Markdown.' + static description = + 'Download a complete document from shopify.dev, returned as Markdown by default. Use this to pull an entire document verbatim — for example, a set of instructions an agent follows like a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `search`.' static usage = `fetch-doc [URL]` diff --git a/packages/cli/src/cli/commands/search.ts b/packages/cli/src/cli/commands/search.ts index bb1346b8341..e2f4d73cd97 100644 --- a/packages/cli/src/cli/commands/search.ts +++ b/packages/cli/src/cli/commands/search.ts @@ -3,7 +3,8 @@ import Command from '@shopify/cli-kit/node/base-command' import {Args} from '@oclif/core' export default class Search extends Command { - static description = 'Starts a search on shopify.dev.' + static description = + 'Search shopify.dev for the most relevant content matching a query. Best for 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]` From 8c58c0c4a5db40f427892c8e06a35b79431fa94b Mon Sep 17 00:00:00 2001 From: Nelson Wittwer Date: Tue, 9 Jun 2026 11:17:29 -0400 Subject: [PATCH 5/8] Note in fetch-doc description that shopify.dev serves Markdown for every page Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/cli/README.md | 9 +++++---- packages/cli/oclif.manifest.json | 2 +- packages/cli/src/cli/commands/fetch-doc.ts | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index 8eb1f9ca9b8..2f29d648526 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -1215,7 +1215,7 @@ DESCRIPTION ## `shopify fetch-doc [URL]` -Download a complete document from shopify.dev, returned as Markdown by default. Use this to pull an entire document verbatim — for example, a set of instructions an agent follows like a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `search`. +Download a complete document from shopify.dev. Every page on shopify.dev has a Markdown version, and that is what this tool returns by default. Use this to pull an entire document verbatim — for example, a set of instructions an agent follows like a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `search`. ``` USAGE @@ -1229,9 +1229,10 @@ FLAGS text/markdown). DESCRIPTION - Download a complete document from shopify.dev, returned as Markdown by default. Use this to pull an entire document - verbatim — for example, a set of instructions an agent follows like a centrally-served skill. For finding the relevant - pieces of content across shopify.dev instead, use `search`. + Download a complete document from shopify.dev. Every page on shopify.dev has a Markdown version, and that is what this + tool returns by default. Use this to pull an entire document verbatim — for example, a set of instructions an agent + follows like a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use + `search`. EXAMPLES # fetch the Markdown version of a Shopify.dev page diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index cef8bc85328..32b5bb4e0be 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -3544,7 +3544,7 @@ "required": true } }, - "description": "Download a complete document from shopify.dev, returned as Markdown by default. Use this to pull an entire document verbatim — for example, a set of instructions an agent follows like a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `search`.", + "description": "Download a complete document from shopify.dev. Every page on shopify.dev has a Markdown version, and that is what this tool returns by default. Use this to pull an entire document verbatim — for example, a set of instructions an agent follows like a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `search`.", "enableJsonFlag": false, "examples": [ "# fetch the Markdown version of a Shopify.dev page\n shopify fetch-doc https://shopify.dev/docs/api/shopify-cli\n\n # fetch the HTML version of a Shopify.dev page\n shopify fetch-doc https://shopify.dev/docs/api/shopify-cli --content-type text/html\n " diff --git a/packages/cli/src/cli/commands/fetch-doc.ts b/packages/cli/src/cli/commands/fetch-doc.ts index 566c7efbdd3..815088b7fff 100644 --- a/packages/cli/src/cli/commands/fetch-doc.ts +++ b/packages/cli/src/cli/commands/fetch-doc.ts @@ -4,7 +4,7 @@ import {Args, Flags} from '@oclif/core' export default class FetchDoc extends Command { static description = - 'Download a complete document from shopify.dev, returned as Markdown by default. Use this to pull an entire document verbatim — for example, a set of instructions an agent follows like a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `search`.' + 'Download a complete document from shopify.dev. Every page on shopify.dev has a Markdown version, and that is what this tool returns by default. Use this to pull an entire document verbatim — for example, a set of instructions an agent follows like a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `search`.' static usage = `fetch-doc [URL]` From 9b7e62ba110c00a34eadb554108ecdbe0bda561a Mon Sep 17 00:00:00 2001 From: Nelson Wittwer Date: Tue, 9 Jun 2026 11:28:28 -0400 Subject: [PATCH 6/8] Add global flags (--verbose, --no-color) to fetch-doc and search These commands previously rejected the CLI's standard global flags. Adding `globalFlags` makes them consistent with the rest of the CLI and lets `--verbose` surface the Monorail analytics payload for local verification. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../interfaces/fetch-doc.interface.ts | 12 ++++++ .../commands/interfaces/search.interface.ts | 10 +++++ .../generated/generated_docs_data_v2.json | 43 +++++++++++++++++-- packages/cli/README.md | 6 +++ packages/cli/oclif.manifest.json | 32 ++++++++++++++ packages/cli/src/cli/commands/fetch-doc.ts | 2 + packages/cli/src/cli/commands/search.ts | 5 +++ 7 files changed, 107 insertions(+), 3 deletions(-) diff --git a/docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts b/docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts index 8052c1a44ee..7c7d1b88049 100644 --- a/docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts +++ b/docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts @@ -9,4 +9,16 @@ export interface fetchdoc { * @environment SHOPIFY_FLAG_CONTENT_TYPE */ '--content-type '?: 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 041775f0570..618c86f1f7f 100644 --- a/docs-shopify.dev/commands/interfaces/search.interface.ts +++ b/docs-shopify.dev/commands/interfaces/search.interface.ts @@ -4,5 +4,15 @@ * @publicDocs */ export interface search { + /** + * 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 be1d9b2c807..97c4b139952 100644 --- a/docs-shopify.dev/generated/generated_docs_data_v2.json +++ b/docs-shopify.dev/generated/generated_docs_data_v2.json @@ -2757,9 +2757,27 @@ "description": "The Accept content type to request (defaults to text/markdown).", "isOptional": true, "environmentValue": "SHOPIFY_FLAG_CONTENT_TYPE" + }, + { + "filePath": "docs-shopify.dev/commands/interfaces/fetch-doc.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/fetch-doc.interface.ts", + "syntaxKind": "PropertySignature", + "name": "--verbose", + "value": "''", + "description": "Increase the verbosity of the output.", + "isOptional": true, + "environmentValue": "SHOPIFY_FLAG_VERBOSE" } ], - "value": "export interface fetchdoc {\n /**\n * The Accept content type to request (defaults to text/markdown).\n * @environment SHOPIFY_FLAG_CONTENT_TYPE\n */\n '--content-type '?: string\n}" + "value": "export interface fetchdoc {\n /**\n * The Accept content type to request (defaults to text/markdown).\n * @environment SHOPIFY_FLAG_CONTENT_TYPE\n */\n '--content-type '?: 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": { @@ -4146,8 +4164,27 @@ "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": "--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 * 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 2f29d648526..f6cf38dd0ab 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -1227,6 +1227,8 @@ ARGUMENTS FLAGS --content-type= [env: SHOPIFY_FLAG_CONTENT_TYPE] The Accept content type to request (defaults to text/markdown). + --no-color [env: SHOPIFY_FLAG_NO_COLOR] Disable color output. + --verbose [env: SHOPIFY_FLAG_VERBOSE] Increase the verbosity of the output. DESCRIPTION Download a complete document from shopify.dev. Every page on shopify.dev has a Markdown version, and that is what this @@ -2118,6 +2120,10 @@ Search shopify.dev for the most relevant content matching a query. Best for disc USAGE $ shopify search [query] +FLAGS + --no-color [env: SHOPIFY_FLAG_NO_COLOR] Disable color output. + --verbose [env: SHOPIFY_FLAG_VERBOSE] Increase the verbosity of the output. + DESCRIPTION Search shopify.dev for the most relevant content matching a query. Best for discovery — surfacing the relevant pieces of documentation for a topic, rather than retrieving a whole document. To download a full document verbatim, use diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index 32b5bb4e0be..f15f7168626 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -3557,6 +3557,22 @@ "multiple": false, "name": "content-type", "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, @@ -5693,6 +5709,22 @@ "# 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": { + "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/fetch-doc.ts b/packages/cli/src/cli/commands/fetch-doc.ts index 815088b7fff..cd2113fec24 100644 --- a/packages/cli/src/cli/commands/fetch-doc.ts +++ b/packages/cli/src/cli/commands/fetch-doc.ts @@ -1,5 +1,6 @@ import {fetchDocService} from '../services/commands/fetch-doc.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 FetchDoc extends Command { @@ -26,6 +27,7 @@ export default class FetchDoc extends Command { } static flags = { + ...globalFlags, 'content-type': Flags.string({ description: 'The Accept content type to request (defaults to text/markdown).', env: 'SHOPIFY_FLAG_CONTENT_TYPE', diff --git a/packages/cli/src/cli/commands/search.ts b/packages/cli/src/cli/commands/search.ts index e2f4d73cd97..f225aa0572f 100644 --- a/packages/cli/src/cli/commands/search.ts +++ b/packages/cli/src/cli/commands/search.ts @@ -1,5 +1,6 @@ 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} from '@oclif/core' export default class Search extends Command { @@ -24,6 +25,10 @@ export default class Search extends Command { query: Args.string(), } + static flags = { + ...globalFlags, + } + async run(): Promise { const {args} = await this.parse(Search) await searchService(args.query) From 32e22ff7f98e1ce7a0337901ee2a4b6e22097ebe Mon Sep 17 00:00:00 2001 From: Nelson Wittwer Date: Tue, 9 Jun 2026 11:59:24 -0400 Subject: [PATCH 7/8] Add --output flag and drop --content-type from fetch-doc - `--output ` (`-o`) writes the document to a file (creating any missing parent directories) instead of printing to stdout. - Remove the `--content-type` flag: fetch-doc now always requests the Markdown representation. Returning HTML is noisy and expensive and not a behavior we want to encourage. - Regenerate oclif manifest, README, and shopify.dev docs. Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/add-fetch-doc-command.md | 2 +- .../interfaces/fetch-doc.interface.ts | 12 ++++----- .../generated/generated_docs_data_v2.json | 20 +++++++------- packages/cli/README.md | 24 +++++++++-------- packages/cli/oclif.manifest.json | 22 ++++++++------- packages/cli/src/cli/commands/fetch-doc.ts | 19 +++++++------ .../cli/services/commands/fetch-doc.test.ts | 22 ++++++++------- .../src/cli/services/commands/fetch-doc.ts | 27 ++++++++++++++----- 8 files changed, 85 insertions(+), 63 deletions(-) diff --git a/.changeset/add-fetch-doc-command.md b/.changeset/add-fetch-doc-command.md index f933a29d7f7..6f6d0eb0f10 100644 --- a/.changeset/add-fetch-doc-command.md +++ b/.changeset/add-fetch-doc-command.md @@ -2,4 +2,4 @@ '@shopify/cli': minor --- -Add a `shopify fetch-doc` command that downloads a document from shopify.dev and prints it to stdout. It requests the Markdown representation by default (overridable with `--content-type`), giving agents an easy way to pull instructional content from the centralized docs. +Add a `shopify fetch-doc` command that downloads a document from shopify.dev and prints it to stdout, or writes it to a file with `--output`. It requests the Markdown representation that every shopify.dev page has, giving agents an easy way to pull instructional content from the centralized docs verbatim. diff --git a/docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts b/docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts index 7c7d1b88049..f4555fcbd94 100644 --- a/docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts +++ b/docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts @@ -4,18 +4,18 @@ * @publicDocs */ export interface fetchdoc { - /** - * The Accept content type to request (defaults to text/markdown). - * @environment SHOPIFY_FLAG_CONTENT_TYPE - */ - '--content-type '?: string - /** * Disable color output. * @environment SHOPIFY_FLAG_NO_COLOR */ '--no-color'?: '' + /** + * Write the document to this file path instead of printing it to stdout. + * @environment SHOPIFY_FLAG_OUTPUT + */ + '-o, --output '?: string + /** * Increase the verbosity of the output. * @environment SHOPIFY_FLAG_VERBOSE diff --git a/docs-shopify.dev/generated/generated_docs_data_v2.json b/docs-shopify.dev/generated/generated_docs_data_v2.json index 97c4b139952..e2b1725e60b 100644 --- a/docs-shopify.dev/generated/generated_docs_data_v2.json +++ b/docs-shopify.dev/generated/generated_docs_data_v2.json @@ -2749,15 +2749,6 @@ "description": "The following flags are available for the `fetch-doc` command:", "isPublicDocs": true, "members": [ - { - "filePath": "docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts", - "syntaxKind": "PropertySignature", - "name": "--content-type ", - "value": "string", - "description": "The Accept content type to request (defaults to text/markdown).", - "isOptional": true, - "environmentValue": "SHOPIFY_FLAG_CONTENT_TYPE" - }, { "filePath": "docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts", "syntaxKind": "PropertySignature", @@ -2775,9 +2766,18 @@ "description": "Increase the verbosity of the output.", "isOptional": true, "environmentValue": "SHOPIFY_FLAG_VERBOSE" + }, + { + "filePath": "docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts", + "syntaxKind": "PropertySignature", + "name": "-o, --output ", + "value": "string", + "description": "Write the document to this file path instead of printing it to stdout.", + "isOptional": true, + "environmentValue": "SHOPIFY_FLAG_OUTPUT" } ], - "value": "export interface fetchdoc {\n /**\n * The Accept content type to request (defaults to text/markdown).\n * @environment SHOPIFY_FLAG_CONTENT_TYPE\n */\n '--content-type '?: 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}" + "value": "export interface fetchdoc {\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Write the document to this file path instead of printing it to stdout.\n * @environment SHOPIFY_FLAG_OUTPUT\n */\n '-o, --output '?: string\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}" } }, "help": { diff --git a/packages/cli/README.md b/packages/cli/README.md index f6cf38dd0ab..84ddb173523 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -1215,7 +1215,7 @@ DESCRIPTION ## `shopify fetch-doc [URL]` -Download a complete document from shopify.dev. Every page on shopify.dev has a Markdown version, and that is what this tool returns by default. Use this to pull an entire document verbatim — for example, a set of instructions an agent follows like a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `search`. +Download a complete document from shopify.dev. Every page on shopify.dev has a Markdown version, and that is what this tool returns. Use this to pull an entire document verbatim — for example, a set of instructions an agent follows like a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `search`. ``` USAGE @@ -1225,22 +1225,24 @@ ARGUMENTS URL The shopify.dev URL to fetch. FLAGS - --content-type= [env: SHOPIFY_FLAG_CONTENT_TYPE] The Accept content type to request (defaults to - text/markdown). - --no-color [env: SHOPIFY_FLAG_NO_COLOR] Disable color output. - --verbose [env: SHOPIFY_FLAG_VERBOSE] Increase the verbosity of the output. + -o, --output= [env: SHOPIFY_FLAG_OUTPUT] Write the document to this file path instead of printing it to + stdout. + --no-color [env: SHOPIFY_FLAG_NO_COLOR] Disable color output. + --verbose [env: SHOPIFY_FLAG_VERBOSE] Increase the verbosity of the output. DESCRIPTION Download a complete document from shopify.dev. Every page on shopify.dev has a Markdown version, and that is what this - tool returns by default. Use this to pull an entire document verbatim — for example, a set of instructions an agent - follows like a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use - `search`. + tool returns. Use this to pull an entire document verbatim — for example, a set of instructions an agent follows like + a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `search`. EXAMPLES # fetch the Markdown version of a Shopify.dev page - shopify fetch-doc https://shopify.dev/docs/api/shopify-cli - # fetch the HTML version of a Shopify.dev page - shopify fetch-doc https://shopify.dev/docs/api/shopify-cli --content-type text/html + + $ shopify fetch-doc https://shopify.dev/docs/api/shopify-cli + + # save the document to a file instead of printing it + + $ shopify fetch-doc https://shopify.dev/docs/api/shopify-cli --output docs/shopify-cli.md ``` ## `shopify help [command] [flags]` diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index f15f7168626..bb3efc59271 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -3544,20 +3544,13 @@ "required": true } }, - "description": "Download a complete document from shopify.dev. Every page on shopify.dev has a Markdown version, and that is what this tool returns by default. Use this to pull an entire document verbatim — for example, a set of instructions an agent follows like a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `search`.", + "description": "Download a complete document from shopify.dev. Every page on shopify.dev has a Markdown version, and that is what this tool returns. Use this to pull an entire document verbatim — for example, a set of instructions an agent follows like a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `search`.", "enableJsonFlag": false, "examples": [ - "# fetch the Markdown version of a Shopify.dev page\n shopify fetch-doc https://shopify.dev/docs/api/shopify-cli\n\n # fetch the HTML version of a Shopify.dev page\n shopify fetch-doc https://shopify.dev/docs/api/shopify-cli --content-type text/html\n " + "# fetch the Markdown version of a Shopify.dev page\nshopify fetch-doc https://shopify.dev/docs/api/shopify-cli", + "# save the document to a file instead of printing it\nshopify fetch-doc https://shopify.dev/docs/api/shopify-cli --output docs/shopify-cli.md" ], "flags": { - "content-type": { - "description": "The Accept content type to request (defaults to text/markdown).", - "env": "SHOPIFY_FLAG_CONTENT_TYPE", - "hasDynamicHelp": false, - "multiple": false, - "name": "content-type", - "type": "option" - }, "no-color": { "allowNo": false, "description": "Disable color output.", @@ -3566,6 +3559,15 @@ "name": "no-color", "type": "boolean" }, + "output": { + "char": "o", + "description": "Write the document to this file path instead of printing it to stdout.", + "env": "SHOPIFY_FLAG_OUTPUT", + "hasDynamicHelp": false, + "multiple": false, + "name": "output", + "type": "option" + }, "verbose": { "allowNo": false, "description": "Increase the verbosity of the output.", diff --git a/packages/cli/src/cli/commands/fetch-doc.ts b/packages/cli/src/cli/commands/fetch-doc.ts index cd2113fec24..7b3d41bb7b1 100644 --- a/packages/cli/src/cli/commands/fetch-doc.ts +++ b/packages/cli/src/cli/commands/fetch-doc.ts @@ -5,17 +5,15 @@ import {Args, Flags} from '@oclif/core' export default class FetchDoc extends Command { static description = - 'Download a complete document from shopify.dev. Every page on shopify.dev has a Markdown version, and that is what this tool returns by default. Use this to pull an entire document verbatim — for example, a set of instructions an agent follows like a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `search`.' + 'Download a complete document from shopify.dev. Every page on shopify.dev has a Markdown version, and that is what this tool returns. Use this to pull an entire document verbatim — for example, a set of instructions an agent follows like a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `search`.' static usage = `fetch-doc [URL]` static examples = [ `# fetch the Markdown version of a Shopify.dev page - shopify fetch-doc https://shopify.dev/docs/api/shopify-cli - - # fetch the HTML version of a Shopify.dev page - shopify fetch-doc https://shopify.dev/docs/api/shopify-cli --content-type text/html - `, +shopify fetch-doc https://shopify.dev/docs/api/shopify-cli`, + `# save the document to a file instead of printing it +shopify fetch-doc https://shopify.dev/docs/api/shopify-cli --output docs/shopify-cli.md`, ] static args = { @@ -28,14 +26,15 @@ export default class FetchDoc extends Command { static flags = { ...globalFlags, - 'content-type': Flags.string({ - description: 'The Accept content type to request (defaults to text/markdown).', - env: 'SHOPIFY_FLAG_CONTENT_TYPE', + output: Flags.string({ + char: 'o', + description: 'Write the document to this file path instead of printing it to stdout.', + env: 'SHOPIFY_FLAG_OUTPUT', }), } async run(): Promise { const {args, flags} = await this.parse(FetchDoc) - await fetchDocService(args.url, flags['content-type']) + await fetchDocService(args.url, flags.output) } } diff --git a/packages/cli/src/cli/services/commands/fetch-doc.test.ts b/packages/cli/src/cli/services/commands/fetch-doc.test.ts index 49b6d8994b5..421a73815d3 100644 --- a/packages/cli/src/cli/services/commands/fetch-doc.test.ts +++ b/packages/cli/src/cli/services/commands/fetch-doc.test.ts @@ -3,9 +3,12 @@ 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 {mkdir, writeFile} from '@shopify/cli-kit/node/fs' +import {dirname, resolvePath} from '@shopify/cli-kit/node/path' vi.mock('@shopify/cli-kit/node/http') vi.mock('@shopify/cli-kit/node/output') +vi.mock('@shopify/cli-kit/node/fs') const okResponse = (body: string) => ({ok: true, status: 200, statusText: 'OK', text: () => Promise.resolve(body)}) as any @@ -15,7 +18,7 @@ beforeEach(() => { }) describe('fetchDocService', () => { - test('requests Markdown by default and prints the body to stdout', async () => { + test('requests Markdown and prints the body to stdout', async () => { await fetchDocService('https://shopify.dev/docs/api/shopify-cli') expect(fetch).toHaveBeenCalledWith('https://shopify.dev/docs/api/shopify-cli', { @@ -24,14 +27,6 @@ describe('fetchDocService', () => { expect(outputResult).toHaveBeenCalledWith('# Doc') }) - test('passes a custom content type through as the Accept header', async () => { - await fetchDocService('https://shopify.dev/docs/api/shopify-cli', 'text/html') - - expect(fetch).toHaveBeenCalledWith('https://shopify.dev/docs/api/shopify-cli', { - headers: {Accept: 'text/html'}, - }) - }) - test('accepts shopify.dev subdomains', async () => { await fetchDocService('https://www.shopify.dev/docs') @@ -48,6 +43,15 @@ describe('fetchDocService', () => { expect(fetch).not.toHaveBeenCalled() }) + test('writes the document to the output path instead of stdout', async () => { + await fetchDocService('https://shopify.dev/docs/api/shopify-cli', 'docs/shopify-cli.md') + + const expectedPath = resolvePath('docs/shopify-cli.md') + expect(mkdir).toHaveBeenCalledWith(dirname(expectedPath)) + expect(writeFile).toHaveBeenCalledWith(expectedPath, '# Doc') + expect(outputResult).not.toHaveBeenCalled() + }) + test('throws when the response is not ok', async () => { vi.mocked(fetch).mockResolvedValue({ok: false, status: 404, statusText: 'Not Found'} as any) diff --git a/packages/cli/src/cli/services/commands/fetch-doc.ts b/packages/cli/src/cli/services/commands/fetch-doc.ts index b912a89cad1..967a5f788eb 100644 --- a/packages/cli/src/cli/services/commands/fetch-doc.ts +++ b/packages/cli/src/cli/services/commands/fetch-doc.ts @@ -1,14 +1,18 @@ import {fetch} from '@shopify/cli-kit/node/http' -import {outputResult} from '@shopify/cli-kit/node/output' +import {outputInfo, outputResult} from '@shopify/cli-kit/node/output' import {AbortError} from '@shopify/cli-kit/node/error' +import {mkdir, writeFile} from '@shopify/cli-kit/node/fs' +import {dirname, resolvePath} from '@shopify/cli-kit/node/path' -const DEFAULT_CONTENT_TYPE = 'text/markdown' +// Every page on shopify.dev has a Markdown representation, which is the clean, +// parseable content agents want — so we always request it. +const MARKDOWN_CONTENT_TYPE = 'text/markdown' // Hosts whose documents are allowed to be fetched. A URL matches when its // hostname is one of these or a subdomain of one of these. const ALLOWED_HOSTS = ['shopify.dev'] -export async function fetchDocService(url: string, contentType?: string) { +export async function fetchDocService(url: string, outputPath?: string) { let parsedURL: URL try { parsedURL = new URL(url) @@ -22,12 +26,23 @@ export async function fetchDocService(url: string, contentType?: string) { throw new AbortError(`Only documents from the following hosts can be fetched: ${ALLOWED_HOSTS.join(', ')}.`) } - const accept = contentType ?? DEFAULT_CONTENT_TYPE - const response = await fetch(url, {headers: {Accept: accept}}) + const response = await fetch(url, {headers: {Accept: MARKDOWN_CONTENT_TYPE}}) if (!response.ok) { throw new AbortError(`Failed to fetch ${url}: ${response.status} ${response.statusText}`) } - outputResult(await response.text()) + const body = await response.text() + + // When an output path is provided, write the document to disk (creating any + // missing parent directories) instead of printing it to stdout. + if (outputPath) { + const absolutePath = resolvePath(outputPath) + await mkdir(dirname(absolutePath)) + await writeFile(absolutePath, body) + outputInfo(`Saved ${url} to ${absolutePath}`) + return + } + + outputResult(body) } From 85aa853971d8ba2da0155fe6bd15f8299fc57c60 Mon Sep 17 00:00:00 2001 From: Nelson Wittwer Date: Wed, 10 Jun 2026 14:43:22 -0400 Subject: [PATCH 8/8] Move `fetch-doc` into the `doc` namespace as `doc fetch` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per the doc-namespace plan, documentation commands live under a `doc` topic. Rename `fetch-doc` → `doc fetch`: - commands/doc/fetch.ts (class DocFetch), services/commands/doc/fetch.ts (docFetchService), and the colocated test. - Register as `doc:fetch`; add a `doc` topic description. - Update cross-references (`search` now points to `doc fetch`; `doc fetch` points to `doc search`). Co-Authored-By: Claude Opus 4.8 (1M context) --- ...oc-command.md => add-doc-fetch-command.md} | 2 +- ...oc.interface.ts => doc-fetch.interface.ts} | 4 +- .../generated/generated_docs_data_v2.json | 18 +-- packages/cli/README.md | 20 ++-- packages/cli/oclif.manifest.json | 108 +++++++++--------- packages/cli/package.json | 3 + .../commands/{fetch-doc.ts => doc/fetch.ts} | 16 +-- packages/cli/src/cli/commands/search.ts | 2 +- .../{fetch-doc.test.ts => doc/fetch.test.ts} | 16 +-- .../commands/{fetch-doc.ts => doc/fetch.ts} | 2 +- packages/cli/src/index.ts | 4 +- packages/e2e/data/snapshots/commands.txt | 3 +- 12 files changed, 101 insertions(+), 97 deletions(-) rename .changeset/{add-fetch-doc-command.md => add-doc-fetch-command.md} (81%) rename docs-shopify.dev/commands/interfaces/{fetch-doc.interface.ts => doc-fetch.interface.ts} (83%) rename packages/cli/src/cli/commands/{fetch-doc.ts => doc/fetch.ts} (71%) rename packages/cli/src/cli/services/commands/{fetch-doc.test.ts => doc/fetch.test.ts} (80%) rename packages/cli/src/cli/services/commands/{fetch-doc.ts => doc/fetch.ts} (96%) diff --git a/.changeset/add-fetch-doc-command.md b/.changeset/add-doc-fetch-command.md similarity index 81% rename from .changeset/add-fetch-doc-command.md rename to .changeset/add-doc-fetch-command.md index 6f6d0eb0f10..d36c83864a0 100644 --- a/.changeset/add-fetch-doc-command.md +++ b/.changeset/add-doc-fetch-command.md @@ -2,4 +2,4 @@ '@shopify/cli': minor --- -Add a `shopify fetch-doc` command that downloads a document from shopify.dev and prints it to stdout, or writes it to a file with `--output`. It requests the Markdown representation that every shopify.dev page has, giving agents an easy way to pull instructional content from the centralized docs verbatim. +Add a `shopify doc fetch` command that downloads a document from shopify.dev and prints it to stdout, or writes it to a file with `--output`. It requests the Markdown representation that every shopify.dev page has, giving agents an easy way to pull instructional content from the centralized docs verbatim. diff --git a/docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts b/docs-shopify.dev/commands/interfaces/doc-fetch.interface.ts similarity index 83% rename from docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts rename to docs-shopify.dev/commands/interfaces/doc-fetch.interface.ts index f4555fcbd94..221e84e9869 100644 --- a/docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts +++ b/docs-shopify.dev/commands/interfaces/doc-fetch.interface.ts @@ -1,9 +1,9 @@ // This is an autogenerated file. Don't edit this file manually. /** - * The following flags are available for the `fetch-doc` command: + * The following flags are available for the `doc fetch` command: * @publicDocs */ -export interface fetchdoc { +export interface docfetch { /** * Disable color output. * @environment SHOPIFY_FLAG_NO_COLOR diff --git a/docs-shopify.dev/generated/generated_docs_data_v2.json b/docs-shopify.dev/generated/generated_docs_data_v2.json index e2b1725e60b..5686e9f510f 100644 --- a/docs-shopify.dev/generated/generated_docs_data_v2.json +++ b/docs-shopify.dev/generated/generated_docs_data_v2.json @@ -2742,15 +2742,15 @@ "value": "export interface configautoupgradestatus {\n\n}" } }, - "fetchdoc": { - "docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts": { - "filePath": "docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts", - "name": "fetchdoc", - "description": "The following flags are available for the `fetch-doc` command:", + "docfetch": { + "docs-shopify.dev/commands/interfaces/doc-fetch.interface.ts": { + "filePath": "docs-shopify.dev/commands/interfaces/doc-fetch.interface.ts", + "name": "docfetch", + "description": "The following flags are available for the `doc fetch` command:", "isPublicDocs": true, "members": [ { - "filePath": "docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts", + "filePath": "docs-shopify.dev/commands/interfaces/doc-fetch.interface.ts", "syntaxKind": "PropertySignature", "name": "--no-color", "value": "''", @@ -2759,7 +2759,7 @@ "environmentValue": "SHOPIFY_FLAG_NO_COLOR" }, { - "filePath": "docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts", + "filePath": "docs-shopify.dev/commands/interfaces/doc-fetch.interface.ts", "syntaxKind": "PropertySignature", "name": "--verbose", "value": "''", @@ -2768,7 +2768,7 @@ "environmentValue": "SHOPIFY_FLAG_VERBOSE" }, { - "filePath": "docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts", + "filePath": "docs-shopify.dev/commands/interfaces/doc-fetch.interface.ts", "syntaxKind": "PropertySignature", "name": "-o, --output ", "value": "string", @@ -2777,7 +2777,7 @@ "environmentValue": "SHOPIFY_FLAG_OUTPUT" } ], - "value": "export interface fetchdoc {\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Write the document to this file path instead of printing it to stdout.\n * @environment SHOPIFY_FLAG_OUTPUT\n */\n '-o, --output '?: string\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}" + "value": "export interface docfetch {\n /**\n * Disable color output.\n * @environment SHOPIFY_FLAG_NO_COLOR\n */\n '--no-color'?: ''\n\n /**\n * Write the document to this file path instead of printing it to stdout.\n * @environment SHOPIFY_FLAG_OUTPUT\n */\n '-o, --output '?: string\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}" } }, "help": { diff --git a/packages/cli/README.md b/packages/cli/README.md index 84ddb173523..73d8d2d9800 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -39,7 +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 fetch-doc [URL]`](#shopify-fetch-doc-url) +* [`shopify doc fetch [URL]`](#shopify-doc-fetch-url) * [`shopify help [command] [flags]`](#shopify-help-command-flags) * [`shopify hydrogen build`](#shopify-hydrogen-build) * [`shopify hydrogen check RESOURCE`](#shopify-hydrogen-check-resource) @@ -1213,13 +1213,13 @@ DESCRIPTION Run `shopify config autoupgrade on` or `shopify config autoupgrade off` to configure it. ``` -## `shopify fetch-doc [URL]` +## `shopify doc fetch [URL]` -Download a complete document from shopify.dev. Every page on shopify.dev has a Markdown version, and that is what this tool returns. Use this to pull an entire document verbatim — for example, a set of instructions an agent follows like a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `search`. +Download a complete document from shopify.dev. Every page on shopify.dev has a Markdown version, and that is what this tool returns. Use this to pull an entire document verbatim — for example, a set of instructions an agent follows like a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `doc search`. ``` USAGE - $ shopify fetch-doc [URL] + $ shopify doc fetch [URL] ARGUMENTS URL The shopify.dev URL to fetch. @@ -1233,16 +1233,16 @@ FLAGS DESCRIPTION Download a complete document from shopify.dev. Every page on shopify.dev has a Markdown version, and that is what this tool returns. Use this to pull an entire document verbatim — for example, a set of instructions an agent follows like - a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `search`. + a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `doc search`. EXAMPLES # fetch the Markdown version of a Shopify.dev page - $ shopify fetch-doc https://shopify.dev/docs/api/shopify-cli + $ shopify doc fetch https://shopify.dev/docs/api/shopify-cli # save the document to a file instead of printing it - $ shopify fetch-doc https://shopify.dev/docs/api/shopify-cli --output docs/shopify-cli.md + $ shopify doc fetch https://shopify.dev/docs/api/shopify-cli --output docs/shopify-cli.md ``` ## `shopify help [command] [flags]` @@ -2116,7 +2116,7 @@ DESCRIPTION ## `shopify search [query]` -Search shopify.dev for the most relevant content matching a query. Best for 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`. +Search shopify.dev for the most relevant content matching a query. Best for 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 @@ -2128,8 +2128,8 @@ FLAGS DESCRIPTION Search shopify.dev for the most relevant content matching a query. Best for 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`. + of documentation for a topic, rather than retrieving a whole document. To download a full document verbatim, use `doc + fetch`. EXAMPLES # open the search modal on Shopify.dev diff --git a/packages/cli/oclif.manifest.json b/packages/cli/oclif.manifest.json index bb3efc59271..536d16c107a 100644 --- a/packages/cli/oclif.manifest.json +++ b/packages/cli/oclif.manifest.json @@ -3424,6 +3424,59 @@ "strict": true, "summary": "Watch and prints out changes to an app." }, + "doc:fetch": { + "aliases": [ + ], + "args": { + "url": { + "description": "The shopify.dev URL to fetch.", + "name": "url", + "required": true + } + }, + "description": "Download a complete document from shopify.dev. Every page on shopify.dev has a Markdown version, and that is what this tool returns. Use this to pull an entire document verbatim — for example, a set of instructions an agent follows like a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `doc search`.", + "enableJsonFlag": false, + "examples": [ + "# fetch the Markdown version of a Shopify.dev page\nshopify doc fetch https://shopify.dev/docs/api/shopify-cli", + "# save the document to a file instead of printing it\nshopify doc fetch https://shopify.dev/docs/api/shopify-cli --output docs/shopify-cli.md" + ], + "flags": { + "no-color": { + "allowNo": false, + "description": "Disable color output.", + "env": "SHOPIFY_FLAG_NO_COLOR", + "hidden": false, + "name": "no-color", + "type": "boolean" + }, + "output": { + "char": "o", + "description": "Write the document to this file path instead of printing it to stdout.", + "env": "SHOPIFY_FLAG_OUTPUT", + "hasDynamicHelp": false, + "multiple": false, + "name": "output", + "type": "option" + }, + "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:fetch", + "pluginAlias": "@shopify/cli", + "pluginName": "@shopify/cli", + "pluginType": "core", + "strict": true, + "usage": "doc fetch [URL]" + }, "docs:generate": { "aliases": [ ], @@ -3534,59 +3587,6 @@ "pluginType": "core", "strict": true }, - "fetch-doc": { - "aliases": [ - ], - "args": { - "url": { - "description": "The shopify.dev URL to fetch.", - "name": "url", - "required": true - } - }, - "description": "Download a complete document from shopify.dev. Every page on shopify.dev has a Markdown version, and that is what this tool returns. Use this to pull an entire document verbatim — for example, a set of instructions an agent follows like a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `search`.", - "enableJsonFlag": false, - "examples": [ - "# fetch the Markdown version of a Shopify.dev page\nshopify fetch-doc https://shopify.dev/docs/api/shopify-cli", - "# save the document to a file instead of printing it\nshopify fetch-doc https://shopify.dev/docs/api/shopify-cli --output docs/shopify-cli.md" - ], - "flags": { - "no-color": { - "allowNo": false, - "description": "Disable color output.", - "env": "SHOPIFY_FLAG_NO_COLOR", - "hidden": false, - "name": "no-color", - "type": "boolean" - }, - "output": { - "char": "o", - "description": "Write the document to this file path instead of printing it to stdout.", - "env": "SHOPIFY_FLAG_OUTPUT", - "hasDynamicHelp": false, - "multiple": false, - "name": "output", - "type": "option" - }, - "verbose": { - "allowNo": false, - "description": "Increase the verbosity of the output.", - "env": "SHOPIFY_FLAG_VERBOSE", - "hidden": false, - "name": "verbose", - "type": "boolean" - } - }, - "hasDynamicHelp": false, - "hiddenAliases": [ - ], - "id": "fetch-doc", - "pluginAlias": "@shopify/cli", - "pluginName": "@shopify/cli", - "pluginType": "core", - "strict": true, - "usage": "fetch-doc [URL]" - }, "help": { "aliases": [ ], @@ -5705,7 +5705,7 @@ "name": "query" } }, - "description": "Search shopify.dev for the most relevant content matching a query. Best for 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": "Search shopify.dev for the most relevant content matching a query. Best for 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": [ "# 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 " 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/fetch-doc.ts b/packages/cli/src/cli/commands/doc/fetch.ts similarity index 71% rename from packages/cli/src/cli/commands/fetch-doc.ts rename to packages/cli/src/cli/commands/doc/fetch.ts index 7b3d41bb7b1..022c77380fa 100644 --- a/packages/cli/src/cli/commands/fetch-doc.ts +++ b/packages/cli/src/cli/commands/doc/fetch.ts @@ -1,19 +1,19 @@ -import {fetchDocService} from '../services/commands/fetch-doc.js' +import {docFetchService} from '../../services/commands/doc/fetch.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 FetchDoc extends Command { +export default class DocFetch extends Command { static description = - 'Download a complete document from shopify.dev. Every page on shopify.dev has a Markdown version, and that is what this tool returns. Use this to pull an entire document verbatim — for example, a set of instructions an agent follows like a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `search`.' + 'Download a complete document from shopify.dev. Every page on shopify.dev has a Markdown version, and that is what this tool returns. Use this to pull an entire document verbatim — for example, a set of instructions an agent follows like a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `doc search`.' - static usage = `fetch-doc [URL]` + static usage = `doc fetch [URL]` static examples = [ `# fetch the Markdown version of a Shopify.dev page -shopify fetch-doc https://shopify.dev/docs/api/shopify-cli`, +shopify doc fetch https://shopify.dev/docs/api/shopify-cli`, `# save the document to a file instead of printing it -shopify fetch-doc https://shopify.dev/docs/api/shopify-cli --output docs/shopify-cli.md`, +shopify doc fetch https://shopify.dev/docs/api/shopify-cli --output docs/shopify-cli.md`, ] static args = { @@ -34,7 +34,7 @@ shopify fetch-doc https://shopify.dev/docs/api/shopify-cli --output docs/shopify } async run(): Promise { - const {args, flags} = await this.parse(FetchDoc) - await fetchDocService(args.url, flags.output) + const {args, flags} = await this.parse(DocFetch) + await docFetchService(args.url, flags.output) } } diff --git a/packages/cli/src/cli/commands/search.ts b/packages/cli/src/cli/commands/search.ts index f225aa0572f..07d96c3d75c 100644 --- a/packages/cli/src/cli/commands/search.ts +++ b/packages/cli/src/cli/commands/search.ts @@ -5,7 +5,7 @@ import {Args} from '@oclif/core' export default class Search extends Command { static description = - 'Search shopify.dev for the most relevant content matching a query. Best for 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`.' + 'Search shopify.dev for the most relevant content matching a query. Best for 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 = `search [query]` diff --git a/packages/cli/src/cli/services/commands/fetch-doc.test.ts b/packages/cli/src/cli/services/commands/doc/fetch.test.ts similarity index 80% rename from packages/cli/src/cli/services/commands/fetch-doc.test.ts rename to packages/cli/src/cli/services/commands/doc/fetch.test.ts index 421a73815d3..8c9a0b8e5b4 100644 --- a/packages/cli/src/cli/services/commands/fetch-doc.test.ts +++ b/packages/cli/src/cli/services/commands/doc/fetch.test.ts @@ -1,4 +1,4 @@ -import {fetchDocService} from './fetch-doc.js' +import {docFetchService} from './fetch.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' @@ -17,9 +17,9 @@ beforeEach(() => { vi.mocked(fetch).mockResolvedValue(okResponse('# Doc')) }) -describe('fetchDocService', () => { +describe('docFetchService', () => { test('requests Markdown and prints the body to stdout', async () => { - await fetchDocService('https://shopify.dev/docs/api/shopify-cli') + await docFetchService('https://shopify.dev/docs/api/shopify-cli') expect(fetch).toHaveBeenCalledWith('https://shopify.dev/docs/api/shopify-cli', { headers: {Accept: 'text/markdown'}, @@ -28,23 +28,23 @@ describe('fetchDocService', () => { }) test('accepts shopify.dev subdomains', async () => { - await fetchDocService('https://www.shopify.dev/docs') + await docFetchService('https://www.shopify.dev/docs') expect(fetch).toHaveBeenCalledOnce() }) test('rejects URLs from disallowed hosts without fetching', async () => { - await expect(fetchDocService('https://example.com/docs')).rejects.toThrowError(AbortError) + await expect(docFetchService('https://example.com/docs')).rejects.toThrowError(AbortError) expect(fetch).not.toHaveBeenCalled() }) test('rejects malformed URLs without fetching', async () => { - await expect(fetchDocService('not a url')).rejects.toThrowError(AbortError) + await expect(docFetchService('not a url')).rejects.toThrowError(AbortError) expect(fetch).not.toHaveBeenCalled() }) test('writes the document to the output path instead of stdout', async () => { - await fetchDocService('https://shopify.dev/docs/api/shopify-cli', 'docs/shopify-cli.md') + await docFetchService('https://shopify.dev/docs/api/shopify-cli', 'docs/shopify-cli.md') const expectedPath = resolvePath('docs/shopify-cli.md') expect(mkdir).toHaveBeenCalledWith(dirname(expectedPath)) @@ -55,7 +55,7 @@ describe('fetchDocService', () => { test('throws when the response is not ok', async () => { vi.mocked(fetch).mockResolvedValue({ok: false, status: 404, statusText: 'Not Found'} as any) - await expect(fetchDocService('https://shopify.dev/missing')).rejects.toThrowError(AbortError) + await expect(docFetchService('https://shopify.dev/missing')).rejects.toThrowError(AbortError) expect(outputResult).not.toHaveBeenCalled() }) }) diff --git a/packages/cli/src/cli/services/commands/fetch-doc.ts b/packages/cli/src/cli/services/commands/doc/fetch.ts similarity index 96% rename from packages/cli/src/cli/services/commands/fetch-doc.ts rename to packages/cli/src/cli/services/commands/doc/fetch.ts index 967a5f788eb..aa7bdde5f07 100644 --- a/packages/cli/src/cli/services/commands/fetch-doc.ts +++ b/packages/cli/src/cli/services/commands/doc/fetch.ts @@ -12,7 +12,7 @@ const MARKDOWN_CONTENT_TYPE = 'text/markdown' // hostname is one of these or a subdomain of one of these. const ALLOWED_HOSTS = ['shopify.dev'] -export async function fetchDocService(url: string, outputPath?: string) { +export async function docFetchService(url: string, outputPath?: string) { let parsedURL: URL try { parsedURL = new URL(url) diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index e006a551047..8dbb25be062 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -1,6 +1,6 @@ import VersionCommand from './cli/commands/version.js' import Search from './cli/commands/search.js' -import FetchDoc from './cli/commands/fetch-doc.js' +import DocFetch from './cli/commands/doc/fetch.js' import Upgrade from './cli/commands/upgrade.js' import Logout from './cli/commands/auth/logout.js' import Login from './cli/commands/auth/login.js' @@ -146,7 +146,7 @@ export const COMMANDS: any = { ...HydrogenCommands, ...StoreCommands, search: Search, - 'fetch-doc': FetchDoc, + 'doc:fetch': DocFetch, upgrade: Upgrade, version: VersionCommand, help: HelpCommand, diff --git a/packages/e2e/data/snapshots/commands.txt b/packages/e2e/data/snapshots/commands.txt index 3fcfd6c99c3..262cd4a91ae 100644 --- a/packages/e2e/data/snapshots/commands.txt +++ b/packages/e2e/data/snapshots/commands.txt @@ -49,7 +49,8 @@ │ ├─ off │ ├─ on │ └─ status -├─ fetch-doc +├─ doc +│ └─ fetch ├─ help ├─ hydrogen │ ├─ build