config

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Sep 17, 2026 License: MIT Imports: 17 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.

Functions

func BlockSlugsFromSource

func BlockSlugsFromSource(src string) []string

BlockSlugsFromSource extracts `slug: '…'` literals that sit in an object literal which also has a `fields:` key (§7.10).

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; duplicates are removed.

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 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 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.
	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.

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
}

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:"-"`

	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