1// SPDX-License-Identifier: MIT
2
3package action
4
5import (
6 "bnema.dev/ermite-actions/template"
7 "bnema.dev/ermite-actions/workflow"
8)
9
10// Error is a problem found in an action manifest. See the package comment for
11// the form of its message.
12type Error = template.Error
13
14// Action is a parsed action.yml.
15type Action struct {
16 // File is the name given to Parse.
17 File string
18 // Name, Description and Author are the keys of the same names; "" when
19 // absent, or not a string. The runner does not read Author.
20 Name, Description, Author string
21 // Inputs are the declared inputs in file order.
22 Inputs []Input
23 // Outputs are the declared outputs in file order. Only a composite
24 // action's outputs have a Value that is used.
25 Outputs []Output
26 // Runs says how the action runs. It is never nil in a returned Action.
27 Runs *Runs
28}
29
30// Input returns the input called name, compared like the runner compares
31// input names (OrdinalIgnoreCase), or nil.
32func (a *Action) Input(name string) *Input {
33 for i := range a.Inputs {
34 if ordinalEqual(a.Inputs[i].Name, name) {
35 return &a.Inputs[i]
36 }
37 }
38 return nil
39}
40
41// Input is an entry of "inputs".
42type Input struct {
43 // Name is the key, as written.
44 Name string
45 // Pos is where the key is.
46 Pos template.Pos
47 // Description is the "description" key, or "".
48 Description string
49 // Required is "required: true" (a boolean, or the string "true"). The
50 // runner does not check it.
51 Required bool
52 // Default is the default value: a string, or an expression
53 // (${{ github.ref }}) that the runner evaluates when the step does not
54 // give the input; the context it may use is the one of
55 // "input-default-context" in the schema (github, strategy, matrix, job,
56 // runner and hashFiles). It is never nil: an input without a default has
57 // the empty string, as in the runner. HasDefault tells them apart.
58 Default template.Token
59 HasDefault bool
60 // DeprecationMessage is "deprecationMessage", or "". The runner warns
61 // "Input '<name>' has been deprecated with message: <message>" when a
62 // step gives the input.
63 DeprecationMessage string
64}
65
66// Deprecated reports whether the input has a deprecation message.
67func (in *Input) Deprecated() bool { return in.DeprecationMessage != "" }
68
69// Output is an entry of "outputs".
70type Output struct {
71 Name string
72 Pos template.Pos
73 Description string
74 // Value is the "value" token of a composite action's output: a string or
75 // an expression over github, strategy, matrix, steps, inputs, job, runner
76 // and env. nil when the output has none.
77 Value template.Token
78}
79
80// RunsKind is how an action runs.
81type RunsKind int
82
83// The kinds of action.
84const (
85 // RunsNode is a JavaScript action: using node12, node16, node20 or node24.
86 RunsNode RunsKind = iota + 1
87 // RunsDocker is a container action: using docker.
88 RunsDocker
89 // RunsComposite is a composite action: using composite.
90 RunsComposite
91 // RunsPlugin is "runs: {plugin: name}", an internal runner plugin. The
92 // manifest loads, but only the runner's own plugins can run.
93 RunsPlugin
94)
95
96// Runs is the "runs" key. Which fields are set depends on Kind.
97type Runs struct {
98 Kind RunsKind
99 // Using is the runtime: "node12", "node16", "node20", "node24", "docker",
100 // "composite", or "plugin". The runner compares "using" without regard to
101 // case; Using is lower case.
102 Using string
103 // Pos is where "runs" is.
104 Pos template.Pos
105
106 // Node actions.
107 //
108 // Main is the script to run; Pre and Post are scripts that run before the
109 // job's first step and after its last, or "". PreIf and PostIf are their
110 // conditions: expressions written without ${{ }}, evaluated as they are
111 // (the runner does not add "success() &&"), "always()" when absent.
112 // Docker actions have the same four conditions for their entrypoints.
113 Main, Pre, Post string
114 PreIf, PostIf string
115
116 // Docker actions.
117 //
118 // Image is a path to a Dockerfile ("Dockerfile", "images/Dockerfile") or
119 // "docker://image[:tag]"; see [ParseImage]. Entrypoint overrides the
120 // image's ENTRYPOINT. Args is a sequence of strings and Env a mapping of
121 // strings, both of which may hold ${{ inputs.x }} expressions (and no other
122 // context); nil when absent. PreEntrypoint and PostEntrypoint are the
123 // entrypoints that run before and after, or "".
124 Image, Entrypoint, PreEntrypoint, PostEntrypoint string
125 Args *template.Sequence
126 Env *template.Mapping
127
128 // Composite actions.
129 //
130 // Steps are the steps in file order. A run step has a shell; the contexts
131 // of each key are those of the schema (github, inputs, strategy, matrix,
132 // steps, job, runner, env), and "if" also takes vars and needs. A step
133 // has no timeout-minutes and no working-directory on a uses step.
134 Steps []*workflow.Step
135
136 // Plugin actions.
137 Plugin string
138}
139
140// HasPre reports whether the action has something to run before the job's
141// steps: a node "pre" or a docker "pre-entrypoint".
142func (r *Runs) HasPre() bool {
143 switch r.Kind {
144 case RunsNode:
145 return r.Pre != ""
146 case RunsDocker:
147 return r.PreEntrypoint != ""
148 }
149 return false
150}
151
152// HasPost reports whether the action has something to run after the job's
153// steps: a node "post" or a docker "post-entrypoint".
154func (r *Runs) HasPost() bool {
155 switch r.Kind {
156 case RunsNode:
157 return r.Post != ""
158 case RunsDocker:
159 return r.PostEntrypoint != ""
160 }
161 return false
162}