diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 932cc1952ee..ccce1b3e3d6 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -11,6 +11,7 @@ import { unified } from '@astrojs/markdown-remark'; import remarkStripEmojis from './src/lib/remark/stripEmojis.js'; import remarkTableDataLabels from './src/lib/remark/tableDataLabels.js'; import rehypeTableWrapper from './src/lib/rehype/tableWrapper.js'; +import { WORKSHOP_SLUGS } from './src/lib/workshop/config.ts'; /** * Creates blog authors config with GitHub profile pictures @@ -295,7 +296,8 @@ export default defineConfig({ errorOnLocalLinks: true, exclude: ({ file, link }) => file.includes('/src/generated/workshop-markdown/') - && link.startsWith('/gh-aw/workshop/?__gh_aw_workshop_local__='), + && link.includes('?__gh_aw_workshop_local__=') + && WORKSHOP_SLUGS.some((slug) => link.startsWith(`/gh-aw/workshops/${slug}/`)), }) ], sidebar: [ diff --git a/docs/scripts/sync-workshop-content.js b/docs/scripts/sync-workshop-content.js index 39a5c2a10db..ab5ab14913b 100644 --- a/docs/scripts/sync-workshop-content.js +++ b/docs/scripts/sync-workshop-content.js @@ -8,7 +8,8 @@ const outputFile = join(generatedDir, 'workshop-content.ts'); const workshopRepo = process.env.GH_AW_WORKSHOP_REPO || 'githubnext/gh-aw-workshop'; const workshopRef = process.env.GH_AW_WORKSHOP_REF || 'main'; const localWorkshopSourceDir = process.env.GH_AW_WORKSHOP_SOURCE_DIR; -const workshopLocalLinkPrefix = '/gh-aw/workshop/?__gh_aw_workshop_local__='; +const workshopOrgSlug = process.env.GH_AW_WORKSHOP_ORG_SLUG || '2026-07-24-hackathon-blue-bat-18'; +const workshopLocalLinkPrefix = `/gh-aw/workshops/${workshopOrgSlug}/?__gh_aw_workshop_local__=`; const publicWorkshopBase = `https://github.com/${workshopRepo}`; const publicWorkshopTreeUrl = `${publicWorkshopBase}/tree/${workshopRef}/workshop`; const publicWorkshopBlobBaseUrl = `${publicWorkshopBase}/blob/${workshopRef}/workshop/`; diff --git a/docs/src/components/workshop/WorkshopExperience.astro b/docs/src/components/workshop/WorkshopExperience.astro index f4c8d9c0ddf..2bb81c90d05 100644 --- a/docs/src/components/workshop/WorkshopExperience.astro +++ b/docs/src/components/workshop/WorkshopExperience.astro @@ -11,6 +11,12 @@ import { } from '../../lib/workshop/manifest'; import { workshopContent, workshopSource, type WorkshopContentEntry } from '../../generated/workshop-content.ts'; +interface Props { + org: string; +} + +const { org } = Astro.props; + type WorkshopEntry = { id: string; title: string; @@ -35,7 +41,7 @@ type WorkshopMarkdownModule = { const workshopGithubBase = workshopSource.githubBaseUrl; const workshopImageBase = workshopSource.rawBaseUrl; -const workshopLocalLinkPrefix = '/gh-aw/workshop/?__gh_aw_workshop_local__='; +const workshopLocalLinkPrefix = `/gh-aw/workshops/${org}/?__gh_aw_workshop_local__=`; const workshopMarkdownModules = import.meta.glob('../../generated/workshop-markdown/*.md', { eager: true }) as Record; const primerIcons = octicons as Record) => string }>; diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx index fbe7b0e1687..d1c5823f4cf 100644 --- a/docs/src/content/docs/index.mdx +++ b/docs/src/content/docs/index.mdx @@ -23,6 +23,7 @@ import FeatureGrid from '../../components/FeatureGrid.astro'; import Video from '../../components/Video.astro'; import BlogLinkSection from '../../components/BlogLinkSection.astro'; import WorkflowHero from '../../components/WorkflowHero.astro'; +import { CURRENT_WORKSHOP_SLUG } from '../../lib/workshop/config'; @@ -202,7 +203,7 @@ Create custom agentic workflows directly from the GitHub web interface using nat ## Workshop - + Choose a terminal, browser, or Copilot path and work through the workshop directly in the docs with saved progress. diff --git a/docs/src/lib/workshop/config.ts b/docs/src/lib/workshop/config.ts new file mode 100644 index 00000000000..4622a296c94 --- /dev/null +++ b/docs/src/lib/workshop/config.ts @@ -0,0 +1,10 @@ +/** + * All workshop slugs, ordered newest-first, in the form `YYYY-MM-DD-org-name`. + * Add a new entry at the top of the array for each new workshop. + */ +export const WORKSHOP_SLUGS = [ + '2026-07-24-hackathon-blue-bat-18', +] as const; + +/** The slug for the current (latest) active workshop. */ +export const CURRENT_WORKSHOP_SLUG = WORKSHOP_SLUGS[0]; diff --git a/docs/src/pages/workshops/[org].astro b/docs/src/pages/workshops/[org].astro new file mode 100644 index 00000000000..ff472fd4738 --- /dev/null +++ b/docs/src/pages/workshops/[org].astro @@ -0,0 +1,29 @@ +--- +import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro'; +import WorkshopExperience from '../../components/workshop/WorkshopExperience.astro'; +import { WORKSHOP_SLUGS } from '../../lib/workshop/config'; + +export function getStaticPaths() { + return WORKSHOP_SLUGS.map((org) => ({ params: { org } })); +} + +const { org } = Astro.params; +if (!org) throw new Error('Workshop route: missing org param'); +--- + + + + + + diff --git a/docs/tests/workshop.spec.ts b/docs/tests/workshop.spec.ts index e552053f99b..faf1c025645 100644 --- a/docs/tests/workshop.spec.ts +++ b/docs/tests/workshop.spec.ts @@ -1,5 +1,7 @@ import { expect, test, type Page } from '@playwright/test'; +import { CURRENT_WORKSHOP_SLUG } from '../src/lib/workshop/config'; +const WORKSHOP_URL = `/gh-aw/workshops/${CURRENT_WORKSHOP_SLUG}/`; const PIXEL_TOLERANCE = 1; const ZEN_MODE_MOBILE_BREAKPOINT = 800; @@ -12,7 +14,7 @@ const workshopDevices = [ ]; async function startWorkshop(page: Page) { - await page.goto('/gh-aw/workshop/'); + await page.goto(WORKSHOP_URL); await page.waitForLoadState('networkidle'); await page.locator('[data-workshop-entry-path="ui-learner"]').click(); await page.locator('[data-workshop-scenario="daily-status"]').click(); @@ -188,7 +190,7 @@ test.describe('Workshop tutorial', () => { test.describe('Workshop URL hash navigation', () => { test('encodes journey and scenario in the URL hash after setup', async ({ page }) => { - await page.goto('/gh-aw/workshop/'); + await page.goto(WORKSHOP_URL); await page.waitForLoadState('networkidle'); await page.locator('[data-workshop-journey="github"]').click(); @@ -220,7 +222,7 @@ test.describe('Workshop URL hash navigation', () => { const stepPosition = await page.locator('[data-workshop-step-position]').textContent(); // Navigate away so storage would otherwise default back to step 1. - await page.goto('/gh-aw/workshop/'); + await page.goto(WORKSHOP_URL); await page.waitForLoadState('networkidle'); // Clear session storage so the only source of truth for the step is the URL hash. await page.evaluate(() => sessionStorage.clear()); @@ -235,7 +237,7 @@ test.describe('Workshop URL hash navigation', () => { }); test('supports browser back navigation from tutorial to setup', async ({ page }) => { - await page.goto('/gh-aw/workshop/'); + await page.goto(WORKSHOP_URL); await page.waitForLoadState('networkidle'); await page.locator('[data-workshop-journey="github"]').click(); @@ -250,7 +252,7 @@ test.describe('Workshop URL hash navigation', () => { }); test('supports browser back navigation from scenario picker to workspace picker', async ({ page }) => { - await page.goto('/gh-aw/workshop/'); + await page.goto(WORKSHOP_URL); await page.waitForLoadState('networkidle'); await page.locator('[data-workshop-journey="github"]').click();