Skip to content

Commit 32e22ff

Browse files
nelsonwittwerclaude
andcommitted
Add --output flag and drop --content-type from fetch-doc
- `--output <path>` (`-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) <noreply@anthropic.com>
1 parent 9b7e62b commit 32e22ff

8 files changed

Lines changed: 85 additions & 63 deletions

File tree

.changeset/add-fetch-doc-command.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
'@shopify/cli': minor
33
---
44

5-
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.
5+
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.

docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,18 @@
44
* @publicDocs
55
*/
66
export interface fetchdoc {
7-
/**
8-
* The Accept content type to request (defaults to text/markdown).
9-
* @environment SHOPIFY_FLAG_CONTENT_TYPE
10-
*/
11-
'--content-type <value>'?: string
12-
137
/**
148
* Disable color output.
159
* @environment SHOPIFY_FLAG_NO_COLOR
1610
*/
1711
'--no-color'?: ''
1812

13+
/**
14+
* Write the document to this file path instead of printing it to stdout.
15+
* @environment SHOPIFY_FLAG_OUTPUT
16+
*/
17+
'-o, --output <value>'?: string
18+
1919
/**
2020
* Increase the verbosity of the output.
2121
* @environment SHOPIFY_FLAG_VERBOSE

docs-shopify.dev/generated/generated_docs_data_v2.json

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2749,15 +2749,6 @@
27492749
"description": "The following flags are available for the `fetch-doc` command:",
27502750
"isPublicDocs": true,
27512751
"members": [
2752-
{
2753-
"filePath": "docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts",
2754-
"syntaxKind": "PropertySignature",
2755-
"name": "--content-type <value>",
2756-
"value": "string",
2757-
"description": "The Accept content type to request (defaults to text/markdown).",
2758-
"isOptional": true,
2759-
"environmentValue": "SHOPIFY_FLAG_CONTENT_TYPE"
2760-
},
27612752
{
27622753
"filePath": "docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts",
27632754
"syntaxKind": "PropertySignature",
@@ -2775,9 +2766,18 @@
27752766
"description": "Increase the verbosity of the output.",
27762767
"isOptional": true,
27772768
"environmentValue": "SHOPIFY_FLAG_VERBOSE"
2769+
},
2770+
{
2771+
"filePath": "docs-shopify.dev/commands/interfaces/fetch-doc.interface.ts",
2772+
"syntaxKind": "PropertySignature",
2773+
"name": "-o, --output <value>",
2774+
"value": "string",
2775+
"description": "Write the document to this file path instead of printing it to stdout.",
2776+
"isOptional": true,
2777+
"environmentValue": "SHOPIFY_FLAG_OUTPUT"
27782778
}
27792779
],
2780-
"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 <value>'?: 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}"
2780+
"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 <value>'?: string\n\n /**\n * Increase the verbosity of the output.\n * @environment SHOPIFY_FLAG_VERBOSE\n */\n '--verbose'?: ''\n}"
27812781
}
27822782
},
27832783
"help": {

packages/cli/README.md

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,7 +1215,7 @@ DESCRIPTION
12151215

12161216
## `shopify fetch-doc [URL]`
12171217

1218-
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`.
1218+
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`.
12191219

12201220
```
12211221
USAGE
@@ -1225,22 +1225,24 @@ ARGUMENTS
12251225
URL The shopify.dev URL to fetch.
12261226
12271227
FLAGS
1228-
--content-type=<value> [env: SHOPIFY_FLAG_CONTENT_TYPE] The Accept content type to request (defaults to
1229-
text/markdown).
1230-
--no-color [env: SHOPIFY_FLAG_NO_COLOR] Disable color output.
1231-
--verbose [env: SHOPIFY_FLAG_VERBOSE] Increase the verbosity of the output.
1228+
-o, --output=<value> [env: SHOPIFY_FLAG_OUTPUT] Write the document to this file path instead of printing it to
1229+
stdout.
1230+
--no-color [env: SHOPIFY_FLAG_NO_COLOR] Disable color output.
1231+
--verbose [env: SHOPIFY_FLAG_VERBOSE] Increase the verbosity of the output.
12321232
12331233
DESCRIPTION
12341234
Download a complete document from shopify.dev. Every page on shopify.dev has a Markdown version, and that is what this
1235-
tool returns by default. Use this to pull an entire document verbatim — for example, a set of instructions an agent
1236-
follows like a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use
1237-
`search`.
1235+
tool returns. Use this to pull an entire document verbatim — for example, a set of instructions an agent follows like
1236+
a centrally-served skill. For finding the relevant pieces of content across shopify.dev instead, use `search`.
12381237
12391238
EXAMPLES
12401239
# fetch the Markdown version of a Shopify.dev page
1241-
shopify fetch-doc https://shopify.dev/docs/api/shopify-cli
1242-
# fetch the HTML version of a Shopify.dev page
1243-
shopify fetch-doc https://shopify.dev/docs/api/shopify-cli --content-type text/html
1240+
1241+
$ shopify fetch-doc https://shopify.dev/docs/api/shopify-cli
1242+
1243+
# save the document to a file instead of printing it
1244+
1245+
$ shopify fetch-doc https://shopify.dev/docs/api/shopify-cli --output docs/shopify-cli.md
12441246
```
12451247

12461248
## `shopify help [command] [flags]`

packages/cli/oclif.manifest.json

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3544,20 +3544,13 @@
35443544
"required": true
35453545
}
35463546
},
3547-
"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`.",
3547+
"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`.",
35483548
"enableJsonFlag": false,
35493549
"examples": [
3550-
"# 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 "
3550+
"# fetch the Markdown version of a Shopify.dev page\nshopify fetch-doc https://shopify.dev/docs/api/shopify-cli",
3551+
"# 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"
35513552
],
35523553
"flags": {
3553-
"content-type": {
3554-
"description": "The Accept content type to request (defaults to text/markdown).",
3555-
"env": "SHOPIFY_FLAG_CONTENT_TYPE",
3556-
"hasDynamicHelp": false,
3557-
"multiple": false,
3558-
"name": "content-type",
3559-
"type": "option"
3560-
},
35613554
"no-color": {
35623555
"allowNo": false,
35633556
"description": "Disable color output.",
@@ -3566,6 +3559,15 @@
35663559
"name": "no-color",
35673560
"type": "boolean"
35683561
},
3562+
"output": {
3563+
"char": "o",
3564+
"description": "Write the document to this file path instead of printing it to stdout.",
3565+
"env": "SHOPIFY_FLAG_OUTPUT",
3566+
"hasDynamicHelp": false,
3567+
"multiple": false,
3568+
"name": "output",
3569+
"type": "option"
3570+
},
35693571
"verbose": {
35703572
"allowNo": false,
35713573
"description": "Increase the verbosity of the output.",

packages/cli/src/cli/commands/fetch-doc.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,15 @@ import {Args, Flags} from '@oclif/core'
55

66
export default class FetchDoc extends Command {
77
static description =
8-
'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`.'
8+
'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`.'
99

1010
static usage = `fetch-doc [URL]`
1111

1212
static examples = [
1313
`# fetch the Markdown version of a Shopify.dev page
14-
shopify fetch-doc https://shopify.dev/docs/api/shopify-cli
15-
16-
# fetch the HTML version of a Shopify.dev page
17-
shopify fetch-doc https://shopify.dev/docs/api/shopify-cli --content-type text/html
18-
`,
14+
shopify fetch-doc https://shopify.dev/docs/api/shopify-cli`,
15+
`# save the document to a file instead of printing it
16+
shopify fetch-doc https://shopify.dev/docs/api/shopify-cli --output docs/shopify-cli.md`,
1917
]
2018

2119
static args = {
@@ -28,14 +26,15 @@ export default class FetchDoc extends Command {
2826

2927
static flags = {
3028
...globalFlags,
31-
'content-type': Flags.string({
32-
description: 'The Accept content type to request (defaults to text/markdown).',
33-
env: 'SHOPIFY_FLAG_CONTENT_TYPE',
29+
output: Flags.string({
30+
char: 'o',
31+
description: 'Write the document to this file path instead of printing it to stdout.',
32+
env: 'SHOPIFY_FLAG_OUTPUT',
3433
}),
3534
}
3635

3736
async run(): Promise<void> {
3837
const {args, flags} = await this.parse(FetchDoc)
39-
await fetchDocService(args.url, flags['content-type'])
38+
await fetchDocService(args.url, flags.output)
4039
}
4140
}

packages/cli/src/cli/services/commands/fetch-doc.test.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ import {describe, expect, test, vi, beforeEach} from 'vitest'
33
import {fetch} from '@shopify/cli-kit/node/http'
44
import {outputResult} from '@shopify/cli-kit/node/output'
55
import {AbortError} from '@shopify/cli-kit/node/error'
6+
import {mkdir, writeFile} from '@shopify/cli-kit/node/fs'
7+
import {dirname, resolvePath} from '@shopify/cli-kit/node/path'
68

79
vi.mock('@shopify/cli-kit/node/http')
810
vi.mock('@shopify/cli-kit/node/output')
11+
vi.mock('@shopify/cli-kit/node/fs')
912

1013
const okResponse = (body: string) =>
1114
({ok: true, status: 200, statusText: 'OK', text: () => Promise.resolve(body)}) as any
@@ -15,7 +18,7 @@ beforeEach(() => {
1518
})
1619

1720
describe('fetchDocService', () => {
18-
test('requests Markdown by default and prints the body to stdout', async () => {
21+
test('requests Markdown and prints the body to stdout', async () => {
1922
await fetchDocService('https://shopify.dev/docs/api/shopify-cli')
2023

2124
expect(fetch).toHaveBeenCalledWith('https://shopify.dev/docs/api/shopify-cli', {
@@ -24,14 +27,6 @@ describe('fetchDocService', () => {
2427
expect(outputResult).toHaveBeenCalledWith('# Doc')
2528
})
2629

27-
test('passes a custom content type through as the Accept header', async () => {
28-
await fetchDocService('https://shopify.dev/docs/api/shopify-cli', 'text/html')
29-
30-
expect(fetch).toHaveBeenCalledWith('https://shopify.dev/docs/api/shopify-cli', {
31-
headers: {Accept: 'text/html'},
32-
})
33-
})
34-
3530
test('accepts shopify.dev subdomains', async () => {
3631
await fetchDocService('https://www.shopify.dev/docs')
3732

@@ -48,6 +43,15 @@ describe('fetchDocService', () => {
4843
expect(fetch).not.toHaveBeenCalled()
4944
})
5045

46+
test('writes the document to the output path instead of stdout', async () => {
47+
await fetchDocService('https://shopify.dev/docs/api/shopify-cli', 'docs/shopify-cli.md')
48+
49+
const expectedPath = resolvePath('docs/shopify-cli.md')
50+
expect(mkdir).toHaveBeenCalledWith(dirname(expectedPath))
51+
expect(writeFile).toHaveBeenCalledWith(expectedPath, '# Doc')
52+
expect(outputResult).not.toHaveBeenCalled()
53+
})
54+
5155
test('throws when the response is not ok', async () => {
5256
vi.mocked(fetch).mockResolvedValue({ok: false, status: 404, statusText: 'Not Found'} as any)
5357

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
import {fetch} from '@shopify/cli-kit/node/http'
2-
import {outputResult} from '@shopify/cli-kit/node/output'
2+
import {outputInfo, outputResult} from '@shopify/cli-kit/node/output'
33
import {AbortError} from '@shopify/cli-kit/node/error'
4+
import {mkdir, writeFile} from '@shopify/cli-kit/node/fs'
5+
import {dirname, resolvePath} from '@shopify/cli-kit/node/path'
46

5-
const DEFAULT_CONTENT_TYPE = 'text/markdown'
7+
// Every page on shopify.dev has a Markdown representation, which is the clean,
8+
// parseable content agents want — so we always request it.
9+
const MARKDOWN_CONTENT_TYPE = 'text/markdown'
610

711
// Hosts whose documents are allowed to be fetched. A URL matches when its
812
// hostname is one of these or a subdomain of one of these.
913
const ALLOWED_HOSTS = ['shopify.dev']
1014

11-
export async function fetchDocService(url: string, contentType?: string) {
15+
export async function fetchDocService(url: string, outputPath?: string) {
1216
let parsedURL: URL
1317
try {
1418
parsedURL = new URL(url)
@@ -22,12 +26,23 @@ export async function fetchDocService(url: string, contentType?: string) {
2226
throw new AbortError(`Only documents from the following hosts can be fetched: ${ALLOWED_HOSTS.join(', ')}.`)
2327
}
2428

25-
const accept = contentType ?? DEFAULT_CONTENT_TYPE
26-
const response = await fetch(url, {headers: {Accept: accept}})
29+
const response = await fetch(url, {headers: {Accept: MARKDOWN_CONTENT_TYPE}})
2730

2831
if (!response.ok) {
2932
throw new AbortError(`Failed to fetch ${url}: ${response.status} ${response.statusText}`)
3033
}
3134

32-
outputResult(await response.text())
35+
const body = await response.text()
36+
37+
// When an output path is provided, write the document to disk (creating any
38+
// missing parent directories) instead of printing it to stdout.
39+
if (outputPath) {
40+
const absolutePath = resolvePath(outputPath)
41+
await mkdir(dirname(absolutePath))
42+
await writeFile(absolutePath, body)
43+
outputInfo(`Saved ${url} to ${absolutePath}`)
44+
return
45+
}
46+
47+
outputResult(body)
3348
}

0 commit comments

Comments
 (0)