From 8ccda4a3914f2b48ead1536faf1a4a765699e025 Mon Sep 17 00:00:00 2001 From: Chris Risner Date: Tue, 18 Aug 2026 13:45:29 -0400 Subject: [PATCH 1/2] feat(scripts): let build-docs fetch a docs ref from GitHub MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `findDocsPath()` resolved five ways, all filesystem — so regenerating src/docs/data.ts meant having a docs.auto.dev checkout and pointing .env at a path on one person's machine. Nobody else could reproduce the output, and the committed result could drift from the docs it claims to mirror with nothing to catch it. Adds DOCS_REF as one more resolution strategy in the same function: DOCS_REF=main pnpm build:docs && pnpm build:docs-data DOCS_REF=feat/no-trials-copy pnpm build:docs It shallow-clones at that ref and returns the path, so everything downstream is untouched — the rest of the script only ever needed a directory. DOCS_REPO overrides the repo if it ever moves. Precedence: a CLI arg still wins, then DOCS_REF, then DOCS_PATH. A ref is a more specific request than a path, so it takes priority over one; an explicit argument beats both. Uses ambient git credentials rather than plumbing a token: a developer's existing auth locally, a token-backed remote in CI. docs.auto.dev is private and this SDK is public, so a workflow using this needs a cross-org read token. That is stated in the code rather than worked around, because there is no way around it. Verified against drivly/docs.auto.dev@feat/no-trials-copy: fetched, converted all 12 product docs, and the regenerated data.ts dropped from 12 "Starter" availability lines to 0 with 12 "**Free**:" in their place — matching drivly/docs.auto.dev#27. data.ts itself is deliberately NOT in this commit. Generating committed content from an unmerged branch pins it to a state that can still change; regenerate from main once #27 lands. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/build-docs.ts | 54 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/scripts/build-docs.ts b/scripts/build-docs.ts index af4c982..b90fa0e 100644 --- a/scripts/build-docs.ts +++ b/scripts/build-docs.ts @@ -3,25 +3,65 @@ * Strips JSX components (TypeTable, Accordion, ClickableCodeBlock) into markdown equivalents. * * Usage: npx tsx scripts/build-docs.ts [docs-path] - * Default docs path: DOCS_PATH env var or ../docs.auto.dev (sibling repo) + * + * Source resolution, in order: CLI arg, DOCS_REF, DOCS_PATH, .env, ~/Workspace, sibling repo. + * + * DOCS_REF fetches from GitHub instead of the filesystem: + * DOCS_REF=main npx tsx scripts/build-docs.ts + * DOCS_REF=feat/no-trials-copy npx tsx scripts/build-docs.ts + * + * Without it the script needs a local docs.auto.dev checkout, which is why regenerating + * src/docs/data.ts used to mean pointing .env at a path on one person's machine — and why the + * committed output could drift from the docs it claims to mirror with nothing to catch it. */ -import { readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync } from 'node:fs' +import { readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync, mkdtempSync } from 'node:fs' import { join, basename, dirname } from 'node:path' import { fileURLToPath } from 'node:url' import { execSync } from 'node:child_process' -import { homedir } from 'node:os' +import { homedir, tmpdir } from 'node:os' const __dirname = dirname(fileURLToPath(import.meta.url)) +const DOCS_REPO = process.env.DOCS_REPO ?? 'drivly/docs.auto.dev' + +/** + * Shallow-clone the docs repo at a ref and return the checkout path. + * + * Uses the ambient git credentials — a developer's existing auth locally, or a token-backed + * remote in CI. docs.auto.dev is private and this SDK is public, so a workflow using this needs + * a cross-org read token; there is no way around that and no attempt to work around it here. + */ +function cloneDocsAtRef(ref: string): string { + const dest = join(mkdtempSync(join(tmpdir(), 'autodev-docs-')), 'docs.auto.dev') + const url = `https://github.com/${DOCS_REPO}.git` + console.log(`Fetching ${DOCS_REPO}@${ref}`) + try { + execSync(`git clone --depth=1 --branch "${ref}" --quiet "${url}" "${dest}"`, { + stdio: ['ignore', 'ignore', 'pipe'], + timeout: 120_000, + }) + } catch (err) { + const detail = err instanceof Error && 'stderr' in err ? String(err.stderr).trim() : String(err) + throw new Error( + `Could not fetch ${DOCS_REPO}@${ref}. Check the ref exists and that you have read access ` + + `(the repo is private). Underlying error: ${detail}`, + ) + } + return dest +} + function findDocsPath(): string { - // 1. CLI arg + // 1. CLI arg — most explicit, wins over everything. if (process.argv[2]) return process.argv[2] - // 2. Env var + // 2. A ref is a more specific request than a path, so it takes precedence over DOCS_PATH. + if (process.env.DOCS_REF) return cloneDocsAtRef(process.env.DOCS_REF) + + // 3. Env var if (process.env.DOCS_PATH) return process.env.DOCS_PATH - // 3. Load from .env file if present + // 4. Load from .env file if present const envFile = join(__dirname, '..', '.env') if (existsSync(envFile)) { const envContent = readFileSync(envFile, 'utf-8') @@ -29,7 +69,7 @@ function findDocsPath(): string { if (match) return match[1].trim().replace(/^["']|["']$/g, '') } - // 4. Try to find docs.auto.dev anywhere under ~/Workspace using find + // 5. Try to find docs.auto.dev anywhere under ~/Workspace using find try { const workspace = join(homedir(), 'Workspace') const result = execSync( From b5f264db5ab5f569a7a0cb7bfd403f2b05707809 Mon Sep 17 00:00:00 2001 From: Chris Risner Date: Tue, 18 Aug 2026 14:23:33 -0400 Subject: [PATCH 2/2] fix(scripts): name the mistake when an env var is passed as an argument MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `pnpm build:docs DOCS_REF=main` is the natural thing to type, and it failed with: Product docs not found at: DOCS_REF=main/content/docs/v2/products which reports the symptom rather than the mistake. An env var placed after the command arrives as a positional, and the CLI-arg branch is checked first, so it was taken as a path. Now it says what to do instead: "DOCS_REF=main" looks like an environment variable, not a path. Put it before the command: DOCS_REF=main pnpm build:docs Matches on SCREAMING_CASE followed by `=`, so real paths are unaffected — verified all three forms still behave: bare path, DOCS_REF env var, and the mistyped version. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/build-docs.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/scripts/build-docs.ts b/scripts/build-docs.ts index b90fa0e..b7859b8 100644 --- a/scripts/build-docs.ts +++ b/scripts/build-docs.ts @@ -53,7 +53,20 @@ function cloneDocsAtRef(ref: string): string { function findDocsPath(): string { // 1. CLI arg — most explicit, wins over everything. - if (process.argv[2]) return process.argv[2] + const arg = process.argv[2] + if (arg) { + // Catch `pnpm build:docs DOCS_REF=main`. An env var placed after the command arrives here + // as a positional, and the arg check above wins, so it would otherwise be treated as a + // path — failing with "Product docs not found at: DOCS_REF=main/content/..." which names + // the symptom and not the mistake. + if (/^[A-Z][A-Z0-9_]*=/.test(arg)) { + throw new Error( + `"${arg}" looks like an environment variable, not a path. Put it before the command:\n` + + ` ${arg} pnpm build:docs`, + ) + } + return arg + } // 2. A ref is a more specific request than a path, so it takes precedence over DOCS_PATH. if (process.env.DOCS_REF) return cloneDocsAtRef(process.env.DOCS_REF)