Documentation
¶
Overview ¶
Package devconsole serves the pw dev web console: one loopback listener holding an index and every pane.
The console is host-side tooling. It reads the project tree and what pw itself started, and it never asks the application process for anything, so a pane keeps answering while the application is stopped — which is most of the time between two working states.
Index ¶
- Constants
- func AssetPane(source AssetSource) http.Handler
- func TextPane(title, summary string, run func(context.Context) (string, error)) http.Handler
- type AssetSource
- type Attachment
- type Console
- func (c *Console) CanReseed() bool
- func (c *Console) Close()
- func (c *Console) Failed(phase string, text string)
- func (c *Console) Publish(phase string, status Status, diagnostic *Diagnostic)
- func (c *Console) SetReseed(action func(context.Context) error)
- func (c *Console) State() State
- func (c *Console) URL() string
- type Diagnostic
- type Pane
- type Project
- type State
- type Status
Constants ¶
const PanePrefixHeader = "X-Pw-Pane-Prefix"
PanePrefixHeader tells a pane where it is mounted.
A pane serves itself and cannot know: the console strips its prefix before the request arrives, so a link the pane writes as an absolute path resolves against the console root and misses. Rather than have every pane guess, the mount says so, and a pane that sees this header prefixes its own links and offers the way back to the console.
Variables ¶
This section is empty.
Functions ¶
func AssetPane ¶
func AssetPane(source AssetSource) http.Handler
AssetPane builds the static asset pane over source.
func TextPane ¶
TextPane shows the output of a pw subcommand, unaltered.
It exists because policy:dev-console-boundary admits an action only where a subcommand already offers it, and the most faithful way to honour that is to run the command and show what it said. A second rendering of the same report would be a second thing to keep true, and it would disagree with the terminal the first time either changed.
The output keeps its own layout, which is what the command spent its effort on: api:cli-doctor orders findings by severity and names a remedy for each, and reflowing that into a table would lose the ordering it means.
Types ¶
type AssetSource ¶
type AssetSource struct {
// Root is the project root; public/ and the Tailwind paths resolve from it.
Root string
// Mount is the configured public mount, or empty when pw could not
// determine it. An undetermined mount is reported as undetermined rather
// than assumed, because a wrong URL here is worse than no URL.
Mount string
// TailwindEnabled, TailwindInput, and TailwindOutput mirror the project
// configuration. Input and Output are project-relative slash paths.
TailwindEnabled bool
TailwindInput string
TailwindOutput string
// Compressible decides precompression eligibility. It is injected rather
// than reimplemented so that the pane and the build agree by construction:
// a pane that guessed differently from api:cli-build would be worse than
// no pane.
Compressible func(path string) bool
}
AssetSource is what the asset pane needs to know about the project. The caller resolves it, because locating a project root and reading its configuration is pw's job and not this package's.
type Attachment ¶
type Attachment struct {
// contains filtered or unexported fields
}
Attachment is what the application published about itself — the address of a pane it serves and the URL it listens on — plus the per-run token that guards both.
The application dials out to announce; the console never dials in. That is what keeps a development pane off the application's own listener while still letting one page reach it, and it is the direction the telemetry exporter already uses.
func NewAttachment ¶
func NewAttachment(token string) *Attachment
NewAttachment prepares one. An empty token accepts no announcement, which is what a loop that generated none should do.
func (*Attachment) Address ¶
func (a *Attachment) Address() string
Address is where the application says it is listening, or empty before it has said so.
func (*Attachment) Handler ¶
func (a *Attachment) Handler(what string) http.Handler
Handler proxies a pane to the attached application.
The pane exists before the application does and outlives every restart, so this is an indirection over an address filled in later. A pane whose application is down says so rather than disappearing from the console.
func (*Attachment) Listening ¶
func (a *Attachment) Listening() string
Listening is the URL the application says it bound, or empty before it has said so.
type Console ¶
type Console struct {
// contains filtered or unexported fields
}
Console is the listener, the panes mounted on it, and the current loop state.
func New ¶
New binds the console listener and serves the index and every pane on it.
The address is fixed by configuration rather than reserved, so a bound port is a real failure with a real remedy rather than something to route around.
func (*Console) CanReseed ¶
CanReseed reports whether the action is available, for the index to decide whether to offer it.
func (*Console) Close ¶
func (c *Console) Close()
Close stops the console with the developer loop. Nothing here is persisted, so there is nothing to flush.
func (*Console) Publish ¶
func (c *Console) Publish(phase string, status Status, diagnostic *Diagnostic)
Publish records a loop transition. It is safe on a nil console, because a console that failed to listen must not turn every phase change into a branch at the call site.
func (*Console) SetReseed ¶
SetReseed installs the action behind the index's reseed button. Nothing is offered until it is set, so a project with no datasets shows no button.
type Diagnostic ¶
type Diagnostic struct {
Text string `json:"text"`
// File, Line, and Column are set only when the diagnostic named a
// location. Zero values mean it did not, not that it pointed at line zero.
File string `json:"file,omitempty"`
Line int `json:"line,omitempty"`
Column int `json:"column,omitempty"`
}
Diagnostic carries a failure exactly as the terminal received it. The text is never reformatted: a rewrapped compiler error is harder to read than the original, and the developer is comparing the two.
type Pane ¶
type Pane struct {
// Slug is the first path segment the pane is mounted under, and the id the
// index links to.
Slug string
Title string
Summary string
// Handler serves the pane below its slug, with the slug already stripped.
// A nil handler means the pane is disabled.
Handler http.Handler
// DisabledBy names the configuration key that would enable a disabled
// pane. The index says this rather than hiding the pane, so a developer
// who expected a surface learns why it is not there.
DisabledBy string
// Framed wraps the pane in a page carrying the console navigation, with the
// pane itself in an iframe.
//
// A pane the console renders can carry the nav directly, and does. A pane it
// does not render cannot: the telemetry viewer is a browser application with
// its own document, so navigating to it left the developer inside a page
// with no way back. A frame is what puts the nav above a document the
// console does not own, and it is worth the sizing it costs only for that
// case.
Framed bool
// RootPaths are absolute console paths this pane owns outside its own
// subtree.
//
// This exists for one reason. The telemetry UI is a committed build whose
// bundle resolves its API against the document origin rather than against
// its own base, so mounting the page under a prefix does not move its
// fetches with it. Teaching it otherwise means rebuilding the bundle,
// which needs the Node toolchain the embedded build exists to avoid.
RootPaths []string
}
Pane is one surface on the console. A pane is a page rather than a region of a shared document: an embedded third-party renderer ships CSS written to own a document, and giving it one costs less than isolating it inside a shell.
type Project ¶
type Project struct {
Name string
// Environment is the APP_ENV the loop runs the application under.
Environment string
// ApplicationURL is where the application listens, or empty when pw could
// not determine it. The index reports an undetermined value as
// undetermined rather than printing a default that may be wrong.
ApplicationURL string
// APIDocURL is the documentation UI the application already serves, at the
// path its configuration puts it. The console links it rather than
// rendering the specification itself: a second renderer would be a second
// thing to keep current with the same document, and a link cannot disagree
// with what the application serves.
//
// Empty means the endpoint is off, or that its path could not be resolved.
APIDocURL string
// APIDocKey names the configuration key that turns the endpoint on, for the
// index to quote when there is no URL to link.
APIDocKey string
}
Project is what the index says about the project itself.
type State ¶
type State struct {
// Build changes whenever the served application changes, so a page can
// tell whether it is looking at a stale one. It is opaque; nothing reads
// it for meaning.
Build string `json:"build"`
// Phase names the loop phase in progress or last completed, using the
// words the terminal progress region already prints.
Phase string `json:"phase"`
Status Status `json:"status"`
Diagnostic *Diagnostic `json:"diagnostic,omitempty"`
Since time.Time `json:"since"`
}
State is the one record the loop publishes on every phase transition. There is exactly one current State and no history: what the loop did earlier is the terminal scrollback and the telemetry pane, and a second copy of it here would be a second thing to keep true.
The record holds no configuration value, no environment variable, and no path from outside the project.
type Status ¶
type Status string
Status is where the developer loop currently stands.
const ( // StatusStarting covers every phase before the application is up. It is // not a failure, and a page waiting on one should say so rather than // showing an error. StatusStarting Status = "starting" // StatusHealthy means the application process is running. It does not // promise that a request to it would succeed. StatusHealthy Status = "healthy" StatusFailed Status = "failed" )