config

package
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Sep 23, 2026 License: MIT Imports: 18 Imported by: 0

Documentation

Overview

Package config owns everything PayCLI knows before it talks to a server: where its files live (§4.1), what is in them (§4.2, §4.3), how the layers combine (§4.5) and what can be learned about the surrounding Payload project from the local filesystem alone (§7.10, §7.11).

Two rules shape this package:

  • Resolve is a pure function. It performs no I/O whatsoever, so §4.5's precedence matrix is a table test (§3.1).
  • Nothing here reads the process environment directly. It enters as an Env snapshot built once by internal/cli/app.go, which is the only file allowed to touch it (§3.1).

Index

Constants

View Source
const (
	KindUser    = "user"
	KindProject = "project"
)

Layer kinds for the source strings of §4.5.

View Source
const (
	ConfigFileName      = "config.toml"
	CredentialsFileName = "credentials.json"
	AuditFileName       = "audit.log"
	UpdateStateFileName = "update-state.json"
	ProjectFileName     = "pay.toml"

	// CacheGeneration is the versioned sub-directory of the cache dir; the
	// whole tree below it is disposable (§4.1, §8).
	CacheGeneration = "v1"
)

File names inside the resolved directories (§4.1).

View Source
const (
	DBPostgres = "postgres"
	DBMongoDB  = "mongodb"
	DBSQLite   = "sqlite"
	DBUnknown  = "unknown"
)

DBAdapter values (§7.11).

View Source
const (
	SourceConfigured    = "configured"
	SourceProjectPkg    = "project-package-json"
	SourceProjectSource = "project-source"
	SourceInferred      = "inferred"
	SourceObserved      = "observed"
	SourceUnknown       = "unknown"
)

Provenance strings shared by §7.10/§7.11 and `pay config explain`.

View Source
const (
	MaxScanFiles = 200
	MaxScanBytes = 2 << 20 // 2 MB total

)

Scan caps (§7.10). They exist so that pointing PayCLI at a monorepo cannot turn a cache miss into a filesystem crawl.

View Source
const (
	DefaultProfileName      = "default"
	DefaultAPIPath          = "/api"
	DefaultGraphQLRoute     = "/graphql"
	DefaultAuthCollection   = "auto"
	DefaultAuthHeaderScheme = "JWT"
	DefaultOutput           = "json"
	DefaultErrorsTo         = "stdout"
	DefaultDepth            = 0
	DefaultLimit            = 20
	DefaultMaxRetries       = 3
	DefaultConcurrency      = 8
	DefaultMaxBulk          = 100
	DefaultAcceptLanguage   = "en"
	DefaultLogLevel         = "info"
	DefaultLogFormat        = "text"
	DefaultUpdateChannel    = "stable"

	// MaxConcurrency is §9.1's hard cap. PayCLI is the only backpressure a
	// Payload 3.x server has.
	MaxConcurrency = 32
)

Built-in defaults (§4.2). These are the last link of §4.5's chain.

View Source
const (
	DefaultTimeout      = 30 * time.Second
	DefaultDeadline     = 120 * time.Second
	DefaultDiscoveryTTL = 24 * time.Hour
	DefaultAccessTTL    = 10 * time.Minute
	DefaultSchemaTTL    = 24 * time.Hour
	DefaultIdentityTTL  = 60 * time.Second
	DefaultSkillsTTL    = 6 * time.Hour
)

Default durations (§4.2).

View Source
const (
	SourceFlag    = "flag"
	SourceDefault = "default"
	// SourceDerivedGraphQL is §4.2's visible coupling between api_path and
	// graphql_route.
	SourceDerivedGraphQL = "derived:api_path+graphql_route"
)

Source strings that are not a layer name.

View Source
const FilePerm fs.FileMode = 0o600

FilePerm is the mode of config.toml (§4.1). It is 0600 like everything else PayCLI writes: the file holds header values that §5.3 treats as secret.

Variables

AuthModes is the accepted set, in help order.

View Source
var DescriptionKeys = []string{
	"admin.description", "custom.description", "custom.docs", "custom.summary",
}

DescriptionKeys are the config paths §7.10 accepts a human description from, in preference order, and the exact strings reported back as description_key.

`admin.description` is Payload's own documented FIELD-level key and is both the right place for per-field docs and the first choice here. A BLOCK has no such key: a Block's `admin` accepts only components / custom / disableBlockName / group / images / jsx, and `tsc --noEmit` rejects `admin: { description }` on one. So a block's description has to live in `custom`, Payload's sanctioned arbitrary-metadata escape hatch — which is free-form, meaning every project spells it differently. The obvious neighbours are therefore all accepted, and whichever one answered is always reported next to the text rather than being flattened into a "description" that implies Payload defines one.

Functions

func BlockSlugsFromSource

func BlockSlugsFromSource(src string) []string

BlockSlugsFromSource extracts the `slug: '…'` half of BlockDeclsFromSource, in the same order and with the same duplicate handling.

func Interpolate

func Interpolate(s string, env Env) (string, []string)

Interpolate expands ${NAME} references in a config value against env (§4.2's `"X-Vercel-Protection-Bypass" = "${VERCEL_BYPASS}"`).

Deliberately minimal, because this string can end up in an HTTP header and in §8.1's headersFP:

  • ${NAME} only. A bare $NAME is left alone, so a literal dollar in a token survives untouched.
  • $${NAME} escapes to the literal ${NAME}.
  • An unset (or set-but-empty, §4.5) variable expands to "" and its name is returned in missing, so the caller can warn instead of silently sending an empty header.

The returned missing slice is sorted and de-duplicated.

func InterpolateMap

func InterpolateMap(in map[string]string, env Env) (map[string]string, []string)

InterpolateMap expands every value of a header map. Keys are never interpolated: a header *name* built from the environment would make §8.1's headersFP and the redaction allow-list unpredictable.

func ProfileNames

func ProfileNames(files ...*File) []string

ProfileNames returns the union of profile names across the layers, sorted, for did-you-mean and `pay auth list`.

Types

type AuthMode

type AuthMode string

AuthMode is §5.0's three-way auth selector plus "auto".

const (
	AuthModeAuto      AuthMode = "auto"
	AuthModeAPIKey    AuthMode = "api-key"
	AuthModeJWT       AuthMode = "jwt"
	AuthModeAnonymous AuthMode = "anonymous"
)

Auth modes. "auto" is a *configuration* value only: it never survives credential resolution, which always lands on one of the other three (§5.1).

func ParseAuthMode

func ParseAuthMode(s string) (AuthMode, error)

ParseAuthMode validates --auth-mode / PAY_AUTH_MODE / the profile key.

func (AuthMode) String

func (m AuthMode) String() string

String satisfies fmt.Stringer.

type BlockDecl added in v0.2.0

type BlockDecl struct {
	Slug string `json:"slug"`
	// InterfaceName is "" for a block that declares none; Payload then derives
	// the GraphQL type name from the slug itself.
	InterfaceName string `json:"interface_name,omitempty"`
	// Fields are the entries of this block's own `fields:` array literal, in
	// source order. They exist for ONE fact GraphQL cannot supply: Payload
	// generates no INPUT_OBJECT for a block type (the blocks mutation argument
	// is a JSON scalar), so the NON_NULL trick that recovers required-ness for
	// a collection field has nothing to read. The config on disk is the only
	// place `required: true` is written down.
	Fields []BlockFieldDecl `json:"fields,omitempty"`
	// FieldsComplete is true only when EVERY element of the `fields:` array
	// was an object literal carrying a `name:` string literal. A helper call
	// (`linkGroup({…})`), a spread, or a computed field makes it false, and a
	// false here is what keeps a missing entry reading as "unknown" instead of
	// "not required" — the exact guess this whole type exists to refuse.
	FieldsComplete bool `json:"fields_complete,omitempty"`

	// LabelSingular and LabelPlural are the block's own `labels:` literals
	// ({ singular: 'Call to Action', plural: 'Calls to Action' }). They are
	// the human name of the block and, like every other fact here, exist only
	// when the project's authors wrote them; "" is "not declared", never a
	// title-cased guess at the slug.
	LabelSingular string `json:"label_singular,omitempty"`
	LabelPlural   string `json:"label_plural,omitempty"`

	// Description is the one-sentence statement of what this block is FOR.
	//
	// Payload blocks have NO standard field for it — verified: a Block's
	// `admin` accepts only components/custom/disableBlockName/group/images/jsx
	// and `tsc --noEmit` rejects `admin: { description }` on a Block — so the
	// text lives in `custom`, Payload's sanctioned arbitrary-metadata escape
	// hatch, under whatever key the project chose. DescriptionKey records
	// which one it was so the output is honest about where the words came from
	// instead of implying a standard field exists.
	Description string `json:"description,omitempty"`
	// DescriptionKey is the literal config path the text was read from:
	// "custom.description", "custom.docs", "custom.summary" or
	// "admin.description". Empty exactly when Description is empty.
	DescriptionKey string `json:"description_key,omitempty"`
}

BlockDecl is one block definition found in project source: the blockType slug the REST API accepts, together with the interfaceName Payload turns into that block's GraphQL union member.

Verified in /home/flo/payload-dummy: src/blocks/CallToAction/config.ts declares slug: 'cta' beside interfaceName: 'CallToActionBlock', and __type(name:"Page_Layout").possibleTypes names CallToActionBlock. Without the pair there is no way back from the union to the slug.

func BlockDeclsFromSource added in v0.2.0

func BlockDeclsFromSource(src string) []BlockDecl

BlockDeclsFromSource extracts the `slug: '…'` and `interfaceName: '…'` literals of every object literal that also has a `fields:` key (§7.10).

The two are harvested TOGETHER, from the same object literal, because the pair is the only bridge between what GraphQL publishes for a blocks field (possibleTypes: CallToActionBlock) and what the REST API accepts for it (blockType: cta). A bare slug list cannot say which field a block belongs to, which is exactly how one global bag ended up attached to every blocks field.

This is a byte scanner, not a TypeScript parser: PayCLI must never import or evaluate project code. It understands strings, template literals and both comment forms well enough that a slug inside one of them cannot be mistaken for a declaration, and it pairs slug/fields by brace nesting, so a nested block definition is picked up while an unrelated `slug:` on a collection export without `fields:` is not.

Order is source order; duplicate slugs are removed.

type BlockFieldDecl added in v0.2.0

type BlockFieldDecl struct {
	Name string `json:"name"`
	// Type is the `type:` literal ('richText', 'upload', 'select', …). It is
	// the only source that can tell lexical richText from a json field and a
	// select from a radio, both of which compile to the SAME GraphQL shape.
	Type string `json:"type,omitempty"`
	// Required is the literal `required: true` / `required: false`, or nil
	// when the key was absent or not a boolean literal.
	Required *bool `json:"required,omitempty"`

	// Description is the field's own human instruction, read from
	// `admin: { description: '…' }` — which, unlike a Block's admin, Payload
	// DOES support on a field and is exactly where a per-field "pass a media
	// document id" belongs. `custom.description` is accepted as well, for the
	// same reason it is on a block.
	Description string `json:"description,omitempty"`
	// DescriptionKey is the literal config path the text was read from
	// ("admin.description", "custom.description", …), empty when Description
	// is empty.
	DescriptionKey string `json:"description_key,omitempty"`
}

BlockFieldDecl is one field read out of a block's `fields:` array.

Required is a POINTER: nil is "the object literal had no `required:` key at all", which for Payload means not required, while a non-literal value (`required: isProd`) is also recorded as nil because the scanner refuses to evaluate project code. The caller decides what to do with each, and only a declaration that was fully parsed is allowed to answer false.

type CacheSection

type CacheSection struct {
	Dir          string `toml:"dir,omitempty"`
	DiscoveryTTL string `toml:"discovery_ttl,omitempty"`
	AccessTTL    string `toml:"access_ttl,omitempty"`
	SchemaTTL    string `toml:"schema_ttl,omitempty"`
	IdentityTTL  string `toml:"identity_ttl,omitempty"`
	SkillsTTL    string `toml:"skills_ttl,omitempty"`
}

CacheSection is the [cache] table (§4.2).

type Defaults

type Defaults struct {
	Output         string `toml:"output,omitempty"`
	Depth          *int   `toml:"depth,omitempty"`
	Limit          *int   `toml:"limit,omitempty"`
	Timeout        string `toml:"timeout,omitempty"`
	Deadline       string `toml:"deadline,omitempty"`
	MaxRetries     *int   `toml:"max_retries,omitempty"`
	Concurrency    *int   `toml:"concurrency,omitempty"`
	Redact         *bool  `toml:"redact,omitempty"`
	AcceptLanguage string `toml:"accept_language,omitempty"`
	MaxBulk        *int   `toml:"max_bulk,omitempty"`
	ConfirmWrites  *bool  `toml:"confirm_writes,omitempty"`
	Locale         string `toml:"locale,omitempty"`
	FallbackLocale string `toml:"fallback_locale,omitempty"`
	ErrorsTo       string `toml:"errors_to,omitempty"`
}

Defaults is the [defaults] table (§4.2). Pointers everywhere: `depth = 0` and `redact = false` are meaningful settings, not absence.

type Env

type Env map[string]string

Env is a snapshot of the process environment.

Lookup implements §4.5's "set but empty counts as unset" rule, so `PAY_API_KEY= pay …` cannot silently blank a credential.

func NewEnv

func NewEnv(pairs []string) Env

NewEnv builds an Env from os.Environ()-shaped "NAME=value" pairs.

func (Env) Get

func (e Env) Get(name string) string

Get returns the value of name or "".

func (Env) Has

func (e Env) Has(name string) bool

Has reports whether name is present at all, empty value included. Only the handful of "presence is the signal" variables should use this; everything in §4.5's precedence chain goes through Lookup.

func (Env) Lookup

func (e Env) Lookup(name string) (string, bool)

Lookup returns the value of name, treating a set-but-empty variable as unset (§4.5).

type FieldDoc added in v0.2.0

type FieldDoc struct {
	Description string `json:"description"`
	// Key is the literal config path — "admin.description" in the normal case.
	Key string `json:"key"`
}

FieldDoc is one field's human documentation as the project wrote it: the text and the config key it came from, never one without the other.

type File

type File struct {
	Version        int                `toml:"version,omitempty"`
	DefaultProfile string             `toml:"default_profile,omitempty"`
	Defaults       Defaults           `toml:"defaults,omitempty"`
	Cache          CacheSection       `toml:"cache,omitempty"`
	Logging        LoggingSection     `toml:"logging,omitempty"`
	Update         UpdateSection      `toml:"update,omitempty"`
	Profiles       map[string]Profile `toml:"profiles,omitempty"`

	// APIKey is not a legal key anywhere (§4.2); it is decoded only so the
	// hard error can name it.
	//nolint:gosec // G117: decode-only. Encode() clears it; see
	// TestEncodeNeverEmitsAPIKey and TestAPIKeyInConfigIsFatal.
	APIKey string `toml:"api_key,omitempty"`

	// Path is where the file came from; it is what `pay config explain`
	// prints as "user:/abs/config.toml" or "project:/abs/pay.toml" (§4.5).
	Path string `toml:"-"`
	// Exists is false for a file that was absent, which is not an error.
	Exists bool `toml:"-"`
	// Kind is "user" or "project"; it prefixes the source string.
	Kind string `toml:"-"`
	// UnknownKeys are decoded-but-unrecognised keys, surfaced as warnings so a
	// typo like `base_ur1` is visible without being fatal.
	UnknownKeys []string `toml:"-"`
}

File is a decoded config.toml (§4.2) or pay.toml (§4.3). The same schema serves both; a project file simply must not carry secrets.

func Load

func Load(path, kind string) (*File, error)

Load reads and validates a config file. A missing file yields a usable empty File with Exists == false and no error: PayCLI must work with no config at all, driven entirely by flags and environment (§4.5, §21).

func Parse

func Parse(data []byte, path, kind string) (*File, error)

Parse decodes TOML bytes. It is the I/O-free half of Load, which is what the tests use.

func (*File) Encode

func (f *File) Encode() ([]byte, error)

Encode renders the file as TOML.

APIKey is cleared first. The field exists only so a config.toml carrying the forbidden `api_key` decodes and can be rejected by name (§4.2); it must never travel in the other direction. Without this, any code path that set it would make Encode write a credential into config.toml — exactly what §5.2 forbids and what scripts/arch-lint.sh greps for. Encode operates on a copy, so the caller's File is untouched.

func (*File) Profile

func (f *File) Profile(name string) (Profile, bool)

Profile returns the named profile and whether it is defined here.

func (*File) Save

func (f *File) Save() error

Save writes the file atomically at 0600 (§4.1). Version is forced to 1 so a hand-written file that omitted it does not round-trip as 0.

func (*File) SetProfile

func (f *File) SetProfile(name string, p Profile)

SetProfile inserts or replaces a profile.

func (*File) Source

func (f *File) Source() string

Source renders this layer's §4.5 source string, e.g. "user:/home/u/.config/pay/config.toml".

func (*File) String

func (f *File) String() string

String makes a File printable in test failures without dumping every field.

func (*File) Validate

func (f *File) Validate() error

Validate applies the schema rules that do not need any other layer.

type Flags

type Flags struct {
	Profile          string
	BaseURL          string
	APIPath          string
	GraphQLPath      string
	GraphQLRoute     string
	AuthCollection   string
	AuthMode         string
	AuthHeaderScheme string

	Output         string
	ErrorsTo       string
	Path           string
	Locale         string
	FallbackLocale string
	AcceptLanguage string

	Timeout  string
	Deadline string
	CacheTTL string

	Depth       *int
	Limit       *int
	MaxDocs     *int
	MaxRetries  *int
	Concurrency *int

	Redact             *bool
	InsecureSkipVerify *bool
	NoCache            *bool
	Refresh            *bool
	Yes                *bool
	DryRun             *bool
	NoAudit            *bool
	Quiet              *bool
	Verbose            *bool

	LogLevel  string
	LogFormat string
}

Flags carries §9.1's root persistent flags. Every field is empty/nil unless the flag was actually changed, which is how "flag" wins §4.5's first slot without a spurious zero value beating a config file.

type Input

type Input struct {
	Flags   Flags
	Env     Env
	Project *File
	User    *File
}

Input is everything Resolve is allowed to see. No file handles, no clock, no environment access: the caller loads the layers, Resolve combines them (§3.1).

type KeyringMode

type KeyringMode string

KeyringMode is PAY_KEYRING (§5.2).

const (
	KeyringAuto  KeyringMode = "auto"
	KeyringOff   KeyringMode = "off"
	KeyringForce KeyringMode = "force"
)

Keyring modes.

func ParseKeyringMode

func ParseKeyringMode(s string) (KeyringMode, error)

ParseKeyringMode validates PAY_KEYRING. The default is "off": §5.1 step 8 reads the keychain only when the profile opted in or PAY_KEYRING=force, so an unset variable must not make the chain touch dbus/wincred. "auto" only affects *writes* (§5.2).

type LoggingSection

type LoggingSection struct {
	Level  string `toml:"level,omitempty"`
	Format string `toml:"format,omitempty"`
}

LoggingSection is the [logging] table (§4.2).

type Paths

type Paths struct {
	ConfigDir  string
	CacheDir   string
	StateDir   string
	ConfigFile string

	Sources map[string]string
	// contains filtered or unexported fields
}

Paths is the one source of truth for where PayCLI keeps its files (§4.1). Sources explains, per directory, which rule produced it, so `pay config paths --output json` never has to guess.

func DefaultPaths

func DefaultPaths(env Env) Paths

DefaultPaths is PathsFor bound to this host. It is the only function in the package that touches the OS, and it never fails: an unknown home directory degrades to the process working directory rather than aborting the CLI.

func PathsFor

func PathsFor(env Env, goos, home string) Paths

PathsFor is the pure form of path resolution: everything it needs is an argument, so the Windows and XDG branches are both testable on any host.

goos is a runtime.GOOS value; home is the user's home directory (may be empty, in which case relative fallbacks are used rather than failing — a missing $HOME must never make `pay version` impossible).

Precedence per directory: the specific PAY_*_DIR variable, then $PAY_HOME, then the platform rule. PAY_HOME "overrides all three" defaults (§4.1) but the more specific variable still wins over it — that is the conventional reading, and it lets a test pin PAY_HOME while redirecting just the cache.

func (Paths) AuditFile

func (p Paths) AuditFile() string

AuditFile is §14's 0600 append-only audit log.

func (Paths) CacheRoot

func (p Paths) CacheRoot() string

CacheRoot is the generation directory; `rm -rf` on it must only ever cost time (§4.1).

func (Paths) CredentialsFile

func (p Paths) CredentialsFile() string

CredentialsFile is §4.4's 0600 credential store.

func (Paths) Map

func (p Paths) Map() map[string]string

Map renders the paths for `pay config paths --output json`.

func (Paths) ScopeDir

func (p Paths) ScopeDir(scope string) string

ScopeDir is the cache directory for one §8.1 scope key.

func (Paths) UpdateStateFile

func (p Paths) UpdateStateFile() string

UpdateStateFile is §15's self-update bookkeeping.

func (Paths) WithCacheDir

func (p Paths) WithCacheDir(dir string) Paths

WithCacheDir applies config.toml's [cache] dir override (§4.2).

func (Paths) WithConfigFile

func (p Paths) WithConfigFile(path string) Paths

WithConfigFile applies the --config flag, which points at the config file itself and therefore also moves nothing else (§9.1).

type Profile

type Profile struct {
	BaseURL            string              `toml:"base_url,omitempty"`
	APIPath            string              `toml:"api_path,omitempty"`
	GraphQLRoute       string              `toml:"graphql_route,omitempty"`
	GraphQLPath        string              `toml:"graphql_path,omitempty"`
	AuthCollection     string              `toml:"auth_collection,omitempty"`
	AuthMode           string              `toml:"auth_mode,omitempty"`
	AuthHeaderScheme   string              `toml:"auth_header_scheme,omitempty"`
	Label              string              `toml:"label,omitempty"`
	APIKeyEnv          string              `toml:"api_key_env,omitempty"`
	CredentialHelper   string              `toml:"credential_helper,omitempty"`
	InsecureSkipVerify *bool               `toml:"insecure_skip_verify,omitempty"`
	Headers            map[string]string   `toml:"headers,omitempty"`
	Blocks             map[string][]string `toml:"blocks,omitempty"`

	// Optional pins. Each one turns a discovered-or-unknown value into a
	// configured one and flips the matching *_source to "configured" (§4.2).
	IDType          string   `toml:"id_type,omitempty"`
	Locales         []string `toml:"locales,omitempty"`
	PayloadVersion  string   `toml:"payload_version,omitempty"`
	DBAdapter       string   `toml:"db_adapter,omitempty"`
	EchoCheckIgnore []string `toml:"echo_check_ignore,omitempty"`
	CustomEndpoints []string `toml:"custom_endpoints,omitempty"`

	// APIKey is never a legal key (§4.2); see File.Validate.
	APIKey string `toml:"api_key,omitempty"`
}

Profile is one [profiles.X] table (§4.2).

Every optional scalar is a pointer or an empty-able string so that "unset" and "set to the zero value" stay distinguishable through §4.5's chain. The APIKey field exists only so that a config.toml carrying the forbidden key decodes instead of landing in the undecoded set: its presence is a hard error (config_secret_in_plaintext).

type Project

type Project struct {
	// Dir is the directory that anchored the discovery — the first one going
	// up that contained any marker at all.
	Dir string
	// ConfigPath is ./pay.toml, the committed project config (§4.3).
	ConfigPath string
	// PackageJSON is the nearest package.json (§7.11 source 2).
	PackageJSON string
	// PayloadConfig is the nearest payload.config.ts / .js / .mjs, at the root
	// or under src/ (§7.10).
	PayloadConfig string
	// SrcDir is the payload config's directory when it lives in src/.
	SrcDir string
	// GitRoot is the directory holding .git; the walk stops after it.
	GitRoot string
	// Start is the directory the walk began in.
	Start string
	// contains filtered or unexported fields
}

Project is what the upward walk of §4.3 found around the working directory. Every field is optional: PayCLI is usable from any directory, including one that has nothing to do with a Payload project.

func FindProject

func FindProject(start string) *Project

FindProject walks up from start looking for a project (§4.3). It stops at the git root, at the filesystem root, or after maxWalkDepth directories.

The walk never leaves the filesystem and never executes anything it finds.

func (*Project) Found

func (p *Project) Found() bool

Found reports whether the walk saw anything project-shaped.

func (*Project) GitignoreCovers

func (p *Project) GitignoreCovers(pattern string) bool

GitignoreCovers reports whether the project's .gitignore mentions pattern. `pay auth login` uses it to warn when a skill directory or a credential file would be committed (§5.3). A missing .gitignore reports false.

func (*Project) IsPayload

func (p *Project) IsPayload() bool

IsPayload reports whether this looks like a Payload project specifically — the precondition for §7.10's block-slug scan and §7.11's version read.

func (*Project) LoadConfig

func (p *Project) LoadConfig() (*File, error)

LoadConfig loads the project pay.toml, if the walk found one. A project without pay.toml is normal and yields an empty, non-existent File.

type Resolved

type Resolved struct {
	Profile        string
	ProfileDefined bool
	Label          string

	BaseURL          string
	APIPath          string
	GraphQLRoute     string
	GraphQLPath      string
	AuthCollection   string
	AuthMode         AuthMode
	AuthHeaderScheme string
	APIKeyEnv        string
	CredentialHelper string
	Headers          map[string]string
	KeyringMode      KeyringMode

	Output         string
	ErrorsTo       string
	Path           string
	Locale         string
	FallbackLocale string
	AcceptLanguage string

	Timeout     time.Duration
	Deadline    time.Duration
	Depth       int
	Limit       int
	MaxDocs     int
	MaxRetries  int
	Concurrency int
	MaxBulk     int

	Redact             bool
	ConfirmWrites      bool
	InsecureSkipVerify bool
	NoCache            bool
	Refresh            bool
	CacheTTL           time.Duration
	Yes                bool
	DryRun             bool
	NoAudit            bool
	Quiet              bool
	Verbose            bool

	CacheDir     string
	DiscoveryTTL time.Duration
	AccessTTL    time.Duration
	SchemaTTL    time.Duration
	IdentityTTL  time.Duration
	SkillsTTL    time.Duration

	LogLevel  string
	LogFormat string

	UpdateAuto    bool
	UpdateChannel string

	// Optional pins (§4.2). An empty value means "not configured"; the
	// matching *Source entry says so explicitly.
	IDType          string
	Locales         []string
	PayloadVersion  string
	DBAdapter       string
	EchoCheckIgnore []string
	CustomEndpoints []string
	Blocks          map[string][]string

	// ConcurrencyClamped is true when the requested concurrency exceeded
	// MaxConcurrency and was lowered.
	ConcurrencyClamped bool
	// Warnings are non-fatal configuration observations for the caller to put
	// in the envelope (§10): a moved base_url credential, an unset ${ENV} in a
	// header, an unknown key in a config file.
	Warnings []string
	// Sources maps a setting name to its §4.5 provenance string.
	Sources map[string]string
	// contains filtered or unexported fields
}

Resolved is the effective configuration for one command invocation.

basicAuth is deliberately unexported and has no JSON/TOML tag anywhere: it holds a credential lifted out of base_url (§4.2) and must never be serialised with the rest of the struct.

func Resolve

func Resolve(in Input) (*Resolved, error)

Resolve combines the layers of §4.5. It is pure: given the same Input it returns the same Resolved, and it never touches the filesystem, the clock or the environment beyond the supplied snapshot.

func (*Resolved) BasicAuth

func (r *Resolved) BasicAuth() (username, password string, ok bool)

BasicAuth returns the credential that was carried in base_url, if any (§4.2). The transport applies it as an Authorization: Basic header, where it is redacted like every other credential.

func (*Resolved) Explain

func (r *Resolved) Explain() map[string]Setting

Explain renders `pay config explain --output json` (§4.5). Header values are masked: the names are the only part that may be shown (§5.3).

func (*Resolved) HeaderNames

func (r *Resolved) HeaderNames() []string

HeaderNames returns the configured extra header names, sorted. Only names ever leave this process; values are secret (§5.3, §8.1).

func (*Resolved) NormURL

func (r *Resolved) NormURL() string

NormURL is §8.1's normalised URL: lowercase scheme and host, the port only when it is non-default, plus api_path. Userinfo is already gone by construction. It lives here because it is a pure function of resolved config, and internal/cache must not re-derive it differently.

func (*Resolved) RequireBaseURL

func (r *Resolved) RequireBaseURL() error

RequireBaseURL is the check every command that talks to a server performs. Resolve itself tolerates an absent base_url so that `pay version`, `pay config paths` and `pay explain` still work on a bare machine.

type ScanResult

type ScanResult struct {
	Root string `json:"root,omitempty"`

	PayloadVersion       string `json:"payload_version,omitempty"`
	PayloadVersionSource string `json:"payload_version_source"`
	PackageJSON          string `json:"package_json,omitempty"`

	// DBAdapter is inferred from the project's @payloadcms/db-* dependency.
	// §7.11 only defines "configured" and "inferred" for this field, so a
	// package.json match reports "inferred" — which deliberately does NOT
	// unlock §9.3's client-side operator block, since that requires
	// "configured".
	DBAdapter       string `json:"db_adapter"`
	DBAdapterSource string `json:"db_adapter_source"`

	BlockSlugs     []string          `json:"blocks,omitempty"`
	BlocksSource   string            `json:"blocks_source"`
	BlockSlugFiles []string          `json:"block_slug_files,omitempty"`
	SlugFile       map[string]string `json:"-"`

	// BlockDecls are the (slug, interfaceName) pairs §7.10 harvests, in scan
	// order. The pair is the load-bearing fact: GraphQL publishes a blocks
	// field's union as interfaceNames (CallToActionBlock) while the REST API
	// only ever accepts the slug (cta), so neither half alone can turn the
	// per-field union into something writable.
	BlockDecls []BlockDecl `json:"block_decls,omitempty"`
	// SlugByInterface is BlockDecls indexed by interfaceName. Blocks that
	// declare no interfaceName are absent: there is nothing to key them by.
	SlugByInterface map[string]string `json:"-"`

	// FieldDocs are the per-field `admin: { description: '…' }` instructions
	// declared by the project's COLLECTIONS and GLOBALS, keyed by entity slug
	// and then by field name.
	//
	// They cover a collection's TOP-LEVEL `fields:` array only. A field nested
	// inside a group, an array or a tab is not reached — the scanner adopts
	// only the direct elements of the array it can attribute — and is reported
	// as undocumented rather than being given a path this scanner would have
	// had to guess.
	//
	// An entity whose config was read but documents nothing is present with an
	// EMPTY map. The key's presence is the fact "PayCLI read this entity's
	// config"; its absence is "there is none on disk", which is what a
	// plugin-provided collection looks like.
	FieldDocs map[string]map[string]FieldDoc `json:"field_docs,omitempty"`
	// FieldDocFiles are the absolute paths FieldDocs were read from, keyed by
	// entity slug, so the answer can always name its own source file.
	FieldDocFiles map[string]string `json:"field_doc_files,omitempty"`

	FilesScanned int   `json:"files_scanned"`
	BytesScanned int64 `json:"bytes_scanned"`
	Truncated    bool  `json:"truncated,omitempty"`
}

ScanResult is everything §7.10 and §7.11 can learn from the project on disk. Every fact carries its provenance; nothing here is ever guessed.

func Scan

func Scan(p *Project) *ScanResult

Scan reads what the local filesystem knows about the project: the Payload version from package.json (§7.11) and the block slugs from project source (§7.10).

It is local-filesystem only. It never makes a network call, never imports or evaluates project code, and never writes anything.

type Setting

type Setting struct {
	Value  any    `json:"value"`
	Source string `json:"source"`
}

Setting is one row of `pay config explain --output json` (§4.5).

type UpdateSection

type UpdateSection struct {
	Auto    *bool  `toml:"auto,omitempty"`
	Channel string `toml:"channel,omitempty"`
}

UpdateSection is the [update] table (§4.2).

Jump to

Keyboard shortcuts

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