feat: add Vale ecosystem links to generated sites#2
Conversation
Add ResourceLink and ResourcesConfig types to support configurable resources page and footer links. Follows existing *bool toggle pattern (nil = enabled by default, explicit false = disabled).
…page Hardcode 3 default links (vale.sh, Vale Studio, Vale GitHub) and merge with any extra_links from config. Generate content/resources/_index.md unless resources.enabled is explicitly false. Resource links always appear in site.json so the footer can use them regardless.
Resources page renders link cards from site.json data. Footer shows Vale and Vale Studio links (marked with footer: true) after the Rulebound credit on every page.
Document the resources config fields, add a custom resource links example, and list Vale ecosystem links in a dedicated README section.
There was a problem hiding this comment.
Pull request overview
Adds a “Resources” concept to generated Hugo sites, including default Vale ecosystem links, optional package-defined extra links, a dedicated /resources/ page, and footer rendering driven by data/site.json.
Changes:
- Introduces
resourcesconfig (enabled,extra_links) and related tests. - Generates
content/resources/_index.mdand emitsresource_linksintodata/site.json. - Updates Hugo theme to render resource links in the footer and adds a resources list layout + CSS.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| internal/hugo/theme/static/css/style.css | Adds styles for the new resources page card/grid components. |
| internal/hugo/theme/layouts/resources/list.html | New list template that renders links from data/site.json. |
| internal/hugo/theme/layouts/_default/baseof.html | Footer now renders ecosystem links from resource_links. |
| internal/generator/resources.go | Implements defaults, merging extra links, and resources page generation. |
| internal/generator/index.go | Extends site.json schema to include resource_links. |
| internal/generator/generator_test.go | Adds generator tests for defaults, extra links, and page enable/disable. |
| internal/generator/generator.go | Wires resource link building, page generation toggle, and site.json emission. |
| internal/config/config_test.go | Adds config parsing tests for resources fields. |
| internal/config/config.go | Adds ResourcesConfig / ResourceLink types to config schema. |
| examples/starter-package/rulebound.yml | Demonstrates resources.extra_links usage in example package. |
| README.md | Documents new config fields and adds a Vale resources section. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| links := stats["resource_links"].([]interface{}) | ||
| if len(links) != 4 { | ||
| t.Fatalf("resource_links length = %d, want 4 (3 defaults + 1 custom)", len(links)) | ||
| } | ||
|
|
||
| last := links[3].(map[string]interface{}) |
There was a problem hiding this comment.
This test uses an unchecked type assertion (stats["resource_links"].([]interface{})), which can panic and obscure the real failure if resource_links is missing or not an array. Mirror the prior test’s checked assertion and fail with a helpful message.
| links := stats["resource_links"].([]interface{}) | |
| if len(links) != 4 { | |
| t.Fatalf("resource_links length = %d, want 4 (3 defaults + 1 custom)", len(links)) | |
| } | |
| last := links[3].(map[string]interface{}) | |
| rawLinks, ok := stats["resource_links"] | |
| if !ok { | |
| t.Fatalf("site.json missing 'resource_links' key: %#v", stats) | |
| } | |
| links, ok := rawLinks.([]interface{}) | |
| if !ok { | |
| t.Fatalf("site.json 'resource_links' has unexpected type %T (value: %#v)", rawLinks, rawLinks) | |
| } | |
| if len(links) != 4 { | |
| t.Fatalf("resource_links length = %d, want 4 (3 defaults + 1 custom)", len(links)) | |
| } | |
| lastRaw := links[3] | |
| last, ok := lastRaw.(map[string]interface{}) | |
| if !ok { | |
| t.Fatalf("resource_links[3] has unexpected type %T (value: %#v)", lastRaw, lastRaw) | |
| } |
| // buildResourceLinks merges defaults with extra links from config. | ||
| func buildResourceLinks(cfg *config.Config) []resourceLink { | ||
| links := defaultResourceLinks() | ||
| for _, extra := range cfg.Resources.ExtraLinks { | ||
| links = append(links, resourceLink{ | ||
| Label: extra.Label, | ||
| URL: extra.URL, | ||
| Description: extra.Description, | ||
| Footer: false, | ||
| }) | ||
| } |
There was a problem hiding this comment.
Custom resource URLs from cfg.Resources.ExtraLinks are written directly into site.json and then rendered into href attributes. This allows unsafe schemes (e.g., javascript:, data:) if a package config is untrusted. Validate extra.URL (and optionally defaults) to only allow http/https (or an explicit allowlist) and fail the build (or skip the link) when invalid; also consider rejecting empty label/URL values.
| {{- $data := index hugo.Data "site" -}} | ||
| {{- range $data.resource_links -}} | ||
| {{- if .footer }} · <a href="{{ .url }}" target="_blank" rel="noopener noreferrer">{{ .label }}</a>{{- end -}} | ||
| {{- end }} |
There was a problem hiding this comment.
The /resources/ page is generated, but there’s no in-site link to it (footer only renders external links and sidebar doesn’t include it), so it’s effectively undiscoverable unless users guess the URL. Consider adding a “Resources” internal link (e.g., to /resources/) in the footer and/or sidebar when resources.enabled is true.
| {{- end }} | |
| {{- end }} | |
| {{- if $data.resources.enabled }} · <a href="{{ "/resources/" | relURL }}">Resources</a>{{- end }} |
| // Verify first default is Vale | ||
| first := links[0].(map[string]interface{}) | ||
| if first["label"] != "Vale" { |
There was a problem hiding this comment.
This test uses an unchecked type assertion (links[0].(map[string]interface{})), which will panic with a less-informative stack trace if the JSON structure changes. Prefer a checked assertion with a clear test failure message.
Summary
/resources/page with card-style layout for all ecosystem linksresources.extra_linksinrulebound.ymlresources.enabled: falseChanges
Config (
internal/config/)ResourceLinkandResourcesConfigtypes with*boolenable/disable patternGenerator (
internal/generator/)resources.go: default links, link merging, resources page generationsite.jsonto includeresource_linksarrayTheme (
internal/hugo/theme/)resources/list.htmllayout with card gridsite.jsondata.resources-*componentsDocs
extra_linksdemoTest plan
go test ./...— all packages pass