-
Notifications
You must be signed in to change notification settings - Fork 2.6k
feat: support external recipes in cookbook #831
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: staged
Are you sure you want to change the base?
Changes from all commits
ba9567c
de75aec
fd24ef8
04083bd
0273497
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -742,9 +742,12 @@ function generateSamplesData() { | |
| const allTags = new Set(); | ||
| let totalRecipes = 0; | ||
|
|
||
| const cookbooks = cookbookManifest.cookbooks.map((cookbook) => { | ||
| // Collect languages | ||
| // First pass: collect all known language IDs across cookbooks | ||
| cookbookManifest.cookbooks.forEach((cookbook) => { | ||
| cookbook.languages.forEach((lang) => allLanguages.add(lang.id)); | ||
| }); | ||
|
|
||
| const cookbooks = cookbookManifest.cookbooks.map((cookbook) => { | ||
|
|
||
| // Process recipes and add file paths | ||
| const recipes = cookbook.recipes.map((recipe) => { | ||
|
|
@@ -753,6 +756,36 @@ function generateSamplesData() { | |
| recipe.tags.forEach((tag) => allTags.add(tag)); | ||
| } | ||
|
|
||
| totalRecipes++; | ||
|
|
||
| // External recipes link to an external URL — skip local file resolution | ||
| if (recipe.external) { | ||
| if (recipe.url) { | ||
| try { | ||
| new URL(recipe.url); | ||
| } catch { | ||
| console.warn(`Warning: Invalid URL for external recipe "${recipe.id}": ${recipe.url}`); | ||
| } | ||
| } else { | ||
| console.warn(`Warning: External recipe "${recipe.id}" is missing a url`); | ||
| } | ||
|
|
||
| // Derive languages from tags that match known language IDs | ||
| const recipeLanguages = (recipe.tags || []).filter((tag) => allLanguages.has(tag)); | ||
|
|
||
| return { | ||
| id: recipe.id, | ||
| name: recipe.name, | ||
| description: recipe.description, | ||
| tags: recipe.tags || [], | ||
| languages: recipeLanguages, | ||
| external: true, | ||
| url: recipe.url || null, | ||
| author: recipe.author || null, | ||
| variants: {}, | ||
| }; | ||
| } | ||
|
Comment on lines
+762
to
+787
|
||
|
|
||
| // Build variants with file paths for each language | ||
| const variants = {}; | ||
| cookbook.languages.forEach((lang) => { | ||
|
|
@@ -771,13 +804,12 @@ function generateSamplesData() { | |
| } | ||
| }); | ||
|
|
||
| totalRecipes++; | ||
|
|
||
| return { | ||
| id: recipe.id, | ||
| name: recipe.name, | ||
| description: recipe.description, | ||
| tags: recipe.tags || [], | ||
| languages: Object.keys(variants), | ||
| variants, | ||
| }; | ||
| }); | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -25,7 +25,11 @@ interface Recipe { | |||||||||||||||||
| name: string; | ||||||||||||||||||
| description: string; | ||||||||||||||||||
| tags: string[]; | ||||||||||||||||||
| languages: string[]; | ||||||||||||||||||
| variants: Record<string, RecipeVariant>; | ||||||||||||||||||
| external?: boolean; | ||||||||||||||||||
| url?: string | null; | ||||||||||||||||||
| author?: { name: string; url?: string } | null; | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| interface Cookbook { | ||||||||||||||||||
|
|
@@ -138,7 +142,7 @@ function setupFilters(): void { | |||||||||||||||||
| languages.forEach((lang, id) => { | ||||||||||||||||||
| const option = document.createElement("option"); | ||||||||||||||||||
| option.value = id; | ||||||||||||||||||
| option.textContent = `${lang.icon} ${lang.name}`; | ||||||||||||||||||
| option.textContent = lang.name; | ||||||||||||||||||
| languageSelect.appendChild(option); | ||||||||||||||||||
| }); | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
@@ -257,10 +261,10 @@ function getFilteredRecipes(): { | |||||||||||||||||
| ); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // Apply language filter | ||||||||||||||||||
| // Apply language filter using per-recipe languages array | ||||||||||||||||||
| if (selectedLanguage) { | ||||||||||||||||||
| results = results.filter( | ||||||||||||||||||
| ({ recipe }) => recipe.variants[selectedLanguage!] | ||||||||||||||||||
| results = results.filter(({ recipe }) => | ||||||||||||||||||
| recipe.languages.includes(selectedLanguage!) | ||||||||||||||||||
| ); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
@@ -370,14 +374,54 @@ function renderRecipeCard( | |||||||||||||||||
| const recipeKey = `${cookbook.id}-${recipe.id}`; | ||||||||||||||||||
| const isExpanded = expandedRecipes.has(recipeKey); | ||||||||||||||||||
|
|
||||||||||||||||||
| // Determine which language to show | ||||||||||||||||||
| const displayLang = selectedLanguage || cookbook.languages[0]?.id || "nodejs"; | ||||||||||||||||||
| const variant = recipe.variants[displayLang]; | ||||||||||||||||||
|
|
||||||||||||||||||
| const tags = recipe.tags | ||||||||||||||||||
| .map((tag) => `<span class="recipe-tag">${escapeHtml(tag)}</span>`) | ||||||||||||||||||
| .join(""); | ||||||||||||||||||
|
|
||||||||||||||||||
| // External recipe — link to external URL | ||||||||||||||||||
| if (recipe.external && recipe.url) { | ||||||||||||||||||
| const authorHtml = recipe.author | ||||||||||||||||||
| ? `<span class="recipe-author">by ${ | ||||||||||||||||||
| recipe.author.url | ||||||||||||||||||
| ? `<a href="${escapeHtml(recipe.author.url)}" target="_blank" rel="noopener">${escapeHtml(recipe.author.name)}</a>` | ||||||||||||||||||
| : escapeHtml(recipe.author.name) | ||||||||||||||||||
| }</span>` | ||||||||||||||||||
| : ""; | ||||||||||||||||||
|
|
||||||||||||||||||
| return ` | ||||||||||||||||||
| <div class="recipe-card external${ | ||||||||||||||||||
| isExpanded ? " expanded" : "" | ||||||||||||||||||
| }" data-recipe="${recipeKey}"> | ||||||||||||||||||
|
||||||||||||||||||
| }" data-recipe="${recipeKey}"> | |
| }" data-recipe="${escapeHtml(recipeKey)}"> |
Copilot
AI
Feb 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The button text "View on GitHub" is hardcoded, but the recipe.url could potentially point to repositories hosted on platforms other than GitHub (GitLab, Bitbucket, etc.). Consider making the button text more generic (e.g., "View Project" or "View Repository") or dynamically determining the text based on the URL domain.
Copilot
AI
Feb 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code accesses cookbook.languages[0]?.id which could fail if cookbook.languages is undefined or null (not just an empty array). For consistency with the community-samples cookbook that has languages: [], consider adding a null check before accessing the array, such as cookbook.languages?.[0]?.id.
This issue also appears on line 425 of the same file.
| const displayLang = selectedLanguage || cookbook.languages[0]?.id || "nodejs"; | |
| const variant = recipe.variants[displayLang]; | |
| const langIndicators = cookbook.languages | |
| const displayLang = selectedLanguage || cookbook.languages?.[0]?.id || "nodejs"; | |
| const variant = recipe.variants[displayLang]; | |
| const langIndicators = (cookbook.languages ?? []) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code assumes
cookbook.languagesalways exists and is an array, but doesn't handle the case where it might be undefined or null. This could cause a runtime error when processing cookbooks with missing or invalid language configuration. Consider adding a null check or defaulting to an empty array.This issue also appears on line 791 of the same file.