lockfile

package
v0.0.6-rc.1 Latest Latest
Warning

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

Go to latest
Published: Sep 8, 2026 License: MIT Imports: 9 Imported by: 0

Documentation

Overview

Package lockfile parses the GitHub Actions dependency lockfile format.

Parse is the entry point: it takes the bytes of .github/workflows/actions.lock and returns a File whose Workflows and Dependencies maps resolve every pinned action for a workflow.

ParseActionRef parses a single workflow `uses:` string into its owner/repo/ref components, returning nil for anything that is not a repository action. ParseReusableWorkflowRef is its mirror for the reusable-workflow shape (owner/repo/.github/workflows/name.yml@ref); IsLocalReusableWorkflow handles the local ./.github/workflows/... shape.

Index

Constants

View Source
const CLIName = "gh actions-lock"

CLIName is the canonical name of the CLI extension that manages lockfiles.

View Source
const MaxActionMetaSize = 1 << 20 // 1 MiB

MaxActionMetaSize is the maximum byte length ParseActionMeta will accept. action.yml files in the wild are well under 1 MiB.

View Source
const MaxParseSize = 1 << 20 // 1 MiB

MaxParseSize is the maximum number of bytes Parse accepts. Larger inputs are rejected before any YAML parsing to prevent memory-exhaustion DoS.

View Source
const Path = ".github/workflows/actions.lock"

Path is the canonical repo-relative location of the dependency lockfile.

View Source
const Version = "v0.0.3"

Version is the latest lockfile schema version this binary writes.

Variables

View Source
var ErrFutureVersion = errors.New("lockfile version is newer than this binary supports")

ErrFutureVersion is returned (via errors.Is) when Parse refuses a lockfile whose schema version is newer than this binary supports.

View Source
var ErrUnsupportedVersion = fmt.Errorf("lockfile version is older than this consumer supports")

ErrUnsupportedVersion is the sentinel returned when ParseWithPolicy refuses a lockfile whose version is older than the consumer's minimum.

Functions

func BestRef added in v0.0.4

func BestRef(tag, branch string) string

BestRef picks the single ref value for lockfile serialization. Tag wins if non-empty, else branch. Both empty yields empty.

func IndexKey

func IndexKey(owner, repo, ref string) string

IndexKey builds the normalized lookup key for a dependency entry: "OWNER/REPO@REF".

func IsFullSha

func IsFullSha(s string) bool

IsFullSha reports whether s looks like a full commit hash (SHA-1 or SHA-256). Callers use this to distinguish bare-SHA `uses:` refs from symbolic refs.

func IsLocalReusableWorkflow

func IsLocalReusableWorkflow(localUses string) bool

IsLocalReusableWorkflow reports whether a local `uses:` value (one starting with "./") names a reusable workflow file rather than a composite action directory. Pass the raw, untrimmed `uses:` string; the leading "./" must be present:

  • "./.github/workflows/ci.yml" → true (local reusable workflow)
  • "./my-composite-action" → false (local composite action)

func Schema

func Schema() string

Schema returns the embedded JSON Schema document for the latest lockfile version (v0.0.3). Callers can surface it for editor integration or external validation.

func SchemaForVersion added in v0.0.4

func SchemaForVersion(version string) (string, bool)

SchemaForVersion returns the embedded JSON Schema for a specific lockfile version. Returns ("", false) for unknown versions.

func ShortSHA

func ShortSHA(s string) string

ShortSHA returns the first 12 characters of a SHA, or the full string if shorter. Used for human-readable log and diagnostic output.

func SplitNWO

func SplitNWO(nwo string) (owner, repo string, ok bool)

SplitNWO splits an owner/repo (Name-With-Owner) string into its two components. It returns ok=false when either segment is missing: the empty string, no slash, a leading slash, or a trailing slash.

For inputs with extra segments ("owner/repo/sub/..."), only the first two are returned; the rest is dropped, matching the lockfile's repo-granularity pin grammar.

SplitNWO does not validate the owner/repo character set — use ParseActionRef for a verbatim `uses:` value where stricter charset rules apply.

func SplitRef added in v0.0.4

func SplitRef(ref string) (tag, branch string)

SplitRef classifies a lockfile ref value back into its tag/branch constituents. If ref parses as a stable semver (via ParseSemVer + IsStable), it's returned as tag; otherwise it's returned as branch. An empty ref yields both empty.

Types

type Action

type Action struct {
	Hostname string   `yaml:"hostname,omitempty"`
	Ref      string   `yaml:"ref,omitempty"`
	Commit   string   `yaml:"commit,omitempty"`
	OwnerID  int64    `yaml:"owner_id"`
	RepoID   int64    `yaml:"repo_id"`
	Uses     []string `yaml:"uses,omitempty"`
}

Action carries the per-action metadata recorded under a pin key.

Hostname is the optional bare canonical hostname of the GitHub instance that owns the dependency: github.com or a lowercase GHE tenant hostname such as octocorp.ghe.com. It is empty when omitted. Ref is the git ref the commit was resolved from (required). Commit is the digest in algo-prefixed form (e.g. "sha1-abc123...", "sha256-def456...") (required). OwnerID and RepoID are the host-specific numeric IDs for the owner and repository, used to detect a repository transfer (the name changes but the ID does not). Uses lists the action's direct nested dependencies as canonical pin keys — empty for leaf actions, populated for composite actions.

type ActionMeta

type ActionMeta struct {
	Name       string
	Execution  ExecutionType
	NestedUses []string
}

ActionMeta is the parsed subset of `action.yml` (or `action.yaml`) relevant to dependency resolution: the action's name, how it executes, and composite action nested `uses:` strings.

func ParseActionMeta

func ParseActionMeta(content string) (*ActionMeta, error)

ParseActionMeta parses the contents of an action.yml or action.yaml file into an ActionMeta. Composite actions emit their nested step `uses:` strings in NestedUses; other actions return an empty NestedUses.

It returns an error only on malformed YAML. Unknown runs.using values resolve to ExecUnknown rather than failing.

type ActionRef

type ActionRef struct {
	Owner string // e.g. "actions"
	Repo  string // e.g. "checkout"
	Path  string // e.g. "save" for actions/cache/save@v4
	Ref   string // tag, branch, or full SHA as written after `@`
	Raw   string // original `uses:` string (post-trim)
}

ActionRef is a parsed `uses:` reference to a repository action.

ParseActionRef is the only constructor; treat zero values as invalid.

func ParseActionRef

func ParseActionRef(uses string) *ActionRef

ParseActionRef parses a `uses:` string into an ActionRef. It returns nil for anything that is not a repository action — expression refs, local paths, docker images, reusable workflow files, or any input whose components are unsafe for the downstream URL/GraphQL builders.

func (ActionRef) FullName

func (a ActionRef) FullName() string

FullName returns owner/repo, or owner/repo/path when a sub-action path is present.

func (ActionRef) NWO

func (a ActionRef) NWO() string

NWO returns owner/repo (Name With Owner). Zero-value ActionRefs return the empty string.

type ExecutionType

type ExecutionType string

ExecutionType describes how an action runs.

const (
	ExecNode      ExecutionType = "node"
	ExecDocker    ExecutionType = "docker"
	ExecComposite ExecutionType = "composite"
	ExecUnknown   ExecutionType = "unknown"
)

type File

type File struct {
	// Version is the lockfile schema version string (e.g. "v0.0.1"), always
	// equal to the [Version] constant for files Parse accepts.
	Version string `yaml:"version"`

	// Dependencies maps each canonical pin key (OWNER/REPO@REF) to the
	// resolved [Action] metadata. Deduplicated across workflows. Use
	// [File.LookupWorkflow] to find a workflow's pin keys, then index here.
	Dependencies map[string]Action `yaml:"dependencies"`

	// Workflows maps each repo-relative workflow path to the flat, transitive
	// list of canonical pin keys (OWNER/REPO@REF) it depends on. Prefer
	// [File.LookupWorkflow] over indexing directly.
	Workflows map[string][]string `yaml:"workflows"`
	// contains filtered or unexported fields
}

File is the parsed lockfile shape.

# .github/workflows/actions.lock
version: v0.0.3
workflows:
  .github/workflows/deploy.yml:
    - actions/checkout@v6
dependencies:
  actions/checkout@v4.3.1:
    hostname: github.com
    ref: v4.3.1
    commit: sha1-34e114876b0b11c390a56381ad16ebd13914f8d5
    owner_id: 44036562
    repo_id: 197814629
    uses:
      - actions/cache@v4.0.0

Dependencies is the deduplicated action DAG; each entry's uses: list names its direct nested dependencies as canonical pin keys. Workflows holds each workflow's full transitive closure as a flat list of pin keys.

func Parse

func Parse(contents []byte, paths ...string) (File, error)

Parse unmarshals the raw bytes of a lockfile and returns the parsed File. Pass the contents of .github/workflows/actions.lock (the Path constant).

Parse checks structural validity — unknown top-level keys are rejected and required Action fields must be present — but does not verify pin integrity or that actions exist on GitHub; those checks belong to the caller.

The variadic paths parameter is optional. Omit it (or pass nil) to validate every dependency entry — the right choice for whole-file tooling. Pass one or more repo-relative workflow paths to limit required-field validation to the entries those workflows reference; other entries are still parsed and returned, and paths absent from the workflows map contribute nothing.

Dependency keys and workflow entries are canonicalized (lowercased) via ParsePin so lookups by Pin.String are casing-agnostic. Workflow path keys are not canonicalized — file paths are case-sensitive.

func ParseWithPolicy added in v0.0.4

func ParseWithPolicy(contents []byte, policy VersionPolicy, paths ...string) (File, error)

ParseWithPolicy is like Parse but enforces version bounds. The lockfile's declared version must be within [policy.Min, policy.Max] inclusive.

func (File) KeyPosition

func (f File) KeyPosition(path ...string) (line, col int, ok bool)

KeyPosition is like Position but resolves the position of the final path segment's key node rather than its value.

func (File) LookupWorkflow

func (f File) LookupWorkflow(workflowKey string) ([]string, bool)

LookupWorkflow returns the flat, transitive list of canonical pin keys (OWNER/REPO@REF) for the given repo-relative workflow path. Look each key up in File.Dependencies for its Action metadata:

pins, ok := f.LookupWorkflow(".github/workflows/deploy.yml")
for _, key := range pins {
    action := f.Dependencies[key]
    fmt.Println(action.Ref, action.Commit)
}

ok=false means the workflow was never onboarded into the lockfile; an onboarded workflow with no dependencies returns an empty slice and ok=true.

func (File) Position

func (f File) Position(path ...string) (line, col int, ok bool)

Position returns the 1-indexed line and column of the value node reached by following path as a sequence of mapping keys from the lockfile root (e.g. Position("version") points at the version value). ok is false when the path can't be resolved or no node tree was retained.

type ParseError

type ParseError struct {
	Line   int
	Column int
	Msg    string
	// contains filtered or unexported fields
}

ParseError describes a failure to parse a lockfile. Parse always returns it (via errors.As) so callers can print file:line:col diagnostics.

Line and Column, when non-zero, are the 1-indexed position within the lockfile bytes. Column is zero for low-level YAML syntax errors, where only a line number is available. Msg is the description without any position prefix; use Error for the full "line N, column M: reason" string.

func (*ParseError) Error

func (e *ParseError) Error() string

func (*ParseError) Unwrap

func (e *ParseError) Unwrap() error

type Pin

type Pin struct {
	NWO   string // "actions/checkout"
	Owner string // "actions"
	Repo  string // "checkout"
	Ref   string // "v4"
}

Pin holds the parsed components of a dependency pin key "OWNER/REPO@REF".

Pins identify an action at repo+ref granularity, matching the runner, which downloads owner/repo@ref once and reuses the tree for any sub-action path. Sub-action paths (e.g. the save in actions/cache/save@v4) are not part of this serialized form.

func ParsePin

func ParsePin(s string) (Pin, bool)

ParsePin parses a canonical pin string "OWNER/REPO@REF". It returns ok=false when the "@" separator is missing or the repo portion carries a sub-action path (e.g. "owner/repo/sub@ref"), which the repo-scoped grammar rejects. On success owner and repo are lowercased; Ref preserves source casing.

func (Pin) Canonical

func (p Pin) Canonical() Pin

Canonical returns a copy of p with owner and repo lowercased. Ref preserves source casing — git refs are case-sensitive. String, IndexKey, and ParsePin all funnel through it.

func (Pin) IndexKey

func (p Pin) IndexKey() string

IndexKey returns the normalized lookup key for this pin: "OWNER/REPO@REF". Identical to String() — retained for API compatibility.

func (Pin) String

func (p Pin) String() string

String returns the canonical pin form: "OWNER/REPO@REF". This doubles as the actions-map key in the lockfile.

type ReusableWorkflowRef

type ReusableWorkflowRef struct {
	Owner string // e.g. "octo"
	Repo  string // e.g. "workflows"
	Path  string // e.g. ".github/workflows/release.yml"
	Ref   string // tag, branch, or full SHA as written after `@`
	Raw   string // original `uses:` string (post-trim)
}

ReusableWorkflowRef is a parsed `uses:` reference to a reusable workflow file — the owner/repo/.github/workflows/<name>.yml@ref shape that ParseActionRef rejects. Path is the in-repo workflow file path.

ParseReusableWorkflowRef is the only constructor; treat zero values as invalid.

func ParseReusableWorkflowRef

func ParseReusableWorkflowRef(uses string) *ReusableWorkflowRef

ParseReusableWorkflowRef parses the remote reusable-workflow `uses:` shape that ParseActionRef rejects: owner/repo/.github/workflows/<name>.yml@ref. It returns nil for anything else, including local reusable workflows (./.github/workflows/...) — use IsLocalReusableWorkflow for those — and nested paths, since reusable workflows must live directly under .github/workflows/.

func (ReusableWorkflowRef) FullName

func (r ReusableWorkflowRef) FullName() string

FullName returns owner/repo/path — the fully-qualified reusable workflow identity.

func (ReusableWorkflowRef) NWO

func (r ReusableWorkflowRef) NWO() string

NWO returns owner/repo (Name With Owner). Zero-value refs return the empty string.

type SemVer

type SemVer struct {
	Prefix string // "v" or "" — whether the original tag had a "v" prefix
	Major  int
	Minor  int
	Patch  int
	// Rest is everything after the patch number (e.g. "-beta.1"). Empty
	// means a stable version; see [SemVer.IsStable].
	Rest string
	Raw  string // original tag string as written (e.g. "v4" or "2.0.0-rc.1")
}

SemVer holds parsed semantic version components.

Its comparison helpers (Greater, Narrows, UpgradeOver, MajorTag, MinorTag, IsFull) are deliberately non-strict-semver: they accept bare versions ("2.0.0"), partial versions ("v4", "v4.2"), and arbitrary suffixes that appear in Actions refs but that golang.org/x/mod/semver rejects.

func ParseSemVer

func ParseSemVer(tag string) (SemVer, bool)

ParseSemVer parses a version tag into its components. Returns false if the tag doesn't look like a version (or is a full SHA that happens to start with a digit).

func (SemVer) Greater

func (s SemVer) Greater(o SemVer) bool

Greater reports whether s should be preferred over o: higher major.minor.patch wins; on a tie a stable version beats a pre-release, a v-prefixed tag beats the same bare version, then a lexicographic compare of the raw tags provides a deterministic final tie-break.

func (SemVer) IsFull

func (s SemVer) IsFull() bool

IsFull returns true if the version has all three components (major.minor.patch) and no pre-release suffix. Tags like "v4" or "v4.2" return false. Only a full version uniquely identifies a release.

func (SemVer) IsMajorOnly

func (s SemVer) IsMajorOnly() bool

IsMajorOnly reports whether the raw tag is a bare major version (e.g. "v4").

func (SemVer) IsMutable

func (s SemVer) IsMutable() bool

IsMutable reports whether the version is a partial (major-only or major.minor) tag — the opposite of SemVer.IsFull. A partial tag can be silently moved to a new patch commit, so it is unsafe to trust without a SHA pin. Use SemVer.Narrows to find a full patch version that narrows it.

func (SemVer) IsStable

func (s SemVer) IsStable() bool

IsStable returns true if the version has no pre-release suffix (Rest == ""). "v1.2.3" is stable; "v1.2.3-beta.1" is not.

func (SemVer) MajorTag

func (s SemVer) MajorTag() string

MajorTag returns the major-only tag string (e.g. "v4").

func (SemVer) MinorTag

func (s SemVer) MinorTag() string

MinorTag returns the major.minor tag string (e.g. "v4.2").

func (SemVer) Narrows

func (s SemVer) Narrows(other SemVer) bool

Narrows reports whether s is a more specific patch version of other. e.g. other="v4", s="v4.1.0" → true; other="v4.2", s="v4.2.1" → true.

func (SemVer) UpgradeOver

func (s SemVer) UpgradeOver(other SemVer) bool

UpgradeOver reports whether s represents a real version upgrade over other. Returns false for noops where other is already at or more specific than s.

type VersionPolicy added in v0.0.4

type VersionPolicy struct {
	// Min is the oldest version the consumer can read. Lockfiles older than
	// this are rejected with ErrUnsupportedVersion.
	Min string
	// Max is the newest version the consumer can read. Lockfiles newer than
	// this are rejected with ErrFutureVersion.
	Max string
}

VersionPolicy controls which lockfile schema versions a consumer accepts. Servers set their own policy to control the rollout of new formats.

func DefaultPolicy added in v0.0.4

func DefaultPolicy() VersionPolicy

DefaultPolicy returns a policy that accepts all versions this binary can parse — from the oldest supported through the latest.

Directories

Path Synopsis
internal
cmd/genschema command

Jump to

Keyboard shortcuts

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