diff --git a/.changeset/six-toes-yawn.md b/.changeset/six-toes-yawn.md new file mode 100644 index 000000000000..3e1aaf10c96c --- /dev/null +++ b/.changeset/six-toes-yawn.md @@ -0,0 +1,15 @@ +--- +"wrangler": minor +--- + +feat: teach `wrangler docs` to use algolia search index + +This PR lets you search Cloudflare's entire docs via `wrangler docs [search term here]`. + +By default, if the search fails to find what you're looking for, you'll get an error like this: + +``` +✘ [ERROR] Could not find docs for: . Please try again with another search term. +``` + +If you provide the `--yes` or `-y` flag, wrangler will open the docs to https://developers.cloudflare.com/workers/wrangler/commands/, even if the search fails. diff --git a/.github/workflows/create-pullrequest-prerelease.yml b/.github/workflows/create-pullrequest-prerelease.yml index 9cd4fe26345d..7e7175bb7fe8 100644 --- a/.github/workflows/create-pullrequest-prerelease.yml +++ b/.github/workflows/create-pullrequest-prerelease.yml @@ -37,6 +37,8 @@ jobs: run: npm run build env: NODE_ENV: "production" + ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} + ALGOLIA_PUBLIC_KEY: ${{ secrets.ALGOLIA_PUBLIC_KEY }} - name: Pack wrangler run: npm pack diff --git a/.github/workflows/prereleases.yml b/.github/workflows/prereleases.yml index fd5e0f59ef2d..f0161fee7406 100644 --- a/.github/workflows/prereleases.yml +++ b/.github/workflows/prereleases.yml @@ -87,6 +87,8 @@ jobs: run: npm run build env: NODE_ENV: "production" + ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} + ALGOLIA_PUBLIC_KEY: ${{ secrets.ALGOLIA_PUBLIC_KEY }} - name: Build & Publish Prerelease Registry run: npm run publish diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5d373880a6d1..077fd66c2d27 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,6 +49,8 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} + ALGOLIA_PUBLIC_KEY: ${{ secrets.ALGOLIA_PUBLIC_KEY }} NODE_ENV: "production" # This is the "production" key for sparrow analytics. # Include this here because this step will rebuild Wrangler and needs to have this available diff --git a/packages/wrangler/scripts/bundle.ts b/packages/wrangler/scripts/bundle.ts index 2473ff26c92c..6b492ed3e5c8 100644 --- a/packages/wrangler/scripts/bundle.ts +++ b/packages/wrangler/scripts/bundle.ts @@ -56,6 +56,12 @@ async function buildMain(flags: BuildFlags = {}) { ...(process.env.SPARROW_SOURCE_KEY ? { SPARROW_SOURCE_KEY: `"${process.env.SPARROW_SOURCE_KEY}"` } : {}), + ...(process.env.ALGOLIA_APP_ID + ? { ALGOLIA_APP_ID: `"${process.env.ALGOLIA_APP_ID}"` } + : {}), + ...(process.env.ALGOLIA_PUBLIC_KEY + ? { ALGOLIA_PUBLIC_KEY: `"${process.env.ALGOLIA_PUBLIC_KEY}"` } + : {}), }, watch: flags.watch ? watchLogger("./wrangler-dist") : false, }); diff --git a/packages/wrangler/src/__tests__/index.test.ts b/packages/wrangler/src/__tests__/index.test.ts index e0b70ff99634..0f3807385293 100644 --- a/packages/wrangler/src/__tests__/index.test.ts +++ b/packages/wrangler/src/__tests__/index.test.ts @@ -32,7 +32,7 @@ describe("wrangler", () => { "wrangler Commands: - wrangler docs [command] 📚 Open wrangler's docs in your browser + wrangler docs [command..] 📚 Open wrangler's docs in your browser wrangler init [name] 📥 Initialize a basic Worker project, including a wrangler.toml file wrangler generate [name] [template] ✨ Generate a new Worker project from an existing Worker template. See https://github.com/cloudflare/templates wrangler dev [script] 👂 Start a local server for developing your worker @@ -85,7 +85,7 @@ describe("wrangler", () => { wrangler Commands: - wrangler docs [command] 📚 Open wrangler's docs in your browser + wrangler docs [command..] 📚 Open wrangler's docs in your browser wrangler init [name] 📥 Initialize a basic Worker project, including a wrangler.toml file wrangler generate [name] [template] ✨ Generate a new Worker project from an existing Worker template. See https://github.com/cloudflare/templates wrangler dev [script] 👂 Start a local server for developing your worker diff --git a/packages/wrangler/src/__tests__/mtls-certificates.test.ts b/packages/wrangler/src/__tests__/mtls-certificates.test.ts index 0f7109745383..bd61509d5e7c 100644 --- a/packages/wrangler/src/__tests__/mtls-certificates.test.ts +++ b/packages/wrangler/src/__tests__/mtls-certificates.test.ts @@ -362,7 +362,7 @@ describe("wrangler", () => { "wrangler Commands: - wrangler docs [command] 📚 Open wrangler's docs in your browser + wrangler docs [command..] 📚 Open wrangler's docs in your browser wrangler init [name] 📥 Initialize a basic Worker project, including a wrangler.toml file wrangler generate [name] [template] ✨ Generate a new Worker project from an existing Worker template. See https://github.com/cloudflare/templates wrangler dev [script] 👂 Start a local server for developing your worker diff --git a/packages/wrangler/src/docs/helpers.ts b/packages/wrangler/src/docs/helpers.ts new file mode 100644 index 000000000000..13fe07a0c85b --- /dev/null +++ b/packages/wrangler/src/docs/helpers.ts @@ -0,0 +1,50 @@ +import assert from "node:assert"; +import { fetch } from "undici"; +import { logger } from "../logger"; + +// The ALGOLIA_APP_ID and ALGOLIA_PUBLIC_KEY are provided at esbuild time as a `define` for production and beta releases. +// Otherwise it is left undefined, which disables search. +declare const ALGOLIA_APP_ID: string; +declare const ALGOLIA_PUBLIC_KEY: string; + +export async function runSearch(searchTerm: string) { + const id = ALGOLIA_APP_ID; + const index = "developers-cloudflare2"; + const key = ALGOLIA_PUBLIC_KEY; + const params = new URLSearchParams({ + query: searchTerm, + hitsPerPage: "1", + getRankingInfo: "0", + }); + + assert(id, "Missing Algolia App ID"); + assert(key, "Missing Algolia Key"); + + const searchResp = await fetch( + `https://${id}-dsn.algolia.net/1/indexes/${index}/query`, + { + method: "POST", + body: JSON.stringify({ + params: params.toString(), + }), + headers: { + "X-Algolia-API-Key": key, + "X-Algolia-Application-Id": id, + }, + } + ); + if (!searchResp.ok) { + logger.error(`Could not search the docs. Please try again later.`); + return; + } + const searchData = (await searchResp.json()) as { hits: { url: string }[] }; + logger.debug("searchData: ", searchData); + if (searchData.hits[0]) { + return searchData.hits[0].url; + } else { + logger.error( + `Could not find docs for: ${searchTerm}. Please try again with another search term.` + ); + return; + } +} diff --git a/packages/wrangler/src/docs/index.ts b/packages/wrangler/src/docs/index.ts index 5d424f4851b6..b853de6ba139 100644 --- a/packages/wrangler/src/docs/index.ts +++ b/packages/wrangler/src/docs/index.ts @@ -3,89 +3,46 @@ import { readConfig } from "../config"; import { logger } from "../logger"; import * as metrics from "../metrics"; import openInBrowser from "../open-in-browser"; - +import { runSearch } from "./helpers"; import type { CommonYargsArgv, StrictYargsOptionsToInterface, } from "../yargs-types"; -const argToUrlHash = { - d1: "d1", - docs: "docs", - init: "init", - generate: "generate", - dev: "dev", - publish: "publish", - delete: "delete", - "kv:namespace": "kvnamespace", - "kv:key": "kvkey", - "kv:bulk": "kvbulk", - "r2 bucket": "r2-bucket", - "r2 object": "r2-object", - secret: "secret", - "secret:bulk": "secretbulk", - tail: "tail", - pages: "pages", - login: "login", - logout: "logout", - whoami: "whoami", - types: "types", - deployments: "deployments", -}; - export function docsOptions(yargs: CommonYargsArgv) { - return yargs.positional("command", { - describe: "Enter the wrangler command you want to know more about", - type: "string", - // requiresArg: true, - choices: [ - "docs", - "init", - "generate", - "dev", - "publish", - "delete", - "tail", - "secret", - "secret:bulk", - "kv:namespace", - "kv:key", - "kv:bulk", - "pages", - // "queues", //TODO: Undocumented - "r2 object", - "r2 bucket", - // "dispatch-namespace", // TODO: Undocumented - Workers for Platforms - "d1", - // "pubsub", //TODO: Undocumented - "login", - "logout", - "whoami", - "types", - "deployments", - "api", - ], - }); -} - -function isValidParam(k: string): k is keyof typeof argToUrlHash { - return k in argToUrlHash; + return yargs + .positional("command", { + describe: "Enter the wrangler command you want to know more about", + type: "string", + array: true, + }) + .option("yes", { + alias: "y", + type: "boolean", + description: "Takes you to the docs, even if search fails", + }); } export async function docsHandler( args: StrictYargsOptionsToInterface ) { + //if no command is provided, open the docs homepage + //or, if a command IS provided, but we can't find anything, open the docs homepage let urlToOpen = - "https://developers.cloudflare.com/workers/wrangler/commands/"; + args.yes || !args.command || args.command.length === 0 + ? "https://developers.cloudflare.com/workers/wrangler/commands/" + : ""; + + if (args.command && args.command.length > 0) { + const searchTerm = args.command.join(" "); + const searchResult = await runSearch(searchTerm); - if (args.command === "api") { - //if api, take them to the API docs - urlToOpen = "https://developers.cloudflare.com/workers/wrangler/api/"; - } else if (args.command && isValidParam(args.command)) { - //otherwise, they get the wrangler commands page - urlToOpen += `#${argToUrlHash[args.command]}`; + urlToOpen = searchResult ?? urlToOpen; } + if (!urlToOpen) { + return; + } await printWranglerBanner(); logger.log(`Opening a link in your default browser: ${urlToOpen}`); diff --git a/packages/wrangler/src/index.ts b/packages/wrangler/src/index.ts index cdb46eaa475e..32e34b6d8bba 100644 --- a/packages/wrangler/src/index.ts +++ b/packages/wrangler/src/index.ts @@ -271,7 +271,7 @@ export function createCLIParser(argv: string[]) { // docs wrangler.command( - "docs [command]", + "docs [command..]", "📚 Open wrangler's docs in your browser", docsOptions, docsHandler