Skip to content

Commit 54e45c7

Browse files
authored
Feat/publish readiness preflight (#26)
* feat(publish): add Webflow readiness preflight * feat(publish): support explicit Webflow targets * feat(docs): auto-create missing docs pages in Webflow * feat(pages): add page management and typed page generation * feat(scaffold): add typed Webflow selector contracts * feat(pages): add page inspect and delete commands * feat(tui): add interactive page management * chore: release v1.3.4
1 parent 4d75623 commit 54e45c7

33 files changed

Lines changed: 2161 additions & 46 deletions

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ How it works:
115115
- `src/features/*` is for reusable behaviors that can be mounted from global or page entries
116116
- `src/global/modules/*` contains reusable global behaviors that you choose to import from the global entry
117117
- `src/pages/*/index.ts` is for per-page code
118+
- `src/generated/wfkit-pages.ts` contains generated page slug types for `definePage(...)`
118119
- `src/utils/dom.ts` contains low-level DOM helpers
119120
- `src/utils/webflow.ts` contains DRY mount helpers that align page and feature bootstrapping with the Webflow runtime
120121
- `docs/index.md` is the default markdown entry for the docs hub page
@@ -137,6 +138,7 @@ wfkit docs
137138
```
138139

139140
By default this targets the Webflow page with slug `docs`.
141+
If that page does not exist yet, `wfkit docs` now creates it automatically before syncing the managed docs block.
140142

141143
The command injects a managed docs block into that page's custom code and mounts the rendered content into:
142144

@@ -169,6 +171,12 @@ Then:
169171
bun run dev
170172
```
171173

174+
When Webflow pages are added or renamed, refresh typed page names in the scaffold with:
175+
176+
```bash
177+
bun run pages:types
178+
```
179+
172180
This starts:
173181

174182
- Vite on `http://localhost:5173`
@@ -408,6 +416,18 @@ Options:
408416
- `--publish` Publish the site after updating the docs page
409417
- `--notify` Show a desktop notification and play a sound when finished
410418

419+
### `wfkit pages`
420+
421+
Inspect and manage Webflow pages without opening the Designer.
422+
423+
Subcommands:
424+
425+
- `wfkit pages list` List static Webflow pages
426+
- `wfkit pages create --name "Docs" --slug docs` Create a static Webflow page
427+
- `wfkit pages inspect --slug docs` Inspect one page and its managed custom code blocks
428+
- `wfkit pages delete --slug docs --yes` Delete one static Webflow page
429+
- `wfkit pages types` Generate `src/generated/wfkit-pages.ts` from the current Webflow pages
430+
411431
### `wfkit migrate`
412432

413433
Migrate inline Webflow custom code into local source files.

cmd/wfkit/app_commands.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import "github.com/urfave/cli/v2"
55
func buildCommands() []*cli.Command {
66
return []*cli.Command{
77
buildInitCommand(),
8+
buildPagesCommand(),
89
buildDocsCommand(),
910
buildMigrateCommand(),
1011
buildPublishCommand(),
@@ -16,6 +17,61 @@ func buildCommands() []*cli.Command {
1617
}
1718
}
1819

20+
func buildPagesCommand() *cli.Command {
21+
return &cli.Command{
22+
Name: "pages",
23+
Usage: "Inspect, create, and type Webflow pages",
24+
Subcommands: []*cli.Command{
25+
{
26+
Name: "list",
27+
Usage: "List static Webflow pages for the current site",
28+
Flags: []cli.Flag{&cli.BoolFlag{Name: "json", Usage: "Print pages as JSON"}},
29+
Action: pagesListMode,
30+
},
31+
{
32+
Name: "create",
33+
Usage: "Create a new static Webflow page",
34+
Flags: []cli.Flag{
35+
&cli.StringFlag{Name: "name", Usage: "Page title to create"},
36+
&cli.StringFlag{Name: "slug", Usage: "Page slug (defaults to a slugified title)"},
37+
&cli.BoolFlag{Name: "json", Usage: "Print the created page as JSON"},
38+
&cli.BoolFlag{Name: "types", Value: true, Usage: "Regenerate local page types after creating the page"},
39+
&cli.StringFlag{Name: "output", Value: "src/generated/wfkit-pages.ts", Usage: "Output path for generated page types when --types is enabled"},
40+
},
41+
Action: pagesCreateMode,
42+
},
43+
{
44+
Name: "types",
45+
Usage: "Generate typed page names from the current Webflow site",
46+
Flags: []cli.Flag{
47+
&cli.StringFlag{Name: "output", Value: "src/generated/wfkit-pages.ts", Usage: "Output path for generated page types"},
48+
},
49+
Action: pagesTypesMode,
50+
},
51+
{
52+
Name: "inspect",
53+
Usage: "Inspect one Webflow page by slug or page id",
54+
Flags: []cli.Flag{
55+
&cli.StringFlag{Name: "slug", Usage: "Page slug to inspect"},
56+
&cli.StringFlag{Name: "id", Usage: "Page id to inspect"},
57+
&cli.BoolFlag{Name: "json", Usage: "Print the page as JSON"},
58+
},
59+
Action: pagesInspectMode,
60+
},
61+
{
62+
Name: "delete",
63+
Usage: "Delete one Webflow page by slug or page id",
64+
Flags: []cli.Flag{
65+
&cli.StringFlag{Name: "slug", Usage: "Page slug to delete"},
66+
&cli.StringFlag{Name: "id", Usage: "Page id to delete"},
67+
&cli.BoolFlag{Name: "yes", Usage: "Delete without an extra safety prompt"},
68+
},
69+
Action: pagesDeleteMode,
70+
},
71+
},
72+
}
73+
}
74+
1975
func buildInitCommand() *cli.Command {
2076
return &cli.Command{
2177
Name: "init",
@@ -61,6 +117,7 @@ func buildMigrateCommand() *cli.Command {
61117
&cli.BoolFlag{Name: "publish", Usage: "After writing local files, build assets, push the artifact branch, and update Webflow"},
62118
&cli.StringFlag{Name: "custom-commit", Value: "Migrate Webflow page code via wfkit", Usage: "Custom commit message"},
63119
&cli.StringFlag{Name: "delivery", Value: "cdn", Usage: "Delivery mode for --publish: cdn or inline"},
120+
&cli.StringFlag{Name: "target", Value: "staging", Usage: "Webflow publish targets for --publish: staging, production, or all"},
64121
&cli.StringFlag{Name: "asset-branch", Value: "wfkit-dist", Usage: "Git branch used for published build artifacts and jsDelivr URLs"},
65122
&cli.StringFlag{Name: "branch", Hidden: true, Usage: "Deprecated alias for --asset-branch"},
66123
&cli.StringFlag{Name: "build-dir", Value: "dist/assets", Usage: "Build directory"},
@@ -83,6 +140,7 @@ func buildPublishCommand() *cli.Command {
83140
&cli.StringFlag{Name: "dev-host", Value: "localhost", Usage: "Local dev server host (dev mode)"},
84141
&cli.StringFlag{Name: "custom-commit", Value: "Auto publish from wfkit tool", Usage: "Custom commit message"},
85142
&cli.StringFlag{Name: "delivery", Value: "cdn", Usage: "Production delivery mode: cdn or inline"},
143+
&cli.StringFlag{Name: "target", Value: "staging", Usage: "Webflow publish targets: staging, production, or all"},
86144
&cli.StringFlag{Name: "asset-branch", Value: "wfkit-dist", Usage: "Git branch used for published build artifacts and jsDelivr URLs"},
87145
&cli.StringFlag{Name: "branch", Hidden: true, Usage: "Deprecated alias for --asset-branch"},
88146
&cli.StringFlag{Name: "build-dir", Value: "dist/assets", Usage: "Build directory"},

cmd/wfkit/docs_flow.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ func (f *docsFlow) printSuccess() {
157157
"Markdown documentation has been published to the Webflow docs page.",
158158
[]utils.SummaryMetric{
159159
{Label: "Page", Value: f.options.PageSlug, Tone: "success"},
160+
{Label: "Created", Value: map[bool]string{true: "yes", false: "no"}[f.result.Created], Tone: "info"},
160161
{Label: "Published", Value: map[bool]string{true: "yes", false: "no"}[f.result.Published], Tone: "info"},
161162
},
162163
"git status",

cmd/wfkit/docs_report.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ func printDocsTimeline(authed, planned, applied bool) {
1111
utils.PrintTimeline(
1212
"Docs Timeline",
1313
utils.TimelineStep{Label: "Authenticate", Status: timelineStatus(authed, false), Details: timelineDetails(authed, "Webflow session ready")},
14-
utils.TimelineStep{Label: "Plan docs hub", Status: timelineStatus(planned, false), Details: timelineDetails(planned, "markdown rendered and target page resolved")},
15-
utils.TimelineStep{Label: "Apply docs hub", Status: timelineStatus(applied, false), Details: timelineDetails(applied, "managed docs block updated")},
14+
utils.TimelineStep{Label: "Plan docs hub", Status: timelineStatus(planned, false), Details: timelineDetails(planned, "markdown rendered and target page prepared")},
15+
utils.TimelineStep{Label: "Apply docs hub", Status: timelineStatus(applied, false), Details: timelineDetails(applied, "page created or docs block updated")},
1616
)
1717
}
1818

@@ -29,6 +29,7 @@ func printDocsPlan(plan publish.DocsHubPlan) {
2929
func printDocsResult(result publish.DocsHubResult) {
3030
utils.PrintSection("Docs Result")
3131
utils.PrintSummary(
32+
utils.SummaryMetric{Label: "Created", Value: map[bool]string{true: "yes", false: "no"}[result.Created], Tone: "info"},
3233
utils.SummaryMetric{Label: "Updated", Value: map[bool]string{true: "yes", false: "no"}[result.Updated], Tone: "success"},
3334
utils.SummaryMetric{Label: "Published", Value: map[bool]string{true: "yes", false: "no"}[result.Published], Tone: "info"},
3435
)
@@ -37,6 +38,8 @@ func printDocsResult(result publish.DocsHubResult) {
3738

3839
func docsStatus(action string) string {
3940
switch action {
41+
case "create":
42+
return "CREATE"
4043
case "update":
4144
return "UPDATE"
4245
case "up_to_date":

cmd/wfkit/doctor.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,14 @@ func doctorDashboardCards(checks []doctorCheck) []utils.DashboardCard {
8282
{key: "project", title: "Project"},
8383
{key: "tooling", title: "Tooling"},
8484
{key: "runtime", title: "Runtime"},
85+
{key: "publish", title: "Publish"},
8586
}
8687

8788
stats := map[string]*aggregate{
8889
"project": {},
8990
"tooling": {},
9091
"runtime": {},
92+
"publish": {},
9193
}
9294

9395
for _, check := range checks {

cmd/wfkit/doctor_flow.go

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66

77
"wfkit/internal/config"
8+
"wfkit/internal/webflow"
89
)
910

1011
type doctorFlow struct {
@@ -66,10 +67,40 @@ func (f *doctorFlow) collectChecks() {
6667
Status: doctorWarn,
6768
Message: "skipped by flag",
6869
})
70+
f.checks = append(f.checks, doctorCheck{
71+
Category: "publish",
72+
Name: "Publish readiness",
73+
Status: doctorWarn,
74+
Message: "skipped because auth checks are disabled",
75+
})
76+
return
77+
}
78+
79+
authCheck, token, cookies := f.checkWebflowSession()
80+
f.checks = append(f.checks, authCheck)
81+
82+
if authCheck.Status != doctorPass {
83+
f.checks = append(f.checks, doctorCheck{
84+
Category: "publish",
85+
Name: "Publish readiness",
86+
Status: doctorWarn,
87+
Message: "skipped because Webflow authentication is not ready",
88+
})
89+
return
90+
}
91+
92+
preflight, err := webflow.GetPublishPreflight(f.context, f.config.AppName, token, cookies)
93+
if err != nil {
94+
f.checks = append(f.checks, doctorCheck{
95+
Category: "publish",
96+
Name: "Publish readiness",
97+
Status: doctorWarn,
98+
Message: err.Error(),
99+
})
69100
return
70101
}
71102

72-
f.checks = append(f.checks, checkWebflowAuth(f.context, f.config))
103+
f.checks = append(f.checks, doctorChecksFromPublishPreflight(preflight)...)
73104
}
74105

75106
func (f *doctorFlow) hasBlockingIssues() bool {
@@ -81,3 +112,22 @@ func (f *doctorFlow) hasBlockingIssues() bool {
81112

82113
return false
83114
}
115+
116+
func (f *doctorFlow) checkWebflowSession() (doctorCheck, string, string) {
117+
designURL := f.config.EffectiveDesignURL()
118+
if designURL == "" {
119+
return doctorCheck{Category: "runtime", Name: "Webflow auth", Status: doctorFail, Message: "cannot build design URL without appName"}, "", ""
120+
}
121+
122+
token, cookies, err := webflow.GetCsrfTokenAndCookies(f.context, designURL)
123+
if err != nil {
124+
return doctorCheck{Category: "runtime", Name: "Webflow auth", Status: doctorWarn, Message: err.Error()}, "", ""
125+
}
126+
127+
return doctorCheck{
128+
Category: "runtime",
129+
Name: "Webflow auth",
130+
Status: doctorPass,
131+
Message: fmt.Sprintf("authenticated against %s", designURL),
132+
}, token, cookies
133+
}

cmd/wfkit/interactive_flow.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ func (f *interactiveFlow) dispatch() error {
6464
return initMode(f.cliContext)
6565
case "docs":
6666
return docsMode(f.cliContext)
67+
case "pages":
68+
return newInteractivePagesFlow(f.cliContext).run()
6769
case "migrate":
6870
return migrateMode(f.cliContext)
6971
case "publish_prod":
@@ -115,6 +117,7 @@ func interactiveActionOptions() []huh.Option[string] {
115117
return []huh.Option[string]{
116118
huh.NewOption("Initialize a project", "init"),
117119
huh.NewOption("Publish docs", "docs"),
120+
huh.NewOption("Manage pages", "pages"),
118121
huh.NewOption("Migrate page code", "migrate"),
119122
huh.NewOption("Publish code to Webflow (prod)", "publish_prod"),
120123
huh.NewOption("Start dev proxy", "proxy_dev"),

0 commit comments

Comments
 (0)