feat(theme): --modes light,dark for --pages gallery captures
Sets the bn-theme cookie + emulated prefers-color-scheme before navigation so each page can be captured in both color modes (Wave B screenshot sets). Mode-suffixed filenames (NN-<slug>-<mode>.png); empty --modes keeps the old single site-default capture and naming. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
ddf2dd59ef
commit
dda752bfc1
@ -23,7 +23,7 @@ func newThemeCmd() *cobra.Command {
|
||||
|
||||
func newThemeScreenshotCmd() *cobra.Command {
|
||||
var gallery, slug, out, mobileOut, themeName, waitSelector string
|
||||
var pages, screenshotsDir string
|
||||
var pages, screenshotsDir, modes string
|
||||
var mobile bool
|
||||
var width, height int
|
||||
cmd := &cobra.Command{
|
||||
@ -101,31 +101,44 @@ the rest of the CLI.`,
|
||||
// files the .bnp packer ships under screenshots/ and the registry
|
||||
// ingests into the plugin gallery at publish.
|
||||
if pages != "" {
|
||||
modeList, err := parseModes(modes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(screenshotsDir, 0o755); err != nil {
|
||||
return fmt.Errorf("create %s: %w", screenshotsDir, err)
|
||||
}
|
||||
for i, pageSlug := range strings.Split(pages, ",") {
|
||||
n := 0
|
||||
for _, pageSlug := range strings.Split(pages, ",") {
|
||||
pageSlug = strings.TrimSpace(pageSlug)
|
||||
if pageSlug == "" {
|
||||
continue
|
||||
}
|
||||
pURL := shot.PreviewURL(gallery, pageSlug, themeName)
|
||||
fmt.Fprintf(os.Stderr, "capturing page %s: %s\n", pageSlug, pURL)
|
||||
ppng, err := shot.Capture(ctx, shot.Options{
|
||||
URL: pURL,
|
||||
Width: width,
|
||||
Height: height,
|
||||
WaitSelector: waitSelector,
|
||||
Timeout: 45 * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
for _, mode := range modeList {
|
||||
pURL := shot.PreviewURL(gallery, pageSlug, themeName)
|
||||
fmt.Fprintf(os.Stderr, "capturing page %s (%s): %s\n", pageSlug, modeLabel(mode), pURL)
|
||||
ppng, err := shot.Capture(ctx, shot.Options{
|
||||
URL: pURL,
|
||||
Width: width,
|
||||
Height: height,
|
||||
WaitSelector: waitSelector,
|
||||
Timeout: 45 * time.Second,
|
||||
Mode: mode,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n++
|
||||
name := fmt.Sprintf("%02d-%s.png", n, slugFileName(pageSlug))
|
||||
if mode != "" {
|
||||
name = fmt.Sprintf("%02d-%s-%s.png", n, slugFileName(pageSlug), mode)
|
||||
}
|
||||
dest := filepath.Join(screenshotsDir, name)
|
||||
if err := os.WriteFile(dest, ppng, 0o644); err != nil {
|
||||
return fmt.Errorf("write %s: %w", dest, err)
|
||||
}
|
||||
fmt.Printf("wrote %s (%d bytes)\n", dest, len(ppng))
|
||||
}
|
||||
dest := filepath.Join(screenshotsDir, fmt.Sprintf("%02d-%s.png", i+1, slugFileName(pageSlug)))
|
||||
if err := os.WriteFile(dest, ppng, 0o644); err != nil {
|
||||
return fmt.Errorf("write %s: %w", dest, err)
|
||||
}
|
||||
fmt.Printf("wrote %s (%d bytes)\n", dest, len(ppng))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -142,9 +155,34 @@ the rest of the CLI.`,
|
||||
cmd.Flags().IntVar(&height, "height", 900, "Desktop viewport height")
|
||||
cmd.Flags().StringVar(&pages, "pages", "", "Comma-separated page slugs to capture into --screenshots-dir (e.g. /,/blog,/about)")
|
||||
cmd.Flags().StringVar(&screenshotsDir, "screenshots-dir", "screenshots", "Directory for --pages captures (packed into the .bnp)")
|
||||
cmd.Flags().StringVar(&modes, "modes", "", `Comma-separated color modes for --pages captures: "light", "dark" or "light,dark" (sets the bn-theme cookie per capture; empty = site default, unsuffixed filenames)`)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// parseModes validates the --modes flag: empty means one site-default capture
|
||||
// (mode ""), otherwise each entry must be "light" or "dark".
|
||||
func parseModes(modes string) ([]string, error) {
|
||||
if strings.TrimSpace(modes) == "" {
|
||||
return []string{""}, nil
|
||||
}
|
||||
var out []string
|
||||
for _, m := range strings.Split(modes, ",") {
|
||||
m = strings.TrimSpace(m)
|
||||
if m != "light" && m != "dark" {
|
||||
return nil, fmt.Errorf("--modes: %q is not a valid mode (want light or dark)", m)
|
||||
}
|
||||
out = append(out, m)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func modeLabel(mode string) string {
|
||||
if mode == "" {
|
||||
return "default"
|
||||
}
|
||||
return mode
|
||||
}
|
||||
|
||||
// slugFileName turns a page slug into a filename fragment ("/" → "home",
|
||||
// "/blog/post" → "blog-post").
|
||||
func slugFileName(slug string) string {
|
||||
|
||||
@ -11,6 +11,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/chromedp/cdproto/emulation"
|
||||
"github.com/chromedp/cdproto/network"
|
||||
"github.com/chromedp/chromedp"
|
||||
)
|
||||
|
||||
@ -22,6 +24,7 @@ type Options struct {
|
||||
FullPage bool // capture the full scroll height, not just the viewport
|
||||
WaitSelector string // CSS selector to wait for before capturing (e.g. "section")
|
||||
Timeout time.Duration // overall navigation+capture timeout
|
||||
Mode string // "" (site default), "light" or "dark" — sets the bn-theme cookie + emulated prefers-color-scheme before navigation
|
||||
}
|
||||
|
||||
// PreviewURL composes the gallery-page URL with the render-only override.
|
||||
@ -67,8 +70,25 @@ func Capture(ctx context.Context, opts Options) ([]byte, error) {
|
||||
var buf []byte
|
||||
tasks := chromedp.Tasks{
|
||||
chromedp.EmulateViewport(int64(opts.Width), int64(opts.Height)),
|
||||
chromedp.Navigate(opts.URL),
|
||||
}
|
||||
if opts.Mode == "light" || opts.Mode == "dark" {
|
||||
u, err := url.Parse(opts.URL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse %s: %w", opts.URL, err)
|
||||
}
|
||||
host := u.Hostname()
|
||||
mode := opts.Mode
|
||||
tasks = append(tasks,
|
||||
chromedp.ActionFunc(func(ctx context.Context) error {
|
||||
return network.SetCookie("bn-theme", mode).
|
||||
WithDomain(host).WithPath("/").Do(ctx)
|
||||
}),
|
||||
emulation.SetEmulatedMedia().WithFeatures([]*emulation.MediaFeature{
|
||||
{Name: "prefers-color-scheme", Value: mode},
|
||||
}),
|
||||
)
|
||||
}
|
||||
tasks = append(tasks, chromedp.Navigate(opts.URL))
|
||||
if opts.WaitSelector != "" {
|
||||
tasks = append(tasks, chromedp.WaitVisible(opts.WaitSelector, chromedp.ByQuery))
|
||||
} else {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user