From e46b27c129667d2596123cfb5184666db048afbf Mon Sep 17 00:00:00 2001 From: Eason WaveKat Date: Sat, 23 May 2026 18:00:32 +1200 Subject: [PATCH 1/3] feat(sync-docs): per-source ref override via env var MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add SYNC_DOCS_REF_ env var (e.g. SYNC_DOCS_REF_VOICE) to pin a source to a specific branch/tag/sha instead of the default "latest tag, fallback to main" logic. When set, the override is the only ref tried — no fallback — so typos fail loudly instead of silently shipping main. preview.yml exposes this via workflow_dispatch inputs voice_ref and cli_ref so previewing in-flight docs branches (e.g. wavekat-voice's feat/onboarding-v2) is a one-click action from the Actions UI. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/preview.yml | 11 +++++++++++ scripts/sync-docs.js | 20 ++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 4d880e1..3f4cc59 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -4,6 +4,15 @@ on: pull_request: branches: [main] workflow_dispatch: + inputs: + voice_ref: + description: "wavekat-voice ref (branch/tag/sha) to pull docs from. Empty = default (latest tag)." + required: false + default: "" + cli_ref: + description: "wavekat-cli ref. Empty = default (latest tag)." + required: false + default: "" permissions: pull-requests: write @@ -27,6 +36,8 @@ jobs: - run: npm run cf:build env: SYNC_DOCS_TOKEN: ${{ secrets.SYNC_DOCS_TOKEN }} + SYNC_DOCS_REF_VOICE: ${{ inputs.voice_ref }} + SYNC_DOCS_REF_CLI: ${{ inputs.cli_ref }} - name: Deploy preview id: deploy diff --git a/scripts/sync-docs.js b/scripts/sync-docs.js index 425e2ea..8a47ea0 100644 --- a/scripts/sync-docs.js +++ b/scripts/sync-docs.js @@ -18,6 +18,11 @@ // — copy from sibling working trees at / // instead of cloning (no GitHub token required). Falls // through to clone if a local copy is missing. +// SYNC_DOCS_REF_= +// — pin a specific source to a branch/tag/sha instead of +// the default "latest tag, fallback to main" logic. E.g. +// SYNC_DOCS_REF_VOICE=feat/onboarding-v2 to preview an +// in-flight branch of wavekat-voice's docs. // // Auth note: cloning private repos requires SYNC_DOCS_TOKEN to be set to a // fine-grained PAT with read access to the relevant repos. In CI it's @@ -167,8 +172,19 @@ function tryRemote(source) { const { slug, repo } = source; const docsPath = source.docsPath ?? "docs/site"; const url = repoUrl(source); - const tag = latestTagRemote(url); - const refs = [tag, "main"].filter(Boolean); + + // Per-source ref override via env var, e.g. SYNC_DOCS_REF_VOICE=feat/onboarding-v2. + // When set, the override is the only ref tried — no tag/main fallback — + // so a typo surfaces as a build error instead of silently shipping main. + const overrideEnv = `SYNC_DOCS_REF_${slug.toUpperCase()}`; + const override = process.env[overrideEnv]; + const refs = override + ? [override] + : [latestTagRemote(url), "main"].filter(Boolean); + + if (override) { + console.log(` (override ${overrideEnv}=${override})`); + } for (const ref of refs) { const cloneDir = join(tmpDir, `${slug}__${ref}`); From caa666de3de7ad7abde43efb3d39a3188510f4c5 Mon Sep 17 00:00:00 2001 From: Eason WaveKat Date: Sat, 23 May 2026 18:08:06 +1200 Subject: [PATCH 2/3] feat(sync-docs): override in local mode + surface git errors - tryLocal now honors SYNC_DOCS_REF_, extracting docs from that ref via git archive against a local clone. Lets you preview an in-flight branch with WAVEKAT_LOCAL_REPOS without needing a token or going through CI. - tryRemote now logs git's actual stderr (last 2 lines) and distinguishes "clone failed" from "clone ok but docsPath missing". No more silent "no docs found at any ref". - Flatten slashes in ref names when building tmp dirs so refs like feat/onboarding-v2 don't create nested directories. Co-Authored-By: Claude Opus 4.7 (1M context) --- scripts/sync-docs.js | 44 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/scripts/sync-docs.js b/scripts/sync-docs.js index 8a47ea0..85253f4 100644 --- a/scripts/sync-docs.js +++ b/scripts/sync-docs.js @@ -132,6 +132,35 @@ function tryLocal(source) { const destDir = join(contentDocsDir, source.slug); const docsPath = source.docsPath ?? "docs/site"; + + // Honor SYNC_DOCS_REF_ override in local mode too — extracts via + // git archive from that ref (branch/tag/sha). No fallback to main on + // failure, matching the remote-mode behavior. + const overrideEnv = `SYNC_DOCS_REF_${source.slug.toUpperCase()}`; + const override = process.env[overrideEnv]; + if (override) { + console.log(` (override ${overrideEnv}=${override})`); + try { + const tmp = join(tmpDir, `${source.slug}__override`); + rmSync(tmp, { recursive: true, force: true }); + mkdirSync(tmp, { recursive: true }); + execSync(`git archive --format=tar ${override} ${docsPath} | tar -x -C ${tmp}`, { + cwd: repoPath, + stdio: ["ignore", "ignore", "pipe"], + }); + if (existsSync(join(tmp, docsPath))) { + copyDocs(join(tmp, docsPath), destDir); + rmSync(tmp, { recursive: true, force: true }); + console.log(` ✓ ${source.slug} @ ${override} (local, ${docsPath})`); + return { version: override }; + } + rmSync(tmp, { recursive: true, force: true }); + } catch (err) { + console.log(` ✗ ${overrideEnv}=${override}: ${err.stderr?.toString().trim() || err.message}`); + } + return null; + } + const tag = latestTagLocal(repoPath); // Try latest tag first. @@ -187,7 +216,10 @@ function tryRemote(source) { } for (const ref of refs) { - const cloneDir = join(tmpDir, `${slug}__${ref}`); + // Slashes in ref names (e.g. feat/onboarding-v2) need to be flattened so + // they don't turn into nested tmp directories. + const safeRef = ref.replace(/\//g, "__"); + const cloneDir = join(tmpDir, `${slug}__${safeRef}`); rmSync(cloneDir, { recursive: true, force: true }); mkdirSync(cloneDir, { recursive: true }); try { @@ -195,7 +227,10 @@ function tryRemote(source) { `git clone --depth 1 --branch ${ref} --filter=blob:none --sparse ${url} .`, { cwd: cloneDir, stdio: ["ignore", "ignore", "pipe"] } ); - execSync(`git sparse-checkout set ${docsPath}`, { cwd: cloneDir, stdio: "ignore" }); + execSync(`git sparse-checkout set ${docsPath}`, { + cwd: cloneDir, + stdio: ["ignore", "ignore", "pipe"], + }); const docsSrc = join(cloneDir, docsPath); if (existsSync(docsSrc)) { copyDocs(docsSrc, join(contentDocsDir, slug)); @@ -203,8 +238,9 @@ function tryRemote(source) { console.log(` ✓ ${slug} @ ${ref} (remote, ${docsPath})`); return { version: ref }; } - } catch { - // try next ref + console.log(` ✗ ${ref}: clone ok but ${docsPath}/ missing on that ref`); + } catch (err) { + console.log(` ✗ ${ref}: ${err.stderr?.toString().trim().split("\n").slice(-2).join(" | ") || err.message}`); } rmSync(cloneDir, { recursive: true, force: true }); } From 631dd387dc71f468929fd2d0d6fd0f5ce01c53fe Mon Sep 17 00:00:00 2001 From: Eason WaveKat Date: Sat, 23 May 2026 18:16:43 +1200 Subject: [PATCH 3/3] feat(docs): list voice on docs index Co-Authored-By: Claude Opus 4.7 (1M context) --- src/pages/docs/index.astro | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/docs/index.astro b/src/pages/docs/index.astro index beea313..759ec52 100644 --- a/src/pages/docs/index.astro +++ b/src/pages/docs/index.astro @@ -15,6 +15,7 @@ type Product = { const PRODUCTS: Product[] = [ { slug: 'cli', label: 'CLI', description: 'Terminal client (wk) for the WaveKat platform.', href: '/docs/cli/' }, + { slug: 'voice', label: 'Voice', description: 'Desktop SIP softphone — call from your Mac, Windows, or Linux.', href: '/docs/voice/' }, ]; const entries = await getCollection('docs');