From 8ccda4a3914f2b48ead1536faf1a4a765699e025 Mon Sep 17 00:00:00 2001 From: Chris Risner Date: Tue, 18 Aug 2026 13:45:29 -0400 Subject: [PATCH 1/3] 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/3] 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) From 6d3c65483c209f99ac0107844971fc723169fdc9 Mon Sep 17 00:00:00 2001 From: Chris Risner Date: Tue, 18 Aug 2026 14:08:07 -0400 Subject: [PATCH 3/3] fix(tsconfig): put scripts/ in a project so editors stop flagging it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tsconfig.json included only ["src"], so nothing under scripts/ belonged to any project. An editor opening scripts/build-docs.ts fell back to inferred defaults — no types:["node"], different module resolution — and reported node:fs, process and __dirname as unresolved. Nothing was actually wrong with the file; it just was not part of a project. Split the two configs by their real jobs: - tsconfig.json is the editor and `typecheck` project: src + scripts. - tsconfig.build.json states include:["src"] itself rather than inheriting it, so what tsup emits stays narrow no matter how the base widens. Safe for the build either way — tsup lists its entry points explicitly and all of them are under src/. Typechecking scripts/ for the first time surfaced four real errors, all regex capture groups being string|undefined under noUncheckedIndexedAccess. One is a latent runtime bug rather than a type nit: in the TypeTable parser, `props.match(...)` on a group that failed to capture would have thrown. Both sites now handle the undefined case. test/ is deliberately still excluded. Including it surfaces 31 more of the same class, which is worth doing but is its own change — not something to bundle into a config fix. typecheck 0 errors, build succeeds, 144/144 tests pass, and the generator runs both ways: DOCS_REF=main and the local .env path. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/build-docs.ts | 8 ++++---- tsconfig.build.json | 11 ++++++++++- tsconfig.json | 20 ++++++++++++++++---- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/scripts/build-docs.ts b/scripts/build-docs.ts index b7859b8..25581cf 100644 --- a/scripts/build-docs.ts +++ b/scripts/build-docs.ts @@ -78,8 +78,8 @@ function findDocsPath(): string { const envFile = join(__dirname, '..', '.env') if (existsSync(envFile)) { const envContent = readFileSync(envFile, 'utf-8') - const match = envContent.match(/^DOCS_PATH=(.+)$/m) - if (match) return match[1].trim().replace(/^["']|["']$/g, '') + const docsPath = envContent.match(/^DOCS_PATH=(.+)$/m)?.[1] + if (docsPath) return docsPath.trim().replace(/^["']|["']$/g, '') } // 5. Try to find docs.auto.dev anywhere under ~/Workspace using find @@ -117,8 +117,8 @@ function convertMdxToMarkdown(content: string): string { const entryRegex = /['"]?([\w.]+)['"]?\s*:\s*\{([^}]+)\}/g let entry while ((entry = entryRegex.exec(inner)) !== null) { - const name = entry[1] - const props = entry[2] + const name = entry[1] ?? '' + const props = entry[2] ?? '' const desc = props.match(/description:\s*'([^']*)'/) const type = props.match(/type:\s*'([^']*)'/) const def = props.match(/default:\s*'([^']*)'/) diff --git a/tsconfig.build.json b/tsconfig.build.json index 35bcdb2..adccf8b 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -2,5 +2,14 @@ "extends": "./tsconfig.json", "compilerOptions": { "ignoreDeprecations": "6.0" - } + }, + "include": [ + "src" + ], + "exclude": [ + "node_modules", + "dist", + "test", + "scripts" + ] } diff --git a/tsconfig.json b/tsconfig.json index 3440e50..f5e9ef2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,8 +3,13 @@ "target": "ES2022", "module": "ESNext", "moduleResolution": "bundler", - "lib": ["ES2022", "DOM"], - "types": ["node"], + "lib": [ + "ES2022", + "DOM" + ], + "types": [ + "node" + ], "outDir": "dist", "declaration": true, "declarationMap": true, @@ -17,6 +22,13 @@ "isolatedModules": true, "noUncheckedIndexedAccess": true }, - "include": ["src"], - "exclude": ["node_modules", "dist", "test"] + "include": [ + "src", + "scripts" + ], + "exclude": [ + "node_modules", + "dist", + "test" + ] }