deploy

package
v0.25.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 10, 2026 License: Apache-2.0 Imports: 23 Imported by: 0

Documentation

Overview

Cloudflare deploy adapter: ship a project to Cloudflare Pages (static output) or Cloudflare Workers (server-side code).

It implements the DeployTarget contract in deploy.go against Cloudflare's REST API. The adapter is deliberately split in two: Pages is the fully implemented path (deploy, history, status, per-deployment preview URLs, and rollback), while Workers covers the single-deployment shape a server-side starter needs. Everything flows through request, config, and client seams so tests drive a local HTTP fake with no account and no network.

Cloudflare Pages adapter: the fully implemented path, for a starter whose build output is static files.

A Pages project hosts a list of deployments. Deploy uploads the built directory as a direct-upload deployment and records it; List reads the project's deployment history; Status reads one deployment's lifecycle state; PreviewURL returns a preview deployment's per-deployment address (the deployment's own pages.dev host); Rollback re-points the project at a prior deployment and marks the superseded one rolled back.

All state lives in the Cloudflare account, keyed by the account id and the project name from CloudflareConfig. The adapter keeps no local history: a fresh process sees exactly what the account holds.

Scope note: the request shapes below (the deployment-create body, the content-addressed asset upload, and the rollback call) are this adapter's protocol against the Cloudflare REST API and are exercised only against the local HTTP fake — no live account was available when they were written. They must be reconciled with a captured real response before this path is treated as verified against Cloudflare.

Cloudflare adapter selection: pick the Pages or Workers adapter for a project from the deploy target its starter manifest declares.

A Cloudflare deploy is one of two shapes — static output on Pages or server-side code on Workers — and the choice lives in the starter manifest's deploy_target field (absent means "pages"). This is a small, pure selector: it holds no I/O of its own, so the CLI and the deploy tools can share it and unit-test it without a network, an account, or a credential store.

Cloudflare Workers adapter: the server-side path, for a starter whose build output is a Worker script rather than static files.

Workers has a fundamentally different shape from Pages: a Worker has one live script per name, not a list of deployments, so there is no per- deployment history, no per-deployment preview address, and no multi-step rollback in the account. This adapter therefore implements Deploy (upload the script read from a file in the build output), Status, and List (a single-entry history for the worker), and reports ErrPreviewUnsupported for PreviewURL and ErrNoPreviousDeployment for Rollback — each an accurate statement of the target's capability rather than an invented one.

Deploy also brings the project's declared bindings with it. A Workers project declares its D1 databases, KV namespaces and R2 buckets in wrangler.toml; the adapter creates or reuses each one and attaches it to the uploaded script, and applies pending D1 migrations before the script goes live. The script upload — the step that makes the worker live — happens only after every binding is resolved, so a deploy never goes live with bindings missing.

Deployment history with rollback for server-side starters is future work (Cloudflare Workers versions/deployments); until then a Worker deploy is a single live script and the interface's history-dependent methods say so.

Wrangler configuration for the Cloudflare Workers adapter.

A Workers project declares the resources its script binds to (D1 databases, KV namespaces, R2 buckets) in wrangler.toml, not in the deploy config. The adapter reads that file from the project root so it can create or reuse each declared resource and attach it to the uploaded script; without it a server-side starter deploys with no database and its API fails at runtime.

Only the blocks the file actually declares matter: a commented-out KV or R2 block is not a binding, so the adapter never invents one. The file is parsed with the TOML decoder the module already depends on (via viper), so no new third-party surface is introduced.

Package deploy defines the deploy-target interface and the in-memory fake adapter used by tests: deploy, status, history (list), rollback, and per-deployment preview URLs.

The package is a pure contract: it holds the DeployTarget interface, the value types that cross it (Deployment, DeployRequest, DeploymentKind), and the build-and-upload orchestrator (build.go). It reaches for nothing outside the standard library and the shell-exec helper the build runner needs, so real adapters and consumers (CLI, agent tools, Ship mode) can depend on it without dragging in a network client. An adapter is a small package that implements the interface; the fake (fake.go) is one such adapter, for tests.

A build is described by DeployRequest. The build runs in the workspace (from the starter manifest's build command) and the adapter uploads the resulting output — it never rebuilds on the target. The request carries the build output directory. The build-and-upload orchestration lives in build.go; the first gate it applies is preview vs production — a production deploy is refused unless the caller supplies an explicit Confirmation (confirmation.go), while a preview may run automatically — followed by the "what was verified is what ships" verification gate; the interface and value types it drives live here.

Index

Constants

View Source
const (
	// DeployTargetPages selects the Cloudflare Pages adapter for static build
	// output. It is also the default when no target is named.
	DeployTargetPages = "pages"
	// DeployTargetWorkers selects the Cloudflare Workers adapter for
	// server-side build output.
	DeployTargetWorkers = "workers"
)

Deploy target ids accepted by CloudflareTargetFor.

View Source
const CloudflareAPITokenEnvVar = "CLOUDFLARE_API_TOKEN" //nolint:gosec // G101: an env var *name*, not a credential value

CloudflareAPITokenEnvVar is the conventional embedding-environment variable for a Cloudflare API token. It is only a fallback hint for callers building a DeployCredentialConfig; the adapter never reads the environment itself — it receives an already-resolved Credential.

View Source
const DefaultCloudflareAPIBaseURL = "https://api.cloudflare.com/client/v4"

DefaultCloudflareAPIBaseURL is the production Cloudflare API root. The adapter appends the versioned path and resource segments to it.

View Source
const DeployResourceStateName = "deploy-resources.json"

DeployResourceStateName is the on-disk file name of the deploy resource state, relative to the project's .sprout/ directory.

View Source
const WranglerFileName = "wrangler.toml"

WranglerFileName is the conventional Cloudflare Workers config file at the project root.

Variables

View Source
var ErrBindingUnavailable = errors.New("cloudflare: a declared binding could not be prepared")

ErrBindingUnavailable is the typed error a deploy fails with when a declared binding cannot be created or attached. It is returned before the script is uploaded, so a deploy never goes live with bindings missing.

View Source
var ErrBuildFailed = errors.New("deploy: build failed")

ErrBuildFailed is returned by BuildAndDeploy when the workspace build exits non-zero (or cannot be started). The failure is wrapped so the underlying cause is available through errors.Is/As; nothing is uploaded.

View Source
var ErrCloudflareRequestFailed = errors.New("cloudflare: API request failed")

ErrCloudflareRequestFailed is the typed error every non-2xx Cloudflare API response is wrapped in. Callers detect it with errors.Is and read the HTTP status, the API error codes and the sanitized messages through the *CloudflareRequestError.

View Source
var ErrMissingDeployCredential = errors.New("missing deploy credential")

ErrMissingDeployCredential is returned by ResolveCredential when neither the credential store nor the embedding environment has a token for the target. It is a typed, distinguishable "missing credential" error: callers detect it with errors.Is and can render actionable guidance without the resolver leaking any value.

View Source
var ErrNoBuildCommand = errors.New("deploy: no build command configured")

ErrNoBuildCommand is returned by BuildAndDeploy when the request carries no build command. The manifest's build command is the single source of the build; the orchestrator never guesses one.

View Source
var ErrNoDeployTarget = errors.New("deploy: no deploy target configured")

ErrNoDeployTarget is returned by BuildAndDeploy when no target is wired to the Deployer. A deploy with nowhere to go is a construction mistake, not a no-op.

View Source
var ErrNoPassingVerification = errors.New("deploy: no passing verification for the current tree")

ErrNoPassingVerification is returned by BuildAndDeploy when the supplied snapshot records no passing verification for the tree (a nil verification result, or one that failed). Nothing is built or uploaded.

View Source
var ErrNoPreviousDeployment = errors.New("no previous deployment to roll back to")

ErrNoPreviousDeployment is returned by Rollback when the target has no earlier deployment to revert to: the deployment being rolled back is the only one recorded (or the list is empty).

View Source
var ErrPreviewUnsupported = errors.New("target does not support preview URLs")

ErrPreviewUnsupported is returned by PreviewURL when the target does not offer a per-deployment preview URL (a target that supports per-deployment previews is one kind of deploy target; a target that only ever serves production returns this instead of inventing a URL).

View Source
var ErrProductionNeedsConfirmation = errors.New("deploy: production deploy requires explicit user confirmation")

ErrProductionNeedsConfirmation is returned when a production deploy is requested without an explicit confirmation. It is a typed refusal: the caller detects it with errors.Is and can render the confirmation prompt without the deploy path having built, fingerprinted, or uploaded anything.

View Source
var ErrTreeChanged = errors.New("deploy: tree changed since verification")

ErrTreeChanged is returned by BuildAndDeploy when the project tree's fingerprint no longer matches the one recorded at verification time. What was verified is what ships: a tree that moved since (or during) the build is refused, and nothing is uploaded.

View Source
var ErrUnknownDeployment = errors.New("unknown deployment")

ErrUnknownDeployment is returned by Status, Rollback, and PreviewURL when the deployment is not one the target knows about (an unknown id, or a Deployment that was never deployed by this target).

Functions

func DefaultBuildRunner

func DefaultBuildRunner(ctx context.Context, root, command string) error

DefaultBuildRunner runs a build command through the user's shell in the project root, streaming output to the process's stdout/stderr so progress is visible. It is the real BuildRunner; tests inject a stub instead.

func DefaultTreeFingerprint

func DefaultTreeFingerprint(root string, skip ...string) (string, error)

DefaultTreeFingerprint computes a stable fingerprint of the project tree at root: a SHA-256 over the sorted list of relative file paths with each file's size and modification time. It is cheap (no file contents) and changes when a file is added, removed, resized, or retouched. It is size+mtime based, so a same-size edit inside one filesystem mtime tick is not distinguished; that is the deliberate cost of a cheap gate over the whole tree.

A directory is pruned when it is one of the skip paths (tree-relative or absolute — BuildAndDeploy passes the build output directory here), or when it is a direct child of root whose name is in fingerprintSkipDirs. The name-based prune is scoped to direct children on purpose: only the top-level output/state directories are conventional, so a nested source directory that happens to be called "build" is still fingerprinted. A skip path that does not exist is ignored.

It is the real TreeFingerprint; tests inject a stub so no walk runs. A root that cannot be read is an error.

func NormalizeDeployTarget added in v0.24.0

func NormalizeDeployTarget(deployTarget string) string

NormalizeDeployTarget returns the effective deploy target for a manifest value: an absent (empty) value means DeployTargetPages. Any other value is returned unchanged — including a whitespace-only or otherwise unrecognised one — so it is reported as an error rather than silently defaulted. This mirrors the manifest validator, which requires a present value to be exactly "pages" or "workers".

Types

type BuildRequest

type BuildRequest struct {
	// Root is the absolute project root the build runs in. Required.
	Root string
	// Command is the starter manifest's build command (pkg/startermanifest
	// .Build). Required: a deploy with no build step has nothing to upload.
	Command string
	// BuildDir is the absolute directory the build produces its deployable
	// output in, resolved from the deploy config / manifest. Required; it is
	// handed to the target unchanged.
	BuildDir string
	// Project is the project name on the target. Required.
	Project string
	// Kind is preview or production. Empty is treated as preview by adapters.
	Kind DeploymentKind
	// Version identifies the build being shipped. Optional.
	Version string
}

BuildRequest is one build-and-upload: where to build, what command to run, and the built output to ship. Its fields are the resolved deploy config and starter manifest flattened to plain values, so this package depends on neither pkg/deployconfig nor pkg/starterstore (pkg/deployconfig imports this package, and the cycle must not form). The caller resolves the manifest and config (deployconfig.Resolve) and fills these in.

type BuildRunner

type BuildRunner func(ctx context.Context, root, command string) error

BuildRunner runs a build command in the project root and reports whether it succeeded. It is the injectable seam over the workspace build: the default spawns a shell, and tests supply a stub so no process runs. A non-nil error means the build failed.

type Call

type Call struct {
	// Method is the interface method name: "Deploy", "Status", "List",
	// "Rollback", or "PreviewURL".
	Method string
	// Project is the project the call concerned, when it concerned one.
	Project string
	// DeploymentID is the deployment the call concerned, when it concerned
	// one.
	DeploymentID string
}

Call records one method invocation on a FakeTarget. Method names the interface method and Args carries the salient input (the request's project, or the deployment id) for a test to assert on.

type CloudflareConfig

type CloudflareConfig struct {
	// AccountID is the Cloudflare account id. Required.
	AccountID string
	// Project is the Pages project name for Pages deploys, or the Worker
	// name for Workers deploys. Required.
	Project string
}

CloudflareConfig names the account and project a Cloudflare adapter ships to. AccountID scopes Pages and Workers resources; Project is the Pages project (or the Worker name) the deployment belongs to and the key its history and rollback are registered under.

type CloudflareError

type CloudflareError struct {
	Code    int
	Message string
}

CloudflareError is one entry from a Cloudflare API error response.

type CloudflarePages

type CloudflarePages struct {
	// contains filtered or unexported fields
}

CloudflarePages implements DeployTarget against a Cloudflare Pages project.

func NewCloudflarePagesTarget

func NewCloudflarePagesTarget(cfg CloudflareConfig, cred Credential, baseURL string, client *http.Client) (*CloudflarePages, error)

NewCloudflarePagesTarget builds a Pages adapter for cfg, authenticating with cred. baseURL is the API root (default DefaultCloudflareAPIBaseURL when empty), and client is the HTTP client (default a client with a bounded timeout when nil). Tests point baseURL at an httptest server and pass that server's client, so no network is touched.

cred is the resolved credential from ResolveCredential — the adapter never reads the environment, so the token is always obtained out of band and can never reach model context, tool arguments, or logs.

func (*CloudflarePages) Deploy

func (p *CloudflarePages) Deploy(req DeployRequest) (Deployment, error)

Deploy uploads the built directory in req.BuildDir as a direct-upload Pages deployment under the project and returns the recorded Deployment.

The adapter is bound to one project (CloudflareConfig.Project): req.Project must name that project, so the interface's Deployment.Project identity stays consistent with the API path the deploy went to. A request naming a different project is refused rather than silently deployed to the configured one.

The deployment is created first (POST /projects/{project}/deployments), which yields the deployment id and its preview host, then the built files are uploaded against it (POST /deployments/{id}/assets). A deployment that fails to accept the files is surfaced as an error rather than a false success.

func (*CloudflarePages) List

func (p *CloudflarePages) List(project string) ([]Deployment, error)

List returns the project's deployment history, oldest first. The project argument must name this adapter's project (or be empty, meaning "this adapter's project"); any other project yields an empty slice, matching the interface's "an unknown project yields an empty slice, not an error". An unknown or empty Cloudflare project likewise yields an empty slice.

func (*CloudflarePages) PreviewURL

func (p *CloudflarePages) PreviewURL(d Deployment) (string, error)

PreviewURL returns the per-deployment preview address for d. A preview deployment has its own pages.dev host; a production deployment is served at the project's live host rather than a per-deployment address, so it reports ErrPreviewUnsupported. An unknown deployment is ErrUnknownDeployment.

func (*CloudflarePages) Rollback

func (p *CloudflarePages) Rollback(d Deployment) (Deployment, error)

Rollback re-points the project at the deployment preceding d and marks d as rolled back, returning the now-live deployment.

It resolves the project's history, finds d within it, and asks Cloudflare to roll the project back to the earlier deployment. d itself is marked rolled-back in the returned history; the deployment it reverts to is reported as ready. It returns ErrUnknownDeployment when d is not in the project's history, and ErrNoPreviousDeployment when d is the earliest.

func (*CloudflarePages) Status

func (p *CloudflarePages) Status(d Deployment) (StatusState, error)

Status returns the current lifecycle state of d, reading it back from the account so a rolled-back or failed deployment is reported accurately. An unknown deployment is ErrUnknownDeployment.

type CloudflareRequestError

type CloudflareRequestError struct {
	// Op names the call, e.g. "create deployment".
	Op string
	// StatusCode is the HTTP status of the response.
	StatusCode int
	// Errors are the API's structured errors, when present.
	Errors []CloudflareError
	// Message is a sanitized human-readable reason: Cloudflare's own error
	// messages with any credential-looking substring scrubbed, or a short
	// fallback when the body was not a Cloudflare envelope.
	Message string
}

CloudflareRequestError describes a failed Cloudflare API call. StatusCode is the HTTP status, and Errors carries the API's structured errors when the response body was a Cloudflare envelope. It never holds the request's auth header or URL, so it is safe to format into a log line or an error.

func (*CloudflareRequestError) Error

func (e *CloudflareRequestError) Error() string

Error implements error.

func (*CloudflareRequestError) Unwrap

func (e *CloudflareRequestError) Unwrap() error

Unwrap exposes ErrCloudflareRequestFailed so errors.Is works uniformly.

type CloudflareTarget

type CloudflareTarget struct {
	// contains filtered or unexported fields
}

CloudflareTarget implements DeployTarget against Cloudflare. It is the entry point that hosts the shared HTTP plumbing; the Pages and Workers adapters embed it and supply the resource shape.

The token is held in an unexported Credential field: it is never a struct field a caller can read back, never part of any request value, and never formatted into an error or a log line. It reaches the wire only through the auth header the HTTP layer sets.

func (*CloudflareTarget) PageURL

func (t *CloudflareTarget) PageURL() string

PageURL is the fallback address a Pages deploy is served at when the API response does not carry a deployment-specific URL: the project's stable host on the pages.dev domain.

type CloudflareWorkers

type CloudflareWorkers struct {
	// contains filtered or unexported fields
}

CloudflareWorkers implements DeployTarget against a Cloudflare Worker.

func NewCloudflareWorkersTarget

func NewCloudflareWorkersTarget(cfg CloudflareConfig, cred Credential, baseURL string, client *http.Client) (*CloudflareWorkers, error)

NewCloudflareWorkersTarget builds a Workers adapter for cfg. It takes the same seams as the Pages constructor.

func (*CloudflareWorkers) Deploy

func (w *CloudflareWorkers) Deploy(req DeployRequest) (Deployment, error)

Deploy uploads the Worker script from req.BuildDir and returns the recorded Deployment. The script file is defaultWorkerEntryFile (or "index.js" as a second convention); a build directory with neither is refused.

Before the script is uploaded, every binding the project's wrangler.toml declares is created or reused and attached to the script (see reconcileBindings), and pending D1 migrations are applied. The upload — the step that makes the worker live — happens only once all of that has succeeded, so a deploy never goes live with a binding missing; a binding that cannot be prepared fails the deploy with ErrBindingUnavailable naming it.

A Worker has no per-deployment identity, so the returned Deployment's ID is the worker name and its URL is the standard workers.dev preview host. The deployment is immediately live for the worker name (Workers scripts are uploaded per name, not per branch).

func (*CloudflareWorkers) List

func (w *CloudflareWorkers) List(project string) ([]Deployment, error)

List returns the worker's single-entry history (oldest first) when the project names this worker; an unknown project yields an empty slice.

func (*CloudflareWorkers) PreviewURL

func (w *CloudflareWorkers) PreviewURL(d Deployment) (string, error)

PreviewURL reports ErrPreviewUnsupported: a Worker serves one live script and has no per-deployment preview address.

func (*CloudflareWorkers) Rollback

func (w *CloudflareWorkers) Rollback(d Deployment) (Deployment, error)

Rollback reports ErrNoPreviousDeployment: a Worker has a single live script, so there is no earlier deployment to restore. An unknown deployment is ErrUnknownDeployment.

func (*CloudflareWorkers) Status

Status reports the Worker as ready: a Workers script is live once uploaded. A deployment whose id does not name this worker is unknown.

type Confirmation

type Confirmation struct {
	// Confirmed reports whether a user explicitly granted this deploy. Only
	// true lets a production deploy proceed; false (the zero value) refuses.
	Confirmed bool
	// ApprovedBy is who granted it — a user identifier, "cli", "ship-mode",
	// or the tool-approval channel. Carried for the audit trail; it does not
	// by itself satisfy the gate. Optional.
	ApprovedBy string
	// Note is a free-form remark from the confirmation (e.g. the prompt text
	// or approval ticket id). Optional; never consulted by the gate.
	Note string
}

Confirmation records that a user explicitly approved a production deploy, and how. It is the injectable seam the deploy path checks: a caller that obtained approval (the CLI prompt, the Ship mode action, a confirmed tool approval) fills it in, and BuildAndDeploy refuses a production request without it.

The zero value is unconfirmed (`Confirmed == false`), so confirmation fails closed: a caller that forgets to set it, or mishandles an all-zero value, cannot accidentally promote to production. Preview deploys ignore it entirely.

func (Confirmation) DescribeApproval

func (c Confirmation) DescribeApproval() string

DescribeApproval renders the confirmation's provenance for an audit line: the approver when one is named, else a generic marker. It never returns an empty string, so a caller can log a granted-but-unattributed confirmation without a blank field. It is display-only and never affects the gate.

func (Confirmation) Grants

func (c Confirmation) Grants() bool

Grants reports whether this confirmation allows a production deploy. It is the single predicate the gate consults, and it is true only for an explicitly confirmed value: an empty or partial Confirmation (including a stray ApprovedBy with Confirmed left false) does not grant.

type Credential

type Credential struct {
	// contains filtered or unexported fields
}

Credential is a resolved deploy credential: the secret value plus the source it came from. The value is returned to the caller (the runtime or the target adapter); it must never be attached to DeployRequest, Deployment, or any other value that crosses to a model or a log.

The secret is held in an unexported field and the type marshals to JSON without it, so a Credential — even embedded in a larger struct — can never be serialized into a payload sent to a model, and an accidental %v/%#v renders a masked form. Reaching the secret requires the explicit Value() accessor, which keeps every use site deliberate.

func ResolveCredential

func ResolveCredential(cfg DeployCredentialConfig) (Credential, error)

ResolveCredential obtains the deploy credential described by cfg, preferring the existing credential store and falling back to the embedding-supplied environment variable only when the store has none.

Resolution order (tokens live in the existing credential store — keyring or encrypted file — or are supplied by the embedding environment):

  1. Credential store: the OS keyring or the encrypted file store. This is the preferred source, so a token a user stored with `sprout keys set` always wins over an ambient environment value.
  2. Embedding environment: the variable named by cfg.EnvVar, when set and non-blank. Used only when the store had nothing.

It returns the secret plus its source, or ErrMissingDeployCredential (wrapped with the target name) when neither source has one. The returned value is for the caller alone: it is never attached to a DeployRequest, a Deployment, or a DeployConfig.

func (Credential) GoString

func (c Credential) GoString() string

GoString implements fmt.GoStringer so a %#v of a Credential is masked too.

func (Credential) MarshalJSON

func (c Credential) MarshalJSON() ([]byte, error)

MarshalJSON serializes a Credential without its secret: only the source is emitted. This is the structural guarantee behind "never in model context": even if a Credential is embedded in an event, request, or tool payload, the secret cannot ride along in its JSON.

func (Credential) Source

func (c Credential) Source() CredentialSource

Source reports where the credential came from (store or environment).

func (Credential) String

func (c Credential) String() string

String returns a safe, opaque description of the credential with the value masked, so an accidental %v or %s of a Credential never reveals the secret.

func (Credential) Value

func (c Credential) Value() string

Value returns the secret token. Sensitive: the caller must not log it or serialize it; it exists for the adapter that needs to authenticate.

type CredentialSource

type CredentialSource string

CredentialSource names where a resolved deploy credential came from, for diagnostics and logs. It is deliberately a small closed set so a caller (and a test) can distinguish "the user stored it" from "the embedding environment supplied it" without ever seeing the secret itself.

const (
	// SourceStore means the secret came from the existing credential store
	// (the OS keyring or the encrypted file store). This is the preferred
	// source.
	SourceStore CredentialSource = "store"
	// SourceEnvironment means the secret came from an embedding-supplied
	// environment variable, used only when the store had none.
	SourceEnvironment CredentialSource = "environment"
)

func (CredentialSource) String

func (s CredentialSource) String() string

String implements fmt.Stringer.

type DeployCredentialConfig

type DeployCredentialConfig struct {
	// Target is the deploy target adapter id from .sprout/deploy.json (e.g.
	// "cloudflare"). It is also the provider key used in the credential store.
	// Required.
	Target string
	// Project is the project name on the target. Optional; it is carried so a
	// caller can key a target-specific credential without inventing a parallel
	// lookup.
	Project string
	// EnvVar is the embedding-environment variable that may supply the token
	// as a fallback (e.g. "CLOUDFLARE_API_TOKEN"). Empty means "no
	// environment fallback": the resolver then only consults the store.
	EnvVar string
}

DeployCredentialConfig names the credential a deploy target needs. Callers build it from the deploy config (target, project) and the target adapter's declared token variable.

It carries no secret: only the provider key used to look a credential up in the store, and the environment variable name an embedding environment may supply as a fallback. The resolved secret is returned to the caller separately, never stored on this struct.

type DeployRequest

type DeployRequest struct {
	// Project is the project name to deploy. Required.
	Project string
	// Kind is preview or production. Empty is treated as KindPreview by
	// adapters that make the distinction.
	Kind DeploymentKind
	// BuildDir is the directory holding the build output to upload, from
	// the starter manifest's build_output. Required.
	BuildDir string
	// Root is the project root the build ran in. Optional; an adapter that
	// needs a project file (a Workers adapter reading wrangler.toml) uses it,
	// and an adapter that does not ignores it.
	Root string
	// Version identifies the build being shipped (e.g. the starter version
	// or plan revision). Optional; adapters may fill a default.
	Version string
}

DeployRequest is the input to Deploy: what to ship and where it came from.

BuildDir is the directory the build produced its deployable output in, taken from the starter manifest's build_output. The adapter uploads that output; it never rebuilds. The field is deliberately a plain path string — resolving it against the manifest and the project root, and enforcing the "what was verified is what ships" gate, is later work.

type DeployResourceState added in v0.25.0

type DeployResourceState struct {
	// Version is the schema version of the file.
	Version int `json:"version"`
	// D1 maps a stable resource name to the D1 database id.
	D1 map[string]string `json:"d1,omitempty"`
	// KV maps a stable resource name to the KV namespace id.
	KV map[string]string `json:"kv,omitempty"`
	// R2 maps a stable resource name to the R2 bucket name.
	R2 map[string]string `json:"r2,omitempty"`
}

DeployResourceState records the account resources a project's Workers deploy created, so a re-run reuses them. It is keyed by the stable resource name (see stableResourceName), so a resource is found even if the display name changes.

func LoadDeployResourceState added in v0.25.0

func LoadDeployResourceState(root string) (*DeployResourceState, error)

LoadDeployResourceState reads the state file under root. A missing file is the normal first-deploy case and yields an empty state (not an error); a corrupt file is an error, because silently starting over would risk creating duplicate resources.

type DeployTarget

type DeployTarget interface {
	// Deploy ships the build described by req and returns the recorded
	// Deployment. It does not rebuild: it uploads the output in
	// req.BuildDir.
	Deploy(req DeployRequest) (Deployment, error)

	// Status returns the current state of a deployment previously returned
	// by Deploy. It returns ErrUnknownDeployment for a deployment this
	// target does not know.
	Status(d Deployment) (StatusState, error)

	// List returns the target's deployment history for a project, oldest
	// first. An unknown project yields an empty slice, not an error.
	List(project string) ([]Deployment, error)

	// Rollback reverts to the deployment preceding d and marks d as rolled
	// back, returning the now-live deployment. It returns
	// ErrNoPreviousDeployment when there is nothing earlier to revert to,
	// and ErrUnknownDeployment when d is unknown.
	Rollback(d Deployment) (Deployment, error)

	// PreviewURL returns the per-deployment preview address for d. It
	// returns ErrPreviewUnsupported when the target has no per-deployment
	// previews, and ErrUnknownDeployment when d is unknown.
	PreviewURL(d Deployment) (string, error)
}

DeployTarget is a destination a project can be shipped to. Adapters are small packages that implement it — the fake adapter (FakeTarget) is one, a real hosting adapter another.

The interface is shaped for later items to build on: Deploy records a deployment and returns it; Status and List read the history; Rollback reverts to the previous deployment; PreviewURL resolves a per-deployment preview address where the target supports one. Implementations must be safe for concurrent use.

func CloudflareTargetFor added in v0.24.0

func CloudflareTargetFor(deployTarget string, cfg CloudflareConfig, cred Credential, baseURL string, client *http.Client) (DeployTarget, error)

CloudflareTargetFor builds the Cloudflare adapter the deploy target selects: the Pages adapter for "pages" (or an empty value, the default) and the Workers adapter for "workers". Any other value — including a whitespace-only or otherwise unrecognised one — is an actionable error naming the accepted values, mirroring the manifest validator.

It forwards the constructor seams unchanged (the API base URL and HTTP client, defaulted by the constructors), so tests point them at an httptest server exactly as they do for the constructors directly.

type Deployer

type Deployer struct {
	// Target receives the built output. Required.
	Target DeployTarget
	// Run builds the project in the workspace. Nil uses DefaultBuildRunner.
	Run BuildRunner
	// Fingerprint computes the tree fingerprint. Nil uses
	// DefaultTreeFingerprint.
	Fingerprint TreeFingerprint
}

Deployer orchestrates a build-and-upload: gate on confirmation and verification, build in the workspace, hand the built output to a target. Its three collaborators are fields so a caller (and a test) wires them independently; nil Run and Fingerprint fall back to the real defaults, while a nil Target is an error (there is nowhere to ship).

func (*Deployer) BuildAndDeploy

func (d *Deployer) BuildAndDeploy(ctx context.Context, req BuildRequest, snap VerificationSnapshot, confirm Confirmation) (Deployment, error)

BuildAndDeploy builds the project in the workspace and uploads the built output through the target, gated first on the preview/production rule and then on a passing verification of the same tree. confirm is the caller's explicit user confirmation; it is required for a production deploy and ignored for a preview.

The order is load-bearing — nothing is built or uploaded until every gate holds, and nothing is uploaded if the tree moves:

  1. A production deploy without an explicit confirmation is refused with ErrProductionNeedsConfirmation, before anything else runs. Preview deploys proceed with no confirmation.
  2. snap.Passed must be true, else ErrNoPassingVerification.
  3. The request's root/command/build directory/project must be set, else a plain error (ErrNoBuildCommand for a missing command).
  4. The current fingerprint must equal snap.Fingerprint, else ErrTreeChanged. The build has not run yet.
  5. The build runs in req.Root with req.Command. A failure is ErrBuildFailed wrapped with its cause, and the target is never called.
  6. The fingerprint is checked again, so a tree that changed while the build ran is ErrTreeChanged and the target is never called.
  7. Only then is req.BuildDir handed to the target's Deploy, which uploads it. The target is never asked to build.

type Deployment

type Deployment struct {
	// ID is the target-assigned deployment identifier. Unique within a
	// target and stable for the life of the deployment.
	ID string
	// Project is the project name the deployment belongs to. A target may
	// serve several projects; history is registered per project.
	Project string
	// Kind is whether this is a preview or a production deployment.
	Kind DeploymentKind
	// URL is the address the deployment is served at. For a preview
	// deployment this is the per-deployment preview URL.
	URL string
	// Version identifies what was built: the starter/plan version the
	// deployment was produced from. Opaque to this package.
	Version string
	// CreatedAt is when the deployment was recorded, in UTC.
	CreatedAt time.Time
	// Status is the deployment's lifecycle state (see StatusState).
	Status StatusState
}

Deployment is one recorded deploy of a project to a target: the identity (ID), what was deployed (Project, Version), where it landed (URL), the deploy shape (Kind), and its lifecycle state (Status).

A Deployment is an immutable value: adapters return copies and never hand out a reference callers can mutate. Rollback and PreviewURL take a Deployment and locate it by its ID (and Project), so a stale copy is still recognised as long as the id matches.

type DeploymentKind

type DeploymentKind string

DeploymentKind distinguishes the two deploy shapes: a preview deployment (safe to run automatically once verification passes, with its own per-deployment URL) and a production deployment (the live site, which always needs explicit user confirmation).

const (
	// KindPreview is a per-deployment preview. Preview deploys may run
	// automatically once verification passes.
	KindPreview DeploymentKind = "preview"
	// KindProduction is the live production deployment. Production deploys
	// always require explicit user confirmation.
	KindProduction DeploymentKind = "production"
)

type FakeTarget

type FakeTarget struct {
	// contains filtered or unexported fields
}

FakeTarget is an in-memory DeployTarget for tests. It records every call, hands out deterministic ids and URLs, keeps a per-project deployment history, and implements rollback by restoring the deployment that preceded the one being rolled back.

It is an ordinary adapter: the interface it satisfies is the same one a real hosting adapter will satisfy, so tests written against FakeTarget describe the behaviour every target must provide. It makes no network calls and needs no credentials; it is safe for concurrent use.

Determinism: ids are "<project>-<n>" with n counting 1,2,3… per target (not per project), and URLs are derived from the id, so a test can assert exact values without a clock or a rand source. CreatedAt advances one second per deploy from a fixed epoch so history ordering is stable and comparable.

func NewFake

func NewFake() *FakeTarget

NewFake returns an empty FakeTarget ready to deploy to.

func (*FakeTarget) Calls

func (f *FakeTarget) Calls() []Call

Calls returns a copy of the recorded call transcript, in order.

func (*FakeTarget) Deploy

func (f *FakeTarget) Deploy(req DeployRequest) (Deployment, error)

Deploy records the build described by req as a deployment, appends it to the project's history, and returns it. IDs and URLs are deterministic (see FakeTarget). A request missing a project or build directory is rejected.

func (*FakeTarget) List

func (f *FakeTarget) List(project string) ([]Deployment, error)

List returns the project's deployment history, oldest first. An unknown project yields an empty (non-nil) slice and no error.

func (*FakeTarget) PreviewURL

func (f *FakeTarget) PreviewURL(d Deployment) (string, error)

PreviewURL returns the per-deployment preview address for d. A deployment that is not a preview has no separate preview address, so this reports ErrPreviewUnsupported for it — matching the interface's "where the target supports per-deployment previews" and the preview/production split.

func (*FakeTarget) Rollback

func (f *FakeTarget) Rollback(d Deployment) (Deployment, error)

Rollback reverts to the deployment immediately preceding d and marks d rolled back, returning the deployment that is now live. The pair of state changes is recorded, so a caller can see both the rolled-back deployment and the restored one.

Rollback is a one-step rewind of the project's current head: it restores the deployment at position len(history)-2 and leaves everything older untouched. Rolling back a deployment that is not the current head is not a re-selection — it still restores that deployment's immediate predecessor, so a deployment that was already rolled back stays marked. See ErrNoPreviousDeployment and ErrUnknownDeployment.

func (*FakeTarget) Status

func (f *FakeTarget) Status(d Deployment) (StatusState, error)

Status returns the current state of a known deployment, or ErrUnknownDeployment.

type StatusState

type StatusState string

StatusState is the lifecycle state of a deployment, as reported by Status and carried on Deployment.Status.

const (
	// StatusQueued means the deploy has been accepted but is not yet live.
	StatusQueued StatusState = "queued"
	// StatusDeploying means the deploy is in progress.
	StatusDeploying StatusState = "deploying"
	// StatusReady means the deploy is live and serving.
	StatusReady StatusState = "ready"
	// StatusFailed means the deploy did not complete.
	StatusFailed StatusState = "failed"
	// StatusRolledBack means the deploy was superseded by a rollback.
	StatusRolledBack StatusState = "rolled_back"
)

type TreeFingerprint

type TreeFingerprint func(root string, skip ...string) (string, error)

TreeFingerprint computes a stable fingerprint of the project tree at root. Two calls over an unchanged tree return the same value; any file added, removed, resized, or retouched changes it. It is the injectable seam over "did the tree move since verification": the default hashes the sorted file list, and tests supply a stub so no directory walk runs.

skip names paths (relative to root or absolute) to leave out of the fingerprint. BuildAndDeploy always passes the request's BuildDir, because a build legitimately creates its own output: without excluding it, the post-build re-check would read the fresh artifact as a tree change. The gate therefore compares the source tree verification saw against the source tree the build ran over, not the artifact. A caller computing the snapshot at verification time must use the same function and skip set, or an unchanged tree can read as changed.

type VerificationSnapshot

type VerificationSnapshot struct {
	// Passed reports whether verification passed on the tree. False means
	// there was no passing result (a nil result, or a failed run).
	Passed bool
	// Fingerprint is the tree fingerprint captured when verification passed.
	Fingerprint string
}

VerificationSnapshot is the verification outcome for one tree state: did verification pass, and what did the tree look like at that moment.

Fingerprint is the value returned by the TreeFingerprint seam over the project root when verification ran; BuildAndDeploy recomputes it and refuses when the two differ. Passed is verify.Result.Passed() — the caller builds it from the agent's last verification result, so this package stays independent of pkg/verify.

type WranglerConfig added in v0.25.0

type WranglerConfig struct {
	// Name is the worker name (`name`). It is the script name the adapter
	// uploads to and the anchor for the resource names it creates.
	Name string `toml:"name"`
	// Main is the worker entry point (`main`), relative to the project root.
	Main string `toml:"main"`
	// D1Databases are the declared [[d1_databases]] blocks.
	D1Databases []WranglerD1 `toml:"d1_databases"`
	// KVNamespaces are the declared [[kv_namespaces]] blocks.
	KVNamespaces []WranglerKV `toml:"kv_namespaces"`
	// R2Buckets are the declared [[r2_buckets]] blocks.
	R2Buckets []WranglerR2 `toml:"r2_buckets"`
}

WranglerConfig is the subset of wrangler.toml the Workers adapter needs: the worker name and entry point, and the bindings the project declares. Each slice holds exactly the blocks present in the file — an absent block is an empty slice, never a synthesized default.

func LoadWranglerConfig added in v0.25.0

func LoadWranglerConfig(root string) (*WranglerConfig, error)

LoadWranglerConfig reads and parses wrangler.toml from the project root. A missing file is an error: a Workers deploy without its declared bindings would silently ship a broken app, so the adapter refuses rather than guessing.

type WranglerD1 added in v0.25.0

type WranglerD1 struct {
	Binding       string `toml:"binding"`
	DatabaseName  string `toml:"database_name"`
	DatabaseID    string `toml:"database_id"`
	MigrationsDir string `toml:"migrations_dir"`
}

WranglerD1 is one [[d1_databases]] block.

type WranglerKV added in v0.25.0

type WranglerKV struct {
	Binding string `toml:"binding"`
	ID      string `toml:"id"`
}

WranglerKV is one [[kv_namespaces]] block.

type WranglerR2 added in v0.25.0

type WranglerR2 struct {
	Binding    string `toml:"binding"`
	BucketName string `toml:"bucket_name"`
}

WranglerR2 is one [[r2_buckets]] block.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL