diff --git a/.agents/skills/update-samples/SKILL.md b/.agents/skills/update-samples/SKILL.md index abdd440cb..78f58715e 100644 --- a/.agents/skills/update-samples/SKILL.md +++ b/.agents/skills/update-samples/SKILL.md @@ -141,6 +141,7 @@ After running the script: | Variable | Required | Description | |---|---|---| | `GITHUB_TOKEN` | No | GitHub personal access token for higher API rate limits (60 → 5000 requests/hour) | +| `SAMPLES_BRANCH` | No | Override the `microsoft/aspire-samples` ref to fetch from (default `main`). Useful for staging `samples.json` against a still-open upstream PR — set to the PR's head ref. The generated `href` fields stay anchored to `main` so links remain valid after the PR branch is deleted. | ### 7. Integration with build diff --git a/src/frontend/scripts/update-samples.ts b/src/frontend/scripts/update-samples.ts index b9bd458a1..bd1e9bf78 100644 --- a/src/frontend/scripts/update-samples.ts +++ b/src/frontend/scripts/update-samples.ts @@ -5,14 +5,32 @@ import { pipeline } from 'stream/promises'; import fetch from 'node-fetch'; const REPO = 'microsoft/aspire-samples'; -const BRANCH = 'main'; +const DEFAULT_BRANCH = 'main'; +// `BRANCH` controls which ref of `microsoft/aspire-samples` is fetched (README, +// AppHost code, raw assets, tree listing). The override exists so contributors +// can stage `samples.json` against a still-open upstream PR by running the +// script with `SAMPLES_BRANCH= pnpm run update:samples` — the output +// is identical to what the next normal run will produce after that PR merges, +// so the staged data PR is idempotent with the upstream merge. +const BRANCH = process.env.SAMPLES_BRANCH ?? DEFAULT_BRANCH; +// PR head refs frequently contain `/` (e.g. `feature/foo`, +// `user/dapine-aspire-api-updates`). GitHub accepts `/` in path positions for +// the raw, contents, and git/trees endpoints, but other URL-significant +// characters in a ref (`#`, `?`, `%`, `&`, etc.) need to be percent-encoded. +// Encoding per `/`-separated segment preserves the `/` boundaries while +// safely escaping every other special character within each segment. +const BRANCH_PATH = BRANCH.split('/').map(encodeURIComponent).join('/'); +const BRANCH_QUERY = encodeURIComponent(BRANCH); const SAMPLES_DIR = 'samples'; const OUTPUT_PATH = './src/data/samples.json'; const ASSETS_DIR = './src/assets/samples'; const ASSETS_IMPORT_PREFIX = '~/assets/samples'; const GITHUB_API = 'https://api.github.com'; -const RAW_BASE = `https://raw.githubusercontent.com/${REPO}/${BRANCH}`; -const TREE_BASE = `https://github.com/${REPO}/tree/${BRANCH}`; +const RAW_BASE = `https://raw.githubusercontent.com/${REPO}/${BRANCH_PATH}`; +// `TREE_BASE` is intentionally pinned to `main` (never the override) so the +// generated `href` fields on each sample stay valid after a PR branch goes +// away. The branch override is for fetching content, not for cementing links. +const TREE_BASE = `https://github.com/${REPO}/tree/${DEFAULT_BRANCH}`; const LEGACY_DOCS_HOST = 'https://learn.microsoft.com'; const ASPIRE_DOC_URL_REWRITES = [ [ @@ -364,7 +382,7 @@ async function fetchText(url: string): Promise { async function listSampleDirs(): Promise { const contents = await fetchJson( - `${GITHUB_API}/repos/${REPO}/contents/${SAMPLES_DIR}?ref=${BRANCH}` + `${GITHUB_API}/repos/${REPO}/contents/${SAMPLES_DIR}?ref=${BRANCH_QUERY}` ); return contents @@ -380,7 +398,7 @@ async function listSampleDirs(): Promise { */ async function fetchSamplePaths(): Promise> { const tree = await fetchJson( - `${GITHUB_API}/repos/${REPO}/git/trees/${BRANCH}?recursive=1` + `${GITHUB_API}/repos/${REPO}/git/trees/${BRANCH_PATH}?recursive=1` ); if (tree.truncated) { @@ -471,7 +489,10 @@ async function processSample( } async function main(): Promise { - console.log(`📦 Fetching sample directories from ${REPO}...`); + console.log(`📦 Fetching sample directories from ${REPO}@${BRANCH}...`); + if (BRANCH !== DEFAULT_BRANCH) { + console.log(` (branch override via SAMPLES_BRANCH; href links stay anchored to ${DEFAULT_BRANCH})`); + } const [dirs, pathsBySample] = await Promise.all([listSampleDirs(), fetchSamplePaths()]); console.log(`📂 Found ${dirs.length} sample directories`);