Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 34 additions & 10 deletions .github/workflows/new-adventure.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,41 @@ jobs:
printf '| Title | %s |\n' "$ADVENTURE_TITLE"
printf '| Month | %s |\n' "$ADVENTURE_MONTH"
printf '| Levels | %s |\n\n' "$ADVENTURE_LEVELS"
printf '### Generated files\n'
printf -- '- `src/data/adventures/%s/adventure.yaml` (fill in TODOs)\n' "$ADVENTURE_ID"
printf -- '- Discussion JSON stubs per level\n'
printf '### Generated files\n\n'
printf -- '- `src/data/adventures/%s/adventure.yaml` fill in the fields below\n' "$ADVENTURE_ID"
printf -- '- Discussion JSON stubs per level (update `discussionUrl` in each)\n'
printf -- '- Routes added to `react-router.config.ts`\n'
printf -- '- Sitemap entries added\n\n'
printf '### Next steps\n'
printf '1. Fill in the TODOs in `src/data/adventures/%s/adventure.yaml`\n' "$ADVENTURE_ID"
printf '2. Update `discussionUrl` in the YAML and each level `-posts.json` file\n'
printf '3. Run `npm run generate` to produce the TypeScript from YAML\n'
printf '4. Run `node scripts/refresh-discussions.mjs`\n'
printf '5. Run all checks: `npm run lint && npm test && npm run build && npm run test:e2e`\n'
printf -- '- Sitemap entries added to `public/sitemap.xml`\n\n'
printf '### Required fields — fill in `adventure.yaml`\n\n'
printf -- '- [ ] `story` — 2-3 sentences: what tech is used and what each level does\n'
printf -- '- [ ] `tags` — technology names matching the topics across all levels\n\n'
printf 'For each level:\n\n'
printf -- '- [ ] `name` — display name (e.g. "Stand Up the Lab")\n'
printf -- '- [ ] `topics` — technology pill tags shown on the level card\n'
printf -- '- [ ] `learnings` — full sentences describing concrete skills gained\n'
printf -- '- [ ] `devcontainerPath` — path to `devcontainer.json` in the challenges repo\n'
printf -- '- [ ] `discussionUrl` — Discourse topic URL or relative path (e.g. `/t/slug/123`)\n'
printf -- '- [ ] `intro` — 1-2 sentences: what to wire up and what to prove works\n'
printf -- '- [ ] `objective` — verifiable outcomes a player can check with a command\n'
printf -- '- [ ] `toolbox` — CLI tools and services available in the Codespace\n'
printf -- '- [ ] `howToPlay` — step-by-step walkthrough; first step confirms broken state\n'
printf -- '- [ ] `verification` — keep defaults unless the script name or message differs\n\n'
printf '### Optional fields (uncomment in `adventure.yaml`)\n\n'
printf -- '- `contributor` — external contributor name, URL, and bio\n'
printf -- '- `backstory` (adventure) — narrative context for the whole adventure (1-3 paragraphs)\n'
printf -- '- `context` — "What you'\''ll be using" explainer section\n'
printf -- '- `rewards` — prize tiers, deadline, and ranking rules\n'
printf -- '- Per level: `audience`, `backstory`, `architecture` + `architectureDiagram` + `diagramAlt`, `helpfulLinks`, `deadline`\n\n'
printf '### Next steps\n\n'
printf '1. Fill in `src/data/adventures/%s/adventure.yaml` (required fields above)\n' "$ADVENTURE_ID"
printf '2. Add the adventure and each level route to `src/routes.ts`\n'
printf '3. Update `discussionUrl` in the YAML and each level `*-posts.json` file\n'
printf '4. Run `npm run generate`\n'
printf '5. Run `node scripts/refresh-discussions.mjs`\n'
printf '6. Add the adventure to `ADVENTURE_CATEGORIES` in `scripts/refresh-leaderboard.mjs` (set `categoryId` and per-level booleans), then run `node scripts/refresh-leaderboard.mjs`\n'
printf '7. Add each level URL to the `ROUTES` array in `e2e/smoke.spec.ts` and `src/test/seo.test.ts`, and to the `pages` array in `src/test/prerender.test.ts` with the expected `<title>` value\n'
printf '8. Update the routes table in `README.md`\n'
printf '9. Run all checks: `npm run lint && npm test && npm run build && npm run test:e2e`\n'
} > /tmp/pr-body.md
gh pr create \
--title "feat: scaffold adventure — ${ADVENTURE_TITLE}" \
Expand Down
45 changes: 34 additions & 11 deletions .github/workflows/new-level.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,18 +78,41 @@ jobs:
printf '| Field | Value |\n|---|---|\n'
printf '| Adventure | `%s` |\n' "$ADVENTURE"
printf '| Level | `%s` |\n\n' "$LEVEL"
printf '### Generated\n'
printf -- '- `src/data/adventures/%s/%s-posts.json` (discussion stub)\n' "$ADVENTURE" "$LEVEL"
printf -- '- Prerender entry in `react-router.config.ts`\n'
printf -- '- Sitemap entry in `public/sitemap.xml`\n\n'
printf '### Manual step required\n'
printf 'Paste this into the `levels` array in `src/data/adventures/%s/adventure.yaml`:\n\n' "$ADVENTURE"
printf '### Generated files\n\n'
printf -- '- `src/data/adventures/%s/%s-posts.json` — update `discussionUrl`\n' "$ADVENTURE" "$LEVEL"
printf -- '- Prerender entry added to `react-router.config.ts`\n'
printf -- '- Sitemap entry added to `public/sitemap.xml`\n'
printf -- '- `has_%s` set to `true` in `ADVENTURE_CATEGORIES` in `scripts/refresh-leaderboard.mjs`\n\n' "$LEVEL"
printf '### Paste this into the `levels` array in `adventure.yaml`\n\n'
printf '```yaml\n%s\n```\n\n' "$SNIPPET"
printf '### Next steps\n'
printf '1. Paste the snippet above and fill in the TODOs\n'
printf '2. Update `discussionUrl` in the YAML and the `-posts.json` file\n'
printf '3. Run `node scripts/refresh-discussions.mjs`\n'
printf '4. Run all checks: `npm run lint && npm test && npm run build && npm run test:e2e`\n'
printf '### Required fields — fill in after pasting\n\n'
printf -- '- [ ] `name` — display name (e.g. "Stand Up the Lab")\n'
printf -- '- [ ] `topics` — technology pill tags shown on the level card\n'
printf -- '- [ ] `learnings` — full sentences describing concrete skills gained\n'
printf -- '- [ ] `devcontainerPath` — path to `devcontainer.json` in the challenges repo\n'
printf -- '- [ ] `discussionUrl` — Discourse topic URL or relative path (e.g. `/t/slug/123`)\n'
printf -- '- [ ] `intro` — 1-2 sentences: what to wire up and what to prove works\n'
printf -- '- [ ] `objective` — verifiable outcomes a player can check with a command\n'
printf -- '- [ ] `toolbox` — CLI tools and services available in the Codespace\n'
printf -- '- [ ] `howToPlay` — step-by-step walkthrough; first step confirms broken state\n'
printf -- '- [ ] `verification` — keep defaults unless the script name or message differs\n\n'
printf '### Optional fields (uncomment after pasting)\n\n'
printf -- '- `audience` — who this level is aimed at and what prior knowledge helps\n'
printf -- '- `backstory` — narrative context that sets the scene for this specific level\n'
printf -- '- `architecture` — technical setup (services, ports, how they connect)\n'
printf -- '- `architectureDiagram` + `diagramAlt` — SVG diagram filename and alt text\n'
printf -- '- `helpfulLinks` — reference docs the player will need\n'
printf -- '- `deadline` — submission deadline if rewards are active\n\n'
printf '### Next steps\n\n'
printf '1. Paste the snippet above into `src/data/adventures/%s/adventure.yaml` and fill in required fields\n' "$ADVENTURE"
printf '2. Add the level route to `src/routes.ts`\n'
printf '3. Update `discussionUrl` in the YAML and `src/data/adventures/%s/%s-posts.json`\n' "$ADVENTURE" "$LEVEL"
printf '4. Run `npm run generate`\n'
printf '5. Run `node scripts/refresh-discussions.mjs`\n'
printf '6. Run `node scripts/refresh-leaderboard.mjs` (requires `DISCOURSE_API_KEY`)\n'
printf '7. Add the level URL to the `ROUTES` array in `e2e/smoke.spec.ts` and `src/test/seo.test.ts`, and to the `pages` array in `src/test/prerender.test.ts` with the expected `<title>` value\n'
printf '8. Update the routes table in `README.md`\n'
printf '9. Run all checks: `npm run lint && npm test && npm run build && npm run test:e2e`\n'
} > /tmp/pr-body.md
gh pr create \
--title "feat: add ${LEVEL} level — ${ADVENTURE}" \
Expand Down
1 change: 1 addition & 0 deletions e2e/smoke.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const ROUTES: RouteSpec[] = [
{ path: "/adventures/blind-by-design", title: /Blind by Design/ },
{ path: "/adventures/blind-by-design/levels/beginner", title: /Stand up the Lab/ },
{ path: "/adventures/blind-by-design/levels/intermediate", title: /Outcome by Cohort/ },
{ path: "/adventures/blind-by-design/levels/expert", title: /Read the Chart/ },
{ path: "/challenges", title: /Open Source Challenges/ },
{ path: "/challenges/argo-cd", title: /Argo CD Challenges/ },
{ path: "/challenges/argo-rollouts", title: /Argo Rollouts Challenges/ },
Expand Down
1 change: 1 addition & 0 deletions public/sitemap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@
<url><loc>https://offon.dev/challenges/terraform/</loc><changefreq>monthly</changefreq><priority>0.7</priority></url>
<url><loc>https://offon.dev/challenges/trivy/</loc><changefreq>monthly</changefreq><priority>0.7</priority></url>
<url><loc>https://offon.dev/challenges/flagd/</loc><changefreq>monthly</changefreq><priority>0.7</priority></url>
<url><loc>https://offon.dev/adventures/blind-by-design/levels/expert/</loc><changefreq>monthly</changefreq><priority>0.8</priority></url>
</urlset>
1 change: 1 addition & 0 deletions react-router.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,6 @@ export default {
"/challenges/terraform",
"/challenges/trivy",
"/challenges/flagd",
"/adventures/blind-by-design/levels/expert",
],
} satisfies Config;
5 changes: 5 additions & 0 deletions schemas/adventure.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,11 @@
},
"description": "Reference documentation links shown at the end of the challenge walkthrough."
},
"metaDescription": {
"type": "string",
"maxLength": 160,
"description": "Optional SEO meta description (max 160 chars). Use when the auto-generated description from learnings is insufficient. If omitted, the generator builds one from the level name, learnings, difficulty, and adventure title."
},
"solvedCount": { "type": "integer" },
"topPlayers": {
"type": "array",
Expand Down
1 change: 1 addition & 0 deletions scripts/generate-adventures.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ function generateLevelCode(level, adventureId, indent) {
lines.push(`${i2}},`);
}

if (level.metaDescription) lines.push(`${i2}metaDescription: ${formatString(level.metaDescription)},`);
if (level.solvedCount !== undefined) lines.push(`${i2}solvedCount: ${level.solvedCount},`);
if (level.topPlayers && level.topPlayers.length > 0) {
lines.push(`${i2}topPlayers: [`);
Expand Down
Loading
Loading