Compare commits
No commits in common. "098a2091b9ff8e1faa8b4251b15e37361d1389ca" and "b3d86b1489da382e7fe4360626ab6d55ddcac4e1" have entirely different histories.
098a2091b9
...
b3d86b1489
@ -53,7 +53,7 @@ artifact and never compile.
|
|||||||
|
|
||||||
Global `--host` flag: orchestrator base URL. **Default is
|
Global `--host` flag: orchestrator base URL. **Default is
|
||||||
`https://my.blockninjacms.com` — PRODUCTION.** For local dev always pass
|
`https://my.blockninjacms.com` — PRODUCTION.** For local dev always pass
|
||||||
`--host https://my.blockninja.dev` (or set it as the active host via
|
`--host https://my.localdev.blockninjacms.com` (or set it as the active host via
|
||||||
`ninja login`). Never publish dev/test plugins to the prod registry by omitting
|
`ninja login`). Never publish dev/test plugins to the prod registry by omitting
|
||||||
`--host`.
|
`--host`.
|
||||||
|
|
||||||
|
|||||||
@ -23,11 +23,9 @@ func newThemeCmd() *cobra.Command {
|
|||||||
|
|
||||||
func newThemeScreenshotCmd() *cobra.Command {
|
func newThemeScreenshotCmd() *cobra.Command {
|
||||||
var gallery, slug, out, mobileOut, themeName, waitSelector string
|
var gallery, slug, out, mobileOut, themeName, waitSelector string
|
||||||
var pages, screenshotsDir, modes string
|
var pages, screenshotsDir string
|
||||||
var mobile bool
|
var mobile bool
|
||||||
var width, height int
|
var width, height int
|
||||||
var scale float64
|
|
||||||
var settle time.Duration
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "screenshot",
|
Use: "screenshot",
|
||||||
Short: "Render this theme's showcase page and write preview.png into the repo",
|
Short: "Render this theme's showcase page and write preview.png into the repo",
|
||||||
@ -57,7 +55,7 @@ the rest of the CLI.`,
|
|||||||
themeName = mod.Plugin.Name
|
themeName = mod.Plugin.Name
|
||||||
}
|
}
|
||||||
if gallery == "" {
|
if gallery == "" {
|
||||||
return fmt.Errorf("--gallery is required (the gallery CMS site base, e.g. https://showcase.blockninja.dev)")
|
return fmt.Errorf("--gallery is required (the gallery CMS site base, e.g. https://showcase.localdev.blockninjacms.com)")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
@ -70,8 +68,6 @@ the rest of the CLI.`,
|
|||||||
Height: height,
|
Height: height,
|
||||||
WaitSelector: waitSelector,
|
WaitSelector: waitSelector,
|
||||||
Timeout: 45 * time.Second,
|
Timeout: 45 * time.Second,
|
||||||
Scale: scale,
|
|
||||||
Settle: settle,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -90,8 +86,6 @@ the rest of the CLI.`,
|
|||||||
Height: 844,
|
Height: 844,
|
||||||
WaitSelector: waitSelector,
|
WaitSelector: waitSelector,
|
||||||
Timeout: 45 * time.Second,
|
Timeout: 45 * time.Second,
|
||||||
Scale: scale,
|
|
||||||
Settle: settle,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -107,48 +101,33 @@ the rest of the CLI.`,
|
|||||||
// files the .bnp packer ships under screenshots/ and the registry
|
// files the .bnp packer ships under screenshots/ and the registry
|
||||||
// ingests into the plugin gallery at publish.
|
// ingests into the plugin gallery at publish.
|
||||||
if pages != "" {
|
if pages != "" {
|
||||||
modeList, err := parseModes(modes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := os.MkdirAll(screenshotsDir, 0o755); err != nil {
|
if err := os.MkdirAll(screenshotsDir, 0o755); err != nil {
|
||||||
return fmt.Errorf("create %s: %w", screenshotsDir, err)
|
return fmt.Errorf("create %s: %w", screenshotsDir, err)
|
||||||
}
|
}
|
||||||
n := 0
|
for i, pageSlug := range strings.Split(pages, ",") {
|
||||||
for _, pageSlug := range strings.Split(pages, ",") {
|
|
||||||
pageSlug = strings.TrimSpace(pageSlug)
|
pageSlug = strings.TrimSpace(pageSlug)
|
||||||
if pageSlug == "" {
|
if pageSlug == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, mode := range modeList {
|
|
||||||
pURL := shot.PreviewURL(gallery, pageSlug, themeName)
|
pURL := shot.PreviewURL(gallery, pageSlug, themeName)
|
||||||
fmt.Fprintf(os.Stderr, "capturing page %s (%s): %s\n", pageSlug, modeLabel(mode), pURL)
|
fmt.Fprintf(os.Stderr, "capturing page %s: %s\n", pageSlug, pURL)
|
||||||
ppng, err := shot.Capture(ctx, shot.Options{
|
ppng, err := shot.Capture(ctx, shot.Options{
|
||||||
URL: pURL,
|
URL: pURL,
|
||||||
Width: width,
|
Width: width,
|
||||||
Height: height,
|
Height: height,
|
||||||
WaitSelector: waitSelector,
|
WaitSelector: waitSelector,
|
||||||
Timeout: 45 * time.Second,
|
Timeout: 45 * time.Second,
|
||||||
Mode: mode,
|
|
||||||
Scale: scale,
|
|
||||||
Settle: settle,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
n++
|
dest := filepath.Join(screenshotsDir, fmt.Sprintf("%02d-%s.png", i+1, slugFileName(pageSlug)))
|
||||||
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 {
|
if err := os.WriteFile(dest, ppng, 0o644); err != nil {
|
||||||
return fmt.Errorf("write %s: %w", dest, err)
|
return fmt.Errorf("write %s: %w", dest, err)
|
||||||
}
|
}
|
||||||
fmt.Printf("wrote %s (%d bytes)\n", dest, len(ppng))
|
fmt.Printf("wrote %s (%d bytes)\n", dest, len(ppng))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -163,36 +142,9 @@ the rest of the CLI.`,
|
|||||||
cmd.Flags().IntVar(&height, "height", 900, "Desktop viewport height")
|
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(&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(&screenshotsDir, "screenshots-dir", "screenshots", "Directory for --pages captures (packed into the .bnp)")
|
||||||
cmd.Flags().DurationVar(&settle, "settle", 0, "Extra wait after --wait matches before capturing (e.g. 1500ms; lets reveal transitions and canvas animations finish)")
|
|
||||||
cmd.Flags().Float64Var(&scale, "scale", 1, "Device scale factor (2 = retina PNGs at Width*2 x Height*2)")
|
|
||||||
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
|
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",
|
// slugFileName turns a page slug into a filename fragment ("/" → "home",
|
||||||
// "/blog/post" → "blog-post").
|
// "/blog/post" → "blog-post").
|
||||||
func slugFileName(slug string) string {
|
func slugFileName(slug string) string {
|
||||||
|
|||||||
@ -11,8 +11,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/chromedp/cdproto/emulation"
|
|
||||||
"github.com/chromedp/cdproto/network"
|
|
||||||
"github.com/chromedp/chromedp"
|
"github.com/chromedp/chromedp"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,9 +22,6 @@ type Options struct {
|
|||||||
FullPage bool // capture the full scroll height, not just the viewport
|
FullPage bool // capture the full scroll height, not just the viewport
|
||||||
WaitSelector string // CSS selector to wait for before capturing (e.g. "section")
|
WaitSelector string // CSS selector to wait for before capturing (e.g. "section")
|
||||||
Timeout time.Duration // overall navigation+capture timeout
|
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
|
|
||||||
Scale float64 // device scale factor; 0 = 1.0. 2 captures retina PNGs (Width*2 × Height*2 px)
|
|
||||||
Settle time.Duration // extra wait after WaitSelector before capturing — lets scroll-reveal transitions and canvas animations finish (0 = capture immediately)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PreviewURL composes the gallery-page URL with the render-only override.
|
// PreviewURL composes the gallery-page URL with the render-only override.
|
||||||
@ -70,39 +65,15 @@ func Capture(ctx context.Context, opts Options) ([]byte, error) {
|
|||||||
defer cancelTimeout()
|
defer cancelTimeout()
|
||||||
|
|
||||||
var buf []byte
|
var buf []byte
|
||||||
viewport := []chromedp.EmulateViewportOption(nil)
|
|
||||||
if opts.Scale > 0 {
|
|
||||||
viewport = append(viewport, chromedp.EmulateScale(opts.Scale))
|
|
||||||
}
|
|
||||||
tasks := chromedp.Tasks{
|
tasks := chromedp.Tasks{
|
||||||
chromedp.EmulateViewport(int64(opts.Width), int64(opts.Height), viewport...),
|
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 != "" {
|
if opts.WaitSelector != "" {
|
||||||
tasks = append(tasks, chromedp.WaitVisible(opts.WaitSelector, chromedp.ByQuery))
|
tasks = append(tasks, chromedp.WaitVisible(opts.WaitSelector, chromedp.ByQuery))
|
||||||
} else {
|
} else {
|
||||||
tasks = append(tasks, chromedp.WaitReady("body", chromedp.ByQuery))
|
tasks = append(tasks, chromedp.WaitReady("body", chromedp.ByQuery))
|
||||||
}
|
}
|
||||||
if opts.Settle > 0 {
|
|
||||||
tasks = append(tasks, chromedp.Sleep(opts.Settle))
|
|
||||||
}
|
|
||||||
if opts.FullPage {
|
if opts.FullPage {
|
||||||
tasks = append(tasks, chromedp.FullScreenshot(&buf, 90))
|
tasks = append(tasks, chromedp.FullScreenshot(&buf, 90))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -3,16 +3,16 @@ package shot
|
|||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
func TestPreviewURLComposesQuery(t *testing.T) {
|
func TestPreviewURLComposesQuery(t *testing.T) {
|
||||||
got := PreviewURL("https://showcase.blockninja.dev", "/", "gotham")
|
got := PreviewURL("https://showcase.localdev.blockninjacms.com", "/", "gotham")
|
||||||
want := "https://showcase.blockninja.dev/?preview_template=gotham"
|
want := "https://showcase.localdev.blockninjacms.com/?preview_template=gotham"
|
||||||
if got != want {
|
if got != want {
|
||||||
t.Fatalf("PreviewURL = %q, want %q", got, want)
|
t.Fatalf("PreviewURL = %q, want %q", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPreviewURLTrimsTrailingSlashHost(t *testing.T) {
|
func TestPreviewURLTrimsTrailingSlashHost(t *testing.T) {
|
||||||
got := PreviewURL("https://showcase.blockninja.dev/", "/", "noir")
|
got := PreviewURL("https://showcase.localdev.blockninjacms.com/", "/", "noir")
|
||||||
want := "https://showcase.blockninja.dev/?preview_template=noir"
|
want := "https://showcase.localdev.blockninjacms.com/?preview_template=noir"
|
||||||
if got != want {
|
if got != want {
|
||||||
t.Fatalf("PreviewURL = %q, want %q", got, want)
|
t.Fatalf("PreviewURL = %q, want %q", got, want)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user