feat(workflow): add the model, the converter and its tests against the reference parser
- commit
- 8372da1ae7704ed4ca476ecb5c1abf827c54537a
- author
- brice · · 2026-10-12 15:50 UTC
- committer
- brice · · 2026-10-12 16:17 UTC
- parent
- 6416d3e
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
168 files · +5300 -0
M template/tokens.go +5 -0
| @@ -283,3 +283,8 @@ func trimDisplay(s string) string { | |||
| 283 | 283 | } | |
| 284 | 284 | return s | |
| 285 | 285 | } | |
| 286 | + | ||
| 287 | + | // Node returns the parsed expression, or nil when the text did not parse or | |
| 288 | + | // is empty. Callers that only inspect the tree (to look for function calls, | |
| 289 | + | // for instance) use it with the types of package expr. | |
| 290 | + | func (t *BasicExpression) Node() expr.Node { return t.node } | |
A workflow/convert.go +143 -0
| @@ -0,0 +1,143 @@ | |||
| 1 | + | // SPDX-License-Identifier: MIT | |
| 2 | + | ||
| 3 | + | package workflow | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "bnema.dev/ermite-actions/template" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | // converter turns the token tree of a workflow into the model. It is a port | |
| 10 | + | // of the reference's converter, which reports problems by throwing an error | |
| 11 | + | // that the caller of each conversion step catches and records; here panics of | |
| 12 | + | // type *template.TokenError play the part of those exceptions. | |
| 13 | + | type converter struct { | |
| 14 | + | ctx *template.Context | |
| 15 | + | schema *template.Schema | |
| 16 | + | w *Workflow | |
| 17 | + | jobIDs *idBuilder | |
| 18 | + | } | |
| 19 | + | ||
| 20 | + | // handle runs fn and records a *template.TokenError panic as an error | |
| 21 | + | // located at the token it names, like handleTemplateTokenErrors. | |
| 22 | + | func (c *converter) handle(root template.Token, fn func()) { | |
| 23 | + | defer func() { | |
| 24 | + | if r := recover(); r != nil { | |
| 25 | + | te, ok := r.(*template.TokenError) | |
| 26 | + | if !ok { | |
| 27 | + | panic(r) | |
| 28 | + | } | |
| 29 | + | if te.Token != nil { | |
| 30 | + | c.ctx.ErrorAt(te.Token, te.Message) | |
| 31 | + | } else { | |
| 32 | + | c.ctx.ErrorAt(root, te.Message) | |
| 33 | + | } | |
| 34 | + | } | |
| 35 | + | }() | |
| 36 | + | fn() | |
| 37 | + | } | |
| 38 | + | ||
| 39 | + | func throw(t template.Token, format string, a ...any) { | |
| 40 | + | panic(&template.TokenError{Token: t, Message: sprintf(format, a...)}) | |
| 41 | + | } | |
| 42 | + | ||
| 43 | + | func ck[T any](v T, err error) T { | |
| 44 | + | if err != nil { | |
| 45 | + | panic(err) | |
| 46 | + | } | |
| 47 | + | return v | |
| 48 | + | } | |
| 49 | + | ||
| 50 | + | func asString(t template.Token, desc string) *template.String { | |
| 51 | + | return ck(template.AssertString(t, desc)) | |
| 52 | + | } | |
| 53 | + | ||
| 54 | + | func asBool(t template.Token, desc string) *template.Boolean { | |
| 55 | + | return ck(template.AssertBoolean(t, desc)) | |
| 56 | + | } | |
| 57 | + | ||
| 58 | + | func asSeq(t template.Token, desc string) *template.Sequence { | |
| 59 | + | return ck(template.AssertSequence(t, desc)) | |
| 60 | + | } | |
| 61 | + | ||
| 62 | + | func asMap(t template.Token, desc string) *template.Mapping { | |
| 63 | + | return ck(template.AssertMapping(t, desc)) | |
| 64 | + | } | |
| 65 | + | ||
| 66 | + | func asScalar(t template.Token, desc string) template.Token { | |
| 67 | + | return ck(template.AssertScalar(t, desc)) | |
| 68 | + | } | |
| 69 | + | ||
| 70 | + | func origin(t template.Token) template.Origin { | |
| 71 | + | return template.Origin{File: t.File(), Pos: t.Pos()} | |
| 72 | + | } | |
| 73 | + | ||
| 74 | + | // convertRoot converts the root mapping. | |
| 75 | + | func (c *converter) convertRoot(root template.Token) { | |
| 76 | + | defer func() { | |
| 77 | + | if r := recover(); r != nil { | |
| 78 | + | te, ok := r.(*template.TokenError) | |
| 79 | + | if !ok { | |
| 80 | + | panic(r) | |
| 81 | + | } | |
| 82 | + | if te.Token != nil { | |
| 83 | + | c.ctx.ErrorAt(te.Token, te.Message) | |
| 84 | + | } else { | |
| 85 | + | c.ctx.ErrorAt(root, te.Message) | |
| 86 | + | } | |
| 87 | + | } | |
| 88 | + | }() | |
| 89 | + | m := asMap(root, "root") | |
| 90 | + | for _, p := range m.Pairs { | |
| 91 | + | key := asString(p.Key, "root key") | |
| 92 | + | switch key.Value { | |
| 93 | + | case "on": | |
| 94 | + | c.handle(root, func() { c.w.On = c.convertOn(p.Value) }) | |
| 95 | + | case "jobs": | |
| 96 | + | c.w.Jobs = nil | |
| 97 | + | c.handle(root, func() { c.w.Jobs = c.convertJobs(p.Value) }) | |
| 98 | + | case "concurrency": | |
| 99 | + | c.handle(root, func() { c.convertConcurrency(p.Value) }) | |
| 100 | + | c.w.Concurrency = p.Value | |
| 101 | + | case "env": | |
| 102 | + | if em, ok := p.Value.(*template.Mapping); ok { | |
| 103 | + | c.w.Env = em | |
| 104 | + | } | |
| 105 | + | case "name": | |
| 106 | + | if s, ok := p.Value.(*template.String); ok { | |
| 107 | + | c.w.Name = s.Value | |
| 108 | + | } | |
| 109 | + | case "run-name": | |
| 110 | + | c.w.RunName = p.Value | |
| 111 | + | case "defaults": | |
| 112 | + | c.w.Defaults = convertDefaults(p.Value) | |
| 113 | + | case "permissions": | |
| 114 | + | c.w.Permissions = convertPermissions(p.Value) | |
| 115 | + | } | |
| 116 | + | } | |
| 117 | + | } | |
| 118 | + | ||
| 119 | + | func convertDefaults(t template.Token) *Defaults { | |
| 120 | + | m, ok := t.(*template.Mapping) | |
| 121 | + | if !ok { | |
| 122 | + | return nil | |
| 123 | + | } | |
| 124 | + | d := &Defaults{} | |
| 125 | + | if run, ok := m.Find("run").(*template.Mapping); ok { | |
| 126 | + | d.Run = &RunDefaults{Shell: run.Find("shell"), WorkingDirectory: run.Find("working-directory")} | |
| 127 | + | } | |
| 128 | + | return d | |
| 129 | + | } | |
| 130 | + | ||
| 131 | + | func convertPermissions(t template.Token) *Permissions { | |
| 132 | + | switch t := t.(type) { | |
| 133 | + | case *template.String: | |
| 134 | + | return &Permissions{All: t.Value} | |
| 135 | + | case *template.Mapping: | |
| 136 | + | p := &Permissions{} | |
| 137 | + | for _, kv := range t.Pairs { | |
| 138 | + | p.Scopes = append(p.Scopes, PermissionScope{Scope: kv.Key.String(), Level: kv.Value.String()}) | |
| 139 | + | } | |
| 140 | + | return p | |
| 141 | + | } | |
| 142 | + | return nil | |
| 143 | + | } | |
A workflow/convert_container.go +88 -0
| @@ -0,0 +1,88 @@ | |||
| 1 | + | // SPDX-License-Identifier: MIT | |
| 2 | + | ||
| 3 | + | package workflow | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "bnema.dev/ermite-actions/template" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | // convertContainer validates a job container or, when service is set, a | |
| 10 | + | // service container. A token that contains an expression is not checked, as | |
| 11 | + | // in the reference. | |
| 12 | + | func (c *converter) convertContainer(t template.Token, service bool) { | |
| 13 | + | if template.HasExpression(t) { | |
| 14 | + | return | |
| 15 | + | } | |
| 16 | + | if _, ok := t.(*template.String); ok { | |
| 17 | + | return | |
| 18 | + | } | |
| 19 | + | var image template.Token | |
| 20 | + | c.handle(t, func() { | |
| 21 | + | m := asMap(t, "container item") | |
| 22 | + | for _, p := range m.Pairs { | |
| 23 | + | key := asString(p.Key, "container item key") | |
| 24 | + | v := p.Value | |
| 25 | + | switch key.Value { | |
| 26 | + | case "image": | |
| 27 | + | image = asString(v, "container image") | |
| 28 | + | case "credentials": | |
| 29 | + | c.convertCredentials(v) | |
| 30 | + | case "env": | |
| 31 | + | for _, e := range asMap(v, "container env").Pairs { | |
| 32 | + | asString(e.Key, "container env value") | |
| 33 | + | } | |
| 34 | + | case "ports": | |
| 35 | + | for _, it := range asSeq(v, "container ports").Items { | |
| 36 | + | asString(it, "container port") | |
| 37 | + | } | |
| 38 | + | case "volumes": | |
| 39 | + | for _, it := range asSeq(v, "container volumes").Items { | |
| 40 | + | asString(it, "container volume") | |
| 41 | + | } | |
| 42 | + | case "options": | |
| 43 | + | asString(v, "container options") | |
| 44 | + | case "entrypoint": | |
| 45 | + | if !service { | |
| 46 | + | c.ctx.Errorf(key, "Unexpected container item key: %s", key.Value) | |
| 47 | + | continue | |
| 48 | + | } | |
| 49 | + | asString(v, "container entrypoint") | |
| 50 | + | case "command": | |
| 51 | + | if !service { | |
| 52 | + | c.ctx.Errorf(key, "Unexpected container item key: %s", key.Value) | |
| 53 | + | continue | |
| 54 | + | } | |
| 55 | + | asString(v, "container command") | |
| 56 | + | default: | |
| 57 | + | c.ctx.Errorf(key, "Unexpected container item key: %s", key.Value) | |
| 58 | + | } | |
| 59 | + | } | |
| 60 | + | }) | |
| 61 | + | if image == nil { | |
| 62 | + | if _, isMap := t.(*template.Mapping); isMap { | |
| 63 | + | c.ctx.ErrorAt(t, "Container image cannot be empty") | |
| 64 | + | } | |
| 65 | + | } | |
| 66 | + | } | |
| 67 | + | ||
| 68 | + | func (c *converter) convertCredentials(t template.Token) { | |
| 69 | + | for _, p := range asMap(t, "credentials").Pairs { | |
| 70 | + | key := asString(p.Key, "credentials item") | |
| 71 | + | switch key.Value { | |
| 72 | + | case "username": | |
| 73 | + | asString(p.Value, "credentials username") | |
| 74 | + | case "password": | |
| 75 | + | asString(p.Value, "credentials password") | |
| 76 | + | default: | |
| 77 | + | c.ctx.Errorf(key, "credentials key %s", key.Value) | |
| 78 | + | } | |
| 79 | + | } | |
| 80 | + | } | |
| 81 | + | ||
| 82 | + | // convertServices validates the services mapping of a job. | |
| 83 | + | func (c *converter) convertServices(t template.Token) { | |
| 84 | + | for _, p := range asMap(t, "services").Pairs { | |
| 85 | + | asString(p.Key, "service key") | |
| 86 | + | c.convertContainer(p.Value, true) | |
| 87 | + | } | |
| 88 | + | } | |
A workflow/convert_jobs.go +184 -0
| @@ -0,0 +1,184 @@ | |||
| 1 | + | // SPDX-License-Identifier: MIT | |
| 2 | + | ||
| 3 | + | package workflow | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "bnema.dev/ermite-actions/template" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | type needsNode struct { | |
| 10 | + | name string | |
| 11 | + | needs []*template.String | |
| 12 | + | } | |
| 13 | + | ||
| 14 | + | // convertJobs is a port of convertJobs: every job is converted on its own, a | |
| 15 | + | // job that fails is dropped after its error is recorded, then the needs graph | |
| 16 | + | // is checked. | |
| 17 | + | func (c *converter) convertJobs(t template.Token) []*Job { | |
| 18 | + | m, ok := t.(*template.Mapping) | |
| 19 | + | if !ok { | |
| 20 | + | c.ctx.ErrorAt(t, "Invalid format for jobs") | |
| 21 | + | return nil | |
| 22 | + | } | |
| 23 | + | var result []*Job | |
| 24 | + | var satisfied, unsatisfied []*needsNode | |
| 25 | + | for _, p := range m.Pairs { | |
| 26 | + | key := asString(p.Key, "job name") | |
| 27 | + | var job *Job | |
| 28 | + | c.handle(m, func() { | |
| 29 | + | job = c.convertJob(key, asMap(p.Value, "job "+key.Value)) | |
| 30 | + | }) | |
| 31 | + | if job == nil { | |
| 32 | + | continue | |
| 33 | + | } | |
| 34 | + | result = append(result, job) | |
| 35 | + | n := &needsNode{name: job.ID, needs: append([]*template.String(nil), job.NeedTokens...)} | |
| 36 | + | if len(n.needs) > 0 { | |
| 37 | + | unsatisfied = append(unsatisfied, n) | |
| 38 | + | } else { | |
| 39 | + | satisfied = append(satisfied, n) | |
| 40 | + | } | |
| 41 | + | } | |
| 42 | + | c.validateNeeds(m, result, satisfied, unsatisfied) | |
| 43 | + | return result | |
| 44 | + | } | |
| 45 | + | ||
| 46 | + | // validateNeeds reports jobs that can never start: it removes satisfied | |
| 47 | + | // dependencies in the order the reference does and complains about what is | |
| 48 | + | // left. | |
| 49 | + | func (c *converter) validateNeeds(t template.Token, jobs []*Job, satisfied, unsatisfied []*needsNode) { | |
| 50 | + | if len(satisfied) == 0 { | |
| 51 | + | c.ctx.ErrorAt(t, "The workflow must contain at least one job with no dependencies.") | |
| 52 | + | return | |
| 53 | + | } | |
| 54 | + | for len(satisfied) > 0 { | |
| 55 | + | current := satisfied[0] | |
| 56 | + | satisfied = satisfied[1:] | |
| 57 | + | for i := len(unsatisfied) - 1; i >= 0; i-- { | |
| 58 | + | u := unsatisfied[i] | |
| 59 | + | for j := len(u.needs) - 1; j >= 0; j-- { | |
| 60 | + | if u.needs[j].Value != current.name { | |
| 61 | + | continue | |
| 62 | + | } | |
| 63 | + | u.needs = append(u.needs[:j], u.needs[j+1:]...) | |
| 64 | + | if len(u.needs) == 0 { | |
| 65 | + | satisfied = append(satisfied, u) | |
| 66 | + | unsatisfied = append(unsatisfied[:i], unsatisfied[i+1:]...) | |
| 67 | + | } | |
| 68 | + | } | |
| 69 | + | } | |
| 70 | + | } | |
| 71 | + | if len(unsatisfied) == 0 { | |
| 72 | + | return | |
| 73 | + | } | |
| 74 | + | known := map[string]bool{} | |
| 75 | + | for _, j := range jobs { | |
| 76 | + | known[j.ID] = true | |
| 77 | + | } | |
| 78 | + | for _, u := range unsatisfied { | |
| 79 | + | for _, need := range u.needs { | |
| 80 | + | if known[need.Value] { | |
| 81 | + | c.ctx.Errorf(need, "Job '%s' depends on job '%s' which creates a cycle in the dependency graph.", u.name, need.Value) | |
| 82 | + | } else { | |
| 83 | + | c.ctx.Errorf(need, "Job '%s' depends on unknown job '%s'.", u.name, need.Value) | |
| 84 | + | } | |
| 85 | + | } | |
| 86 | + | } | |
| 87 | + | } | |
| 88 | + | ||
| 89 | + | // convertJob is a port of convertJob. | |
| 90 | + | func (c *converter) convertJob(key *template.String, m *template.Mapping) *Job { | |
| 91 | + | if msg := c.jobIDs.tryAddKnownID(key.Value); msg != "" { | |
| 92 | + | c.ctx.ErrorAt(key, msg) | |
| 93 | + | } | |
| 94 | + | j := &Job{ID: key.Value, IDToken: key} | |
| 95 | + | var name template.Token | |
| 96 | + | needsSeen := false | |
| 97 | + | for _, p := range m.Pairs { | |
| 98 | + | prop := asString(p.Key, "job property name") | |
| 99 | + | v := p.Value | |
| 100 | + | switch prop.Value { | |
| 101 | + | case "concurrency": | |
| 102 | + | c.handle(v, func() { c.convertConcurrency(v) }) | |
| 103 | + | j.Concurrency = v | |
| 104 | + | case "container": | |
| 105 | + | c.convertContainer(v, false) | |
| 106 | + | j.Container = v | |
| 107 | + | case "env": | |
| 108 | + | c.handle(v, func() { j.Env = asMap(v, "job env") }) | |
| 109 | + | case "environment": | |
| 110 | + | c.handle(v, func() { c.convertEnvironment(v) }) | |
| 111 | + | j.Environment = v | |
| 112 | + | case "if": | |
| 113 | + | j.If = c.convertIf(v, "job-if") | |
| 114 | + | case "name": | |
| 115 | + | name = asScalar(v, "job name") | |
| 116 | + | case "needs": | |
| 117 | + | needsSeen = true | |
| 118 | + | j.NeedTokens = nil | |
| 119 | + | if s, ok := v.(*template.String); ok { | |
| 120 | + | j.NeedTokens = append(j.NeedTokens, asString(s, "job needs id")) | |
| 121 | + | } | |
| 122 | + | if seq, ok := v.(*template.Sequence); ok { | |
| 123 | + | for _, it := range seq.Items { | |
| 124 | + | j.NeedTokens = append(j.NeedTokens, asString(it, "job needs id")) | |
| 125 | + | } | |
| 126 | + | } | |
| 127 | + | case "outputs": | |
| 128 | + | c.handle(v, func() { j.Outputs = asMap(v, "job outputs") }) | |
| 129 | + | case "runs-on": | |
| 130 | + | c.handle(v, func() { c.convertRunsOn(v) }) | |
| 131 | + | j.RunsOn = v | |
| 132 | + | case "services": | |
| 133 | + | c.convertServices(v) | |
| 134 | + | j.Services = v | |
| 135 | + | case "snapshot": | |
| 136 | + | j.Snapshot = v | |
| 137 | + | case "steps": | |
| 138 | + | j.Steps = c.convertSteps(v) | |
| 139 | + | case "strategy": | |
| 140 | + | j.Strategy = v | |
| 141 | + | case "uses": | |
| 142 | + | j.Uses = asString(v, "job uses value") | |
| 143 | + | case "with": | |
| 144 | + | c.handle(v, func() { j.With = asMap(v, "uses-with value") }) | |
| 145 | + | case "secrets": | |
| 146 | + | if s, ok := v.(*template.String); ok && s.Value == "inherit" { | |
| 147 | + | j.InheritSecrets = true | |
| 148 | + | } else { | |
| 149 | + | c.handle(v, func() { j.Secrets = asMap(v, "uses-secrets value") }) | |
| 150 | + | } | |
| 151 | + | case "timeout-minutes": | |
| 152 | + | j.TimeoutMinutes = v | |
| 153 | + | case "cancel-timeout-minutes": | |
| 154 | + | j.CancelTimeoutMinutes = v | |
| 155 | + | case "continue-on-error": | |
| 156 | + | j.ContinueOnError = v | |
| 157 | + | case "defaults": | |
| 158 | + | j.Defaults = convertDefaults(v) | |
| 159 | + | case "permissions": | |
| 160 | + | j.Permissions = convertPermissions(v) | |
| 161 | + | } | |
| 162 | + | } | |
| 163 | + | _ = needsSeen | |
| 164 | + | for _, n := range j.NeedTokens { | |
| 165 | + | j.Needs = append(j.Needs, n.Value) | |
| 166 | + | } | |
| 167 | + | if j.If == nil { | |
| 168 | + | j.If = c.defaultIf("job-if") | |
| 169 | + | } | |
| 170 | + | switch { | |
| 171 | + | case name == nil: | |
| 172 | + | j.Name = key | |
| 173 | + | case isEmptyString(name): | |
| 174 | + | j.Name = key | |
| 175 | + | default: | |
| 176 | + | j.Name, j.HasName = name, true | |
| 177 | + | } | |
| 178 | + | return j | |
| 179 | + | } | |
| 180 | + | ||
| 181 | + | func isEmptyString(t template.Token) bool { | |
| 182 | + | s, ok := t.(*template.String) | |
| 183 | + | return ok && s.Value == "" | |
| 184 | + | } | |
A workflow/convert_misc.go +192 -0
| @@ -0,0 +1,192 @@ | |||
| 1 | + | // SPDX-License-Identifier: MIT | |
| 2 | + | ||
| 3 | + | package workflow | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "fmt" | |
| 7 | + | "strings" | |
| 8 | + | ||
| 9 | + | "bnema.dev/ermite-actions/expr" | |
| 10 | + | "bnema.dev/ermite-actions/template" | |
| 11 | + | ) | |
| 12 | + | ||
| 13 | + | func sprintf(format string, a ...any) string { | |
| 14 | + | if len(a) == 0 { | |
| 15 | + | return format | |
| 16 | + | } | |
| 17 | + | return fmt.Sprintf(format, a...) | |
| 18 | + | } | |
| 19 | + | ||
| 20 | + | // convertConcurrency validates a concurrency token. Expressions are skipped. | |
| 21 | + | func (c *converter) convertConcurrency(t template.Token) { | |
| 22 | + | if template.IsExpression(t) { | |
| 23 | + | return | |
| 24 | + | } | |
| 25 | + | if _, ok := t.(*template.String); ok { | |
| 26 | + | return | |
| 27 | + | } | |
| 28 | + | m := asMap(t, "concurrency group") | |
| 29 | + | for _, p := range m.Pairs { | |
| 30 | + | name := asString(p.Key, "concurrency group key") | |
| 31 | + | if template.IsExpression(p.Key) || template.IsExpression(p.Value) { | |
| 32 | + | continue | |
| 33 | + | } | |
| 34 | + | switch name.Value { | |
| 35 | + | case "group": | |
| 36 | + | asString(p.Value, "concurrency group") | |
| 37 | + | case "cancel-in-progress": | |
| 38 | + | asBool(p.Value, "cancel-in-progress") | |
| 39 | + | case "queue": | |
| 40 | + | asString(p.Value, "queue") | |
| 41 | + | default: | |
| 42 | + | c.ctx.Errorf(name, "Invalid property name: %s", name.Value) | |
| 43 | + | } | |
| 44 | + | } | |
| 45 | + | } | |
| 46 | + | ||
| 47 | + | // convertEnvironment validates a job environment token. | |
| 48 | + | func (c *converter) convertEnvironment(t template.Token) { | |
| 49 | + | if template.IsExpression(t) || template.IsScalar(t) { | |
| 50 | + | return | |
| 51 | + | } | |
| 52 | + | m := asMap(t, "job environment") | |
| 53 | + | for _, p := range m.Pairs { | |
| 54 | + | name := asString(p.Key, "job environment key") | |
| 55 | + | if template.IsExpression(p.Key) || template.IsExpression(p.Value) { | |
| 56 | + | continue | |
| 57 | + | } | |
| 58 | + | switch name.Value { | |
| 59 | + | case "name": | |
| 60 | + | asScalar(p.Value, "job environment name key") | |
| 61 | + | case "deployment": | |
| 62 | + | asBool(p.Value, "job environment deployment") | |
| 63 | + | } | |
| 64 | + | } | |
| 65 | + | } | |
| 66 | + | ||
| 67 | + | // convertRunsOn validates a runs-on token. | |
| 68 | + | func (c *converter) convertRunsOn(t template.Token) { | |
| 69 | + | labels := runsOnLabels(t) | |
| 70 | + | _ = labels | |
| 71 | + | m, ok := t.(*template.Mapping) | |
| 72 | + | if !ok { | |
| 73 | + | return | |
| 74 | + | } | |
| 75 | + | for _, p := range m.Pairs { | |
| 76 | + | key := asString(p.Key, "job runs-on property name") | |
| 77 | + | switch key.Value { | |
| 78 | + | case "group": | |
| 79 | + | if template.IsExpression(p.Value) { | |
| 80 | + | continue | |
| 81 | + | } | |
| 82 | + | group := asString(p.Value, "job runs-on group name").Value | |
| 83 | + | names := strings.Split(group, "/") | |
| 84 | + | switch len(names) { | |
| 85 | + | case 1: | |
| 86 | + | case 2: | |
| 87 | + | switch names[0] { | |
| 88 | + | case "org", "organization", "ent", "enterprise": | |
| 89 | + | default: | |
| 90 | + | c.ctx.Errorf(p.Value, "Invalid runs-on group name '%s. Please use 'organization/' or 'enterprise/' prefix to target a single runner group.'", group) | |
| 91 | + | continue | |
| 92 | + | } | |
| 93 | + | if names[1] == "" { | |
| 94 | + | c.ctx.Errorf(p.Value, "Invalid runs-on group name '%s'.", group) | |
| 95 | + | } | |
| 96 | + | default: | |
| 97 | + | c.ctx.Errorf(p.Value, "Invalid runs-on group name '%s. Please use 'organization/' or 'enterprise/' prefix to target a single runner group.'", group) | |
| 98 | + | } | |
| 99 | + | case "labels": | |
| 100 | + | runsOnLabels(p.Value) | |
| 101 | + | } | |
| 102 | + | } | |
| 103 | + | } | |
| 104 | + | ||
| 105 | + | // runsOnLabels collects the literal labels of a runs-on token, asserting that | |
| 106 | + | // the items are strings. | |
| 107 | + | func runsOnLabels(t template.Token) []string { | |
| 108 | + | if template.IsExpression(t) { | |
| 109 | + | return nil | |
| 110 | + | } | |
| 111 | + | switch t := t.(type) { | |
| 112 | + | case *template.String: | |
| 113 | + | return []string{t.Value} | |
| 114 | + | case *template.Sequence: | |
| 115 | + | var out []string | |
| 116 | + | for _, it := range t.Items { | |
| 117 | + | if template.IsExpression(it) { | |
| 118 | + | continue | |
| 119 | + | } | |
| 120 | + | out = append(out, asString(it, "job runs-on label sequence item").Value) | |
| 121 | + | } | |
| 122 | + | return out | |
| 123 | + | } | |
| 124 | + | return nil | |
| 125 | + | } | |
| 126 | + | ||
| 127 | + | // statusFunctions are the functions that make a condition a status check. | |
| 128 | + | var statusFunctions = map[string]bool{"success": true, "failure": true, "cancelled": true, "always": true} | |
| 129 | + | ||
| 130 | + | func hasStatusFunction(n expr.Node) bool { | |
| 131 | + | switch n := n.(type) { | |
| 132 | + | case *expr.FunctionCall: | |
| 133 | + | if statusFunctions[strings.ToLower(n.Name.Lexeme)] { | |
| 134 | + | return true | |
| 135 | + | } | |
| 136 | + | for _, a := range n.Args { | |
| 137 | + | if hasStatusFunction(a) { | |
| 138 | + | return true | |
| 139 | + | } | |
| 140 | + | } | |
| 141 | + | case *expr.Binary: | |
| 142 | + | return hasStatusFunction(n.Left) || hasStatusFunction(n.Right) | |
| 143 | + | case *expr.Unary: | |
| 144 | + | return hasStatusFunction(n.Expr) | |
| 145 | + | case *expr.Logical: | |
| 146 | + | for _, a := range n.Args { | |
| 147 | + | if hasStatusFunction(a) { | |
| 148 | + | return true | |
| 149 | + | } | |
| 150 | + | } | |
| 151 | + | case *expr.Grouping: | |
| 152 | + | return hasStatusFunction(n.Group) | |
| 153 | + | case *expr.IndexAccess: | |
| 154 | + | return hasStatusFunction(n.Expr) || hasStatusFunction(n.Index) | |
| 155 | + | } | |
| 156 | + | return false | |
| 157 | + | } | |
| 158 | + | ||
| 159 | + | // convertIf converts an if condition: the result always contains a status | |
| 160 | + | // function, wrapping the condition as "success() && (cond)" when it has none. | |
| 161 | + | // A condition that does not parse is recorded as an error and returns nil. | |
| 162 | + | func (c *converter) convertIf(t template.Token, defName string) *template.BasicExpression { | |
| 163 | + | scalar := asScalar(t, "if condition") | |
| 164 | + | def := t.Definition() | |
| 165 | + | if def == nil { | |
| 166 | + | def = c.schema.Info(defName) | |
| 167 | + | } | |
| 168 | + | var condition string | |
| 169 | + | switch s := scalar.(type) { | |
| 170 | + | case *template.BasicExpression: | |
| 171 | + | condition = s.Expression | |
| 172 | + | default: | |
| 173 | + | condition = strings.TrimSpace(asString(scalar, "if condition").Value) | |
| 174 | + | } | |
| 175 | + | final := "success()" | |
| 176 | + | if condition != "" { | |
| 177 | + | final = condition | |
| 178 | + | if node, err := expr.Parse(condition, def.NamedContexts(), def.Functions()); err == nil && !hasStatusFunction(node) { | |
| 179 | + | final = "success() && (" + condition + ")" | |
| 180 | + | } | |
| 181 | + | } | |
| 182 | + | if _, err := expr.Parse(final, def.NamedContexts(), def.Functions()); err != nil { | |
| 183 | + | c.ctx.ErrorAt(t, err.Error()) | |
| 184 | + | return nil | |
| 185 | + | } | |
| 186 | + | return template.NewExpression(origin(t), final, def) | |
| 187 | + | } | |
| 188 | + | ||
| 189 | + | // defaultIf is the condition of a job or step that has none. | |
| 190 | + | func (c *converter) defaultIf(defName string) *template.BasicExpression { | |
| 191 | + | return template.NewExpression(template.Origin{}, "success()", c.schema.Info(defName)) | |
| 192 | + | } | |
A workflow/convert_steps.go +126 -0
| @@ -0,0 +1,126 @@ | |||
| 1 | + | // SPDX-License-Identifier: MIT | |
| 2 | + | ||
| 3 | + | package workflow | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "strings" | |
| 7 | + | ||
| 8 | + | "bnema.dev/ermite-actions/template" | |
| 9 | + | ) | |
| 10 | + | ||
| 11 | + | // convertSteps is a port of convertSteps: steps that fail to convert are | |
| 12 | + | // dropped, ids written in the file are checked, and the others get a | |
| 13 | + | // generated id. | |
| 14 | + | func (c *converter) convertSteps(t template.Token) []*Step { | |
| 15 | + | seq, ok := t.(*template.Sequence) | |
| 16 | + | if !ok { | |
| 17 | + | c.ctx.ErrorAt(t, "Invalid format for steps") | |
| 18 | + | return nil | |
| 19 | + | } | |
| 20 | + | ids := newIDBuilder() | |
| 21 | + | var result []*Step | |
| 22 | + | for _, it := range seq.Items { | |
| 23 | + | c.handle(seq, func() { | |
| 24 | + | if s := c.convertStep(ids, it); s != nil { | |
| 25 | + | result = append(result, s) | |
| 26 | + | } | |
| 27 | + | }) | |
| 28 | + | } | |
| 29 | + | for _, s := range result { | |
| 30 | + | if s.ID != "" { | |
| 31 | + | continue | |
| 32 | + | } | |
| 33 | + | id := "" | |
| 34 | + | if s.Uses != nil { | |
| 35 | + | id = actionStepID(s.Uses.Value) | |
| 36 | + | } | |
| 37 | + | if id == "" { | |
| 38 | + | id = "run" | |
| 39 | + | } | |
| 40 | + | ids.appendSegment("__" + id) | |
| 41 | + | s.ID = ids.build() | |
| 42 | + | } | |
| 43 | + | return result | |
| 44 | + | } | |
| 45 | + | ||
| 46 | + | func (c *converter) convertStep(ids *idBuilder, t template.Token) *Step { | |
| 47 | + | m := asMap(t, "steps item") | |
| 48 | + | s := &Step{Pos: t.Pos()} | |
| 49 | + | var uses *template.String | |
| 50 | + | var run template.Token | |
| 51 | + | for _, p := range m.Pairs { | |
| 52 | + | key := asString(p.Key, "steps item key") | |
| 53 | + | v := p.Value | |
| 54 | + | switch key.Value { | |
| 55 | + | case "id": | |
| 56 | + | id := asString(v, "steps item id") | |
| 57 | + | if msg := ids.tryAddKnownID(id.Value); msg != "" { | |
| 58 | + | c.ctx.ErrorAt(id, msg) | |
| 59 | + | } | |
| 60 | + | s.ID = id.Value | |
| 61 | + | s.Explicit = id.Value != "" | |
| 62 | + | case "name": | |
| 63 | + | s.Name = asScalar(v, "steps item name") | |
| 64 | + | case "run": | |
| 65 | + | run = asScalar(v, "steps item run") | |
| 66 | + | case "uses": | |
| 67 | + | uses = asString(v, "steps item uses") | |
| 68 | + | case "env": | |
| 69 | + | s.Env = asMap(v, "step env") | |
| 70 | + | case "if": | |
| 71 | + | s.If = c.convertIf(v, "step-if") | |
| 72 | + | case "continue-on-error": | |
| 73 | + | if !template.IsExpression(v) { | |
| 74 | + | asBool(v, "steps item continue-on-error") | |
| 75 | + | } else { | |
| 76 | + | asScalar(v, "steps item continue-on-error") | |
| 77 | + | } | |
| 78 | + | s.ContinueOnError = v | |
| 79 | + | case "shell": | |
| 80 | + | s.Shell = v | |
| 81 | + | case "working-directory": | |
| 82 | + | s.WorkingDirectory = v | |
| 83 | + | case "timeout-minutes": | |
| 84 | + | s.TimeoutMinutes = v | |
| 85 | + | case "with": | |
| 86 | + | if wm, ok := v.(*template.Mapping); ok { | |
| 87 | + | s.With = wm | |
| 88 | + | } | |
| 89 | + | } | |
| 90 | + | } | |
| 91 | + | switch { | |
| 92 | + | case run != nil: | |
| 93 | + | s.Run = run | |
| 94 | + | case uses != nil: | |
| 95 | + | s.Uses = uses | |
| 96 | + | default: | |
| 97 | + | c.ctx.ErrorAt(t, "Expected one of uses or run to be defined") | |
| 98 | + | return nil | |
| 99 | + | } | |
| 100 | + | if s.If == nil { | |
| 101 | + | s.If = c.defaultIf("step-if") | |
| 102 | + | } | |
| 103 | + | return s | |
| 104 | + | } | |
| 105 | + | ||
| 106 | + | // actionStepID is the id segment generated for a uses step without an id. | |
| 107 | + | func actionStepID(uses string) string { | |
| 108 | + | switch { | |
| 109 | + | case strings.HasPrefix(uses, "docker://"): | |
| 110 | + | return uses[len("docker://"):] | |
| 111 | + | case strings.HasPrefix(uses, "./"), strings.HasPrefix(uses, ".\\"), strings.HasPrefix(uses, "$/"): | |
| 112 | + | return "self" | |
| 113 | + | } | |
| 114 | + | segments := strings.Split(uses, "@") | |
| 115 | + | if len(segments) != 2 { | |
| 116 | + | return "" | |
| 117 | + | } | |
| 118 | + | var path []string | |
| 119 | + | for _, p := range strings.FieldsFunc(segments[0], func(r rune) bool { return r == '/' || r == '\\' }) { | |
| 120 | + | path = append(path, p) | |
| 121 | + | } | |
| 122 | + | if len(path) >= 2 && path[0] != "" && path[1] != "" && segments[1] != "" { | |
| 123 | + | return path[0] + "/" + path[1] | |
| 124 | + | } | |
| 125 | + | return "" | |
| 126 | + | } | |
A workflow/convert_test.go +172 -0
| @@ -0,0 +1,172 @@ | |||
| 1 | + | // SPDX-License-Identifier: MIT | |
| 2 | + | ||
| 3 | + | package workflow | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "encoding/json" | |
| 7 | + | "os" | |
| 8 | + | "path/filepath" | |
| 9 | + | "reflect" | |
| 10 | + | "sort" | |
| 11 | + | "strings" | |
| 12 | + | "testing" | |
| 13 | + | ) | |
| 14 | + | ||
| 15 | + | // refResult is what testdata/convert.json records for one file: the output of | |
| 16 | + | // @actions/workflow-parser 0.3.61 (see gen/convert.mjs). | |
| 17 | + | type refResult struct { | |
| 18 | + | Errors []string `json:"errors"` | |
| 19 | + | Jobs []struct { | |
| 20 | + | ID string `json:"id"` | |
| 21 | + | Name string `json:"name"` | |
| 22 | + | If string `json:"if"` | |
| 23 | + | Needs []string `json:"needs"` | |
| 24 | + | Steps []struct { | |
| 25 | + | ID string `json:"id"` | |
| 26 | + | If string `json:"if"` | |
| 27 | + | } `json:"steps"` | |
| 28 | + | } `json:"jobs"` | |
| 29 | + | Events map[string]any `json:"events"` | |
| 30 | + | } | |
| 31 | + | ||
| 32 | + | func loadRef(t *testing.T) map[string]refResult { | |
| 33 | + | t.Helper() | |
| 34 | + | data, err := os.ReadFile("testdata/convert.json") | |
| 35 | + | if err != nil { | |
| 36 | + | t.Fatal(err) | |
| 37 | + | } | |
| 38 | + | var m map[string]refResult | |
| 39 | + | if err := json.Unmarshal(data, &m); err != nil { | |
| 40 | + | t.Fatal(err) | |
| 41 | + | } | |
| 42 | + | return m | |
| 43 | + | } | |
| 44 | + | ||
| 45 | + | func errorTexts(errs []Error) []string { | |
| 46 | + | out := make([]string, len(errs)) | |
| 47 | + | for i := range errs { | |
| 48 | + | out[i] = errs[i].Error() | |
| 49 | + | } | |
| 50 | + | return out | |
| 51 | + | } | |
| 52 | + | ||
| 53 | + | // isYAMLError recognises the reference's YAML syntax errors, whose text is | |
| 54 | + | // the JavaScript library's and cannot be reproduced word for word. | |
| 55 | + | func isYAMLError(s string) bool { return strings.Contains(s, " at line ") } | |
| 56 | + | ||
| 57 | + | func TestConvertAgainstReference(t *testing.T) { | |
| 58 | + | ref := loadRef(t) | |
| 59 | + | files, err := filepath.Glob("testdata/convert/*.yml") | |
| 60 | + | if err != nil || len(files) == 0 { | |
| 61 | + | t.Fatalf("no corpus: %v", err) | |
| 62 | + | } | |
| 63 | + | for _, f := range files { | |
| 64 | + | name := filepath.Base(f) | |
| 65 | + | want, ok := ref[name] | |
| 66 | + | if !ok { | |
| 67 | + | t.Errorf("%s: missing from convert.json", name) | |
| 68 | + | continue | |
| 69 | + | } | |
| 70 | + | t.Run(strings.TrimSuffix(name, ".yml"), func(t *testing.T) { | |
| 71 | + | content, err := os.ReadFile(f) | |
| 72 | + | if err != nil { | |
| 73 | + | t.Fatal(err) | |
| 74 | + | } | |
| 75 | + | w, errs := Parse(name, content) | |
| 76 | + | got := errorTexts(errs) | |
| 77 | + | if len(want.Errors) == 1 && isYAMLError(want.Errors[0]) { | |
| 78 | + | // Syntax errors: the Go parser words them differently. Only | |
| 79 | + | // the fact that there is exactly one, on the file, is checked. | |
| 80 | + | if len(got) != 1 || !strings.HasPrefix(got[0], name+": ") { | |
| 81 | + | t.Errorf("syntax error: got %q, reference %q", got, want.Errors) | |
| 82 | + | } | |
| 83 | + | if w != nil { | |
| 84 | + | t.Error("workflow returned with errors") | |
| 85 | + | } | |
| 86 | + | return | |
| 87 | + | } | |
| 88 | + | if !reflect.DeepEqual(nonNil(got), nonNil(want.Errors)) { | |
| 89 | + | t.Errorf("errors differ\n got: %q\nwant: %q", got, want.Errors) | |
| 90 | + | } | |
| 91 | + | if len(want.Errors) > 0 { | |
| 92 | + | if w != nil { | |
| 93 | + | t.Error("workflow returned with errors") | |
| 94 | + | } | |
| 95 | + | return | |
| 96 | + | } | |
| 97 | + | if w == nil { | |
| 98 | + | t.Fatal("no workflow and no errors") | |
| 99 | + | } | |
| 100 | + | if len(w.Jobs) != len(want.Jobs) { | |
| 101 | + | t.Fatalf("jobs: got %d, reference %d", len(w.Jobs), len(want.Jobs)) | |
| 102 | + | } | |
| 103 | + | for i, wj := range want.Jobs { | |
| 104 | + | j := w.Jobs[i] | |
| 105 | + | if j.ID != wj.ID || j.Name.String() != wj.Name || j.If.Expression != wj.If { | |
| 106 | + | t.Errorf("job %d: got id=%q name=%q if=%q, reference id=%q name=%q if=%q", i, j.ID, j.Name, j.If.Expression, wj.ID, wj.Name, wj.If) | |
| 107 | + | } | |
| 108 | + | if !reflect.DeepEqual(nonNil(j.Needs), nonNil(wj.Needs)) && !(j.IsReusable() && len(wj.Needs) == 0) { | |
| 109 | + | t.Errorf("job %s needs: got %v, reference %v", j.ID, j.Needs, wj.Needs) | |
| 110 | + | } | |
| 111 | + | if len(j.Steps) != len(wj.Steps) { | |
| 112 | + | t.Errorf("job %s: %d steps, reference %d", j.ID, len(j.Steps), len(wj.Steps)) | |
| 113 | + | continue | |
| 114 | + | } | |
| 115 | + | for k, ws := range wj.Steps { | |
| 116 | + | if j.Steps[k].ID != ws.ID || j.Steps[k].If.Expression != ws.If { | |
| 117 | + | t.Errorf("job %s step %d: got id=%q if=%q, reference id=%q if=%q", j.ID, k, j.Steps[k].ID, j.Steps[k].If.Expression, ws.ID, ws.If) | |
| 118 | + | } | |
| 119 | + | } | |
| 120 | + | } | |
| 121 | + | checkEvents(t, w, want.Events) | |
| 122 | + | }) | |
| 123 | + | } | |
| 124 | + | } | |
| 125 | + | ||
| 126 | + | func nonNil(s []string) []string { | |
| 127 | + | if s == nil { | |
| 128 | + | return []string{} | |
| 129 | + | } | |
| 130 | + | return s | |
| 131 | + | } | |
| 132 | + | ||
| 133 | + | // checkEvents compares the filters of the events with the reference. | |
| 134 | + | func checkEvents(t *testing.T, w *Workflow, want map[string]any) { | |
| 135 | + | t.Helper() | |
| 136 | + | var names []string | |
| 137 | + | for _, e := range w.On.Events { | |
| 138 | + | names = append(names, e.Name) | |
| 139 | + | } | |
| 140 | + | var wnames []string | |
| 141 | + | for k := range want { | |
| 142 | + | wnames = append(wnames, k) | |
| 143 | + | } | |
| 144 | + | sort.Strings(names) | |
| 145 | + | sort.Strings(wnames) | |
| 146 | + | if !reflect.DeepEqual(names, wnames) { | |
| 147 | + | t.Errorf("events: got %v, reference %v", names, wnames) | |
| 148 | + | return | |
| 149 | + | } | |
| 150 | + | for _, e := range w.On.Events { | |
| 151 | + | wf, _ := want[e.Name].(map[string]any) | |
| 152 | + | for key, got := range map[string][]string{ | |
| 153 | + | "branches": e.Branches, "branches-ignore": e.BranchesIgnore, "tags": e.Tags, | |
| 154 | + | "tags-ignore": e.TagsIgnore, "paths": e.Paths, "paths-ignore": e.PathsIgnore, "types": e.Types, | |
| 155 | + | } { | |
| 156 | + | raw, present := wf[key] | |
| 157 | + | if (got != nil) != present { | |
| 158 | + | t.Errorf("event %s filter %s: present=%v, reference present=%v", e.Name, key, got != nil, present) | |
| 159 | + | continue | |
| 160 | + | } | |
| 161 | + | if present { | |
| 162 | + | var ws []string | |
| 163 | + | for _, x := range raw.([]any) { | |
| 164 | + | ws = append(ws, x.(string)) | |
| 165 | + | } | |
| 166 | + | if !reflect.DeepEqual(nonNil(got), nonNil(ws)) { | |
| 167 | + | t.Errorf("event %s filter %s: got %v, reference %v", e.Name, key, got, ws) | |
| 168 | + | } | |
| 169 | + | } | |
| 170 | + | } | |
| 171 | + | } | |
| 172 | + | } | |
A workflow/cron.go +151 -0
| @@ -0,0 +1,151 @@ | |||
| 1 | + | // SPDX-License-Identifier: MIT | |
| 2 | + | ||
| 3 | + | package workflow | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "math" | |
| 7 | + | "regexp" | |
| 8 | + | "strconv" | |
| 9 | + | "strings" | |
| 10 | + | ) | |
| 11 | + | ||
| 12 | + | // Cron validation, ported from converter/cron.ts. | |
| 13 | + | ||
| 14 | + | type cronRange struct { | |
| 15 | + | min, max int | |
| 16 | + | names map[string]int | |
| 17 | + | } | |
| 18 | + | ||
| 19 | + | var ( | |
| 20 | + | cronMinute = cronRange{0, 59, nil} | |
| 21 | + | cronHour = cronRange{0, 23, nil} | |
| 22 | + | cronDOM = cronRange{1, 31, nil} | |
| 23 | + | cronMonth = cronRange{1, 12, map[string]int{"jan": 1, "feb": 2, "mar": 3, "apr": 4, "may": 5, "jun": 6, "jul": 7, "aug": 8, "sep": 9, "oct": 10, "nov": 11, "dec": 12}} | |
| 24 | + | cronDOW = cronRange{0, 6, map[string]int{"sun": 0, "mon": 1, "tue": 2, "wed": 3, "thu": 4, "fri": 5, "sat": 6}} | |
| 25 | + | ) | |
| 26 | + | ||
| 27 | + | func isValidCron(cron string) bool { | |
| 28 | + | parts := splitSpaces(cron) | |
| 29 | + | if len(parts) != 5 { | |
| 30 | + | return false | |
| 31 | + | } | |
| 32 | + | return validateCronPart(parts[0], cronMinute, true) && validateCronPart(parts[1], cronHour, true) && | |
| 33 | + | validateCronPart(parts[2], cronDOM, true) && validateCronPart(parts[3], cronMonth, true) && | |
| 34 | + | validateCronPart(parts[4], cronDOW, true) | |
| 35 | + | } | |
| 36 | + | ||
| 37 | + | // splitSpaces is JavaScript's split(/ +/): runs of spaces separate, other | |
| 38 | + | // whitespace does not, and leading or trailing spaces give empty parts. | |
| 39 | + | func splitSpaces(s string) []string { | |
| 40 | + | var parts []string | |
| 41 | + | start := 0 | |
| 42 | + | i := 0 | |
| 43 | + | for i < len(s) { | |
| 44 | + | if s[i] != ' ' { | |
| 45 | + | i++ | |
| 46 | + | continue | |
| 47 | + | } | |
| 48 | + | parts = append(parts, s[start:i]) | |
| 49 | + | for i < len(s) && s[i] == ' ' { | |
| 50 | + | i++ | |
| 51 | + | } | |
| 52 | + | start = i | |
| 53 | + | } | |
| 54 | + | return append(parts, s[start:]) | |
| 55 | + | } | |
| 56 | + | ||
| 57 | + | var jsDecimal = regexp.MustCompile(`^[+-]?(?:[0-9]+\.?[0-9]*|\.[0-9]+)(?:[eE][+-]?[0-9]+)?$`) | |
| 58 | + | ||
| 59 | + | // jsNumber is JavaScript's Number(string): surrounding white space is | |
| 60 | + | // ignored, an empty string is 0, and decimal, hexadecimal, octal and binary | |
| 61 | + | // literals and Infinity are numbers. ok is false for NaN. | |
| 62 | + | func jsNumber(v string) (float64, bool) { | |
| 63 | + | t := strings.Trim(v, " \t\n\v\f\r\u00a0\u2028\u2029\ufeff") | |
| 64 | + | switch { | |
| 65 | + | case t == "": | |
| 66 | + | return 0, true | |
| 67 | + | case jsDecimal.MatchString(t): | |
| 68 | + | f, err := strconv.ParseFloat(t, 64) | |
| 69 | + | return f, err == nil || math.IsInf(f, 0) | |
| 70 | + | case t == "Infinity" || t == "+Infinity": | |
| 71 | + | return math.Inf(1), true | |
| 72 | + | case t == "-Infinity": | |
| 73 | + | return math.Inf(-1), true | |
| 74 | + | case len(t) > 2 && t[0] == '0': | |
| 75 | + | base := map[byte]int{'x': 16, 'X': 16, 'o': 8, 'O': 8, 'b': 2, 'B': 2}[t[1]] | |
| 76 | + | if base == 0 { | |
| 77 | + | return 0, false | |
| 78 | + | } | |
| 79 | + | n, err := strconv.ParseUint(t[2:], base, 64) | |
| 80 | + | if err != nil { | |
| 81 | + | if ne, ok := err.(*strconv.NumError); ok && ne.Err == strconv.ErrRange { | |
| 82 | + | return math.Inf(1), true | |
| 83 | + | } | |
| 84 | + | return 0, false | |
| 85 | + | } | |
| 86 | + | return float64(n), true | |
| 87 | + | } | |
| 88 | + | return 0, false | |
| 89 | + | } | |
| 90 | + | ||
| 91 | + | func cronNumber(v string) (float64, bool) { return jsNumber(v) } | |
| 92 | + | ||
| 93 | + | func validateCronPart(value string, r cronRange, allowSeparators bool) bool { | |
| 94 | + | lower := strings.ToLower(value) | |
| 95 | + | if _, ok := r.names[lower]; ok { | |
| 96 | + | return true | |
| 97 | + | } | |
| 98 | + | if value == "*" { | |
| 99 | + | return true | |
| 100 | + | } | |
| 101 | + | if strings.Contains(value, ",") { | |
| 102 | + | if !allowSeparators { | |
| 103 | + | return false | |
| 104 | + | } | |
| 105 | + | for _, v := range strings.Split(value, ",") { | |
| 106 | + | if v == "" || !validateCronPart(v, r, true) { | |
| 107 | + | return false | |
| 108 | + | } | |
| 109 | + | } | |
| 110 | + | return true | |
| 111 | + | } | |
| 112 | + | if strings.Contains(value, "/") { | |
| 113 | + | if !allowSeparators { | |
| 114 | + | return false | |
| 115 | + | } | |
| 116 | + | parts := strings.Split(value, "/") | |
| 117 | + | start, step := parts[0], "" | |
| 118 | + | if len(parts) > 1 { | |
| 119 | + | step = parts[1] | |
| 120 | + | } | |
| 121 | + | n, ok := cronNumber(step) | |
| 122 | + | if len(parts) > 2 || !ok || n <= 0 || start == "" || step == "" { | |
| 123 | + | return false | |
| 124 | + | } | |
| 125 | + | return validateCronPart(start, r, true) && validateCronPart(step, r, false) | |
| 126 | + | } | |
| 127 | + | if strings.Contains(value, "-") { | |
| 128 | + | if !allowSeparators { | |
| 129 | + | return false | |
| 130 | + | } | |
| 131 | + | parts := strings.Split(value, "-") | |
| 132 | + | if len(parts) != 2 || parts[0] == "" || parts[1] == "" { | |
| 133 | + | return false | |
| 134 | + | } | |
| 135 | + | s, e := cronRangeValue(parts[0], r), cronRangeValue(parts[1], r) | |
| 136 | + | return validateCronPart(parts[0], r, false) && validateCronPart(parts[1], r, false) && e >= s | |
| 137 | + | } | |
| 138 | + | n, ok := cronNumber(value) | |
| 139 | + | return ok && n >= float64(r.min) && n <= float64(r.max) | |
| 140 | + | } | |
| 141 | + | ||
| 142 | + | func cronRangeValue(v string, r cronRange) float64 { | |
| 143 | + | if n, ok := r.names[strings.ToLower(v)]; ok { | |
| 144 | + | return float64(n) | |
| 145 | + | } | |
| 146 | + | n, ok := cronNumber(v) | |
| 147 | + | if !ok { | |
| 148 | + | return math.NaN() | |
| 149 | + | } | |
| 150 | + | return n | |
| 151 | + | } | |
A workflow/doc.go +17 -0
| @@ -0,0 +1,17 @@ | |||
| 1 | + | // SPDX-License-Identifier: MIT | |
| 2 | + | ||
| 3 | + | // Package workflow parses GitHub Actions workflow files into a model and | |
| 4 | + | // implements the planning rules that depend only on the file: validation, | |
| 5 | + | // job matrices, push trigger filters and skip directives in commit messages. | |
| 6 | + | // | |
| 7 | + | // Parse is a port of the converter of the npm package | |
| 8 | + | // @actions/workflow-parser 0.3.61 (MIT, GitHub, actions/languageservices) on | |
| 9 | + | // top of package template. Values that GitHub computes later, such as | |
| 10 | + | // runs-on, strategy, names and conditions, stay unevaluated template tokens | |
| 11 | + | // in the model; package template evaluates them. | |
| 12 | + | // | |
| 13 | + | // The trigger rules (MatchPush, SkipCI) and the matrix expansion | |
| 14 | + | // (ExpandMatrix) are written from GitHub's documentation and from the | |
| 15 | + | // behaviour of the official runner; PORTING.md lists what to verify against | |
| 16 | + | // recordings of github.com. | |
| 17 | + | package workflow | |
A workflow/events.go +271 -0
| @@ -0,0 +1,271 @@ | |||
| 1 | + | // SPDX-License-Identifier: MIT | |
| 2 | + | ||
| 3 | + | package workflow | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "bnema.dev/ermite-actions/template" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | // On is the "on" key: the events that trigger a workflow. | |
| 10 | + | type On struct { | |
| 11 | + | // Events are the events in file order. | |
| 12 | + | Events []*Event | |
| 13 | + | } | |
| 14 | + | ||
| 15 | + | // Event returns the configuration of the named event, or nil. | |
| 16 | + | func (o *On) Event(name string) *Event { | |
| 17 | + | if o == nil { | |
| 18 | + | return nil | |
| 19 | + | } | |
| 20 | + | for _, e := range o.Events { | |
| 21 | + | if e.Name == name { | |
| 22 | + | return e | |
| 23 | + | } | |
| 24 | + | } | |
| 25 | + | return nil | |
| 26 | + | } | |
| 27 | + | ||
| 28 | + | // Has reports whether the workflow is triggered by the named event. | |
| 29 | + | func (o *On) Has(name string) bool { return o.Event(name) != nil } | |
| 30 | + | ||
| 31 | + | // Event is one event of the "on" key with its filters. A filter that is | |
| 32 | + | // absent from the file is nil; one written as an empty list is empty and | |
| 33 | + | // non-nil. | |
| 34 | + | type Event struct { | |
| 35 | + | Name string | |
| 36 | + | // Token is the position of the event name. | |
| 37 | + | Token template.Token | |
| 38 | + | ||
| 39 | + | Branches []string | |
| 40 | + | BranchesIgnore []string | |
| 41 | + | Tags []string | |
| 42 | + | TagsIgnore []string | |
| 43 | + | Paths []string | |
| 44 | + | PathsIgnore []string | |
| 45 | + | Types []string | |
| 46 | + | Versions []string | |
| 47 | + | Names []string | |
| 48 | + | Workflows []string | |
| 49 | + | ||
| 50 | + | // Schedule holds the entries of the "schedule" event. | |
| 51 | + | Schedule []Schedule | |
| 52 | + | // Inputs holds the inputs of workflow_dispatch and workflow_call. | |
| 53 | + | Inputs []Input | |
| 54 | + | } | |
| 55 | + | ||
| 56 | + | // Schedule is an entry of the "schedule" event. | |
| 57 | + | type Schedule struct { | |
| 58 | + | Cron string | |
| 59 | + | Timezone string | |
| 60 | + | } | |
| 61 | + | ||
| 62 | + | // Input is an input of workflow_dispatch or workflow_call. | |
| 63 | + | type Input struct { | |
| 64 | + | Name string | |
| 65 | + | Type string // "string", "choice", "boolean", "number", "environment" | |
| 66 | + | Description string | |
| 67 | + | Required bool | |
| 68 | + | // HasDefault reports whether the file gave a default; Default is its text | |
| 69 | + | // (an expression keeps its ${{ }} form). | |
| 70 | + | HasDefault bool | |
| 71 | + | Default string | |
| 72 | + | Options []string | |
| 73 | + | } | |
| 74 | + | ||
| 75 | + | // convertOn is a port of convertOn in converter/events.ts. | |
| 76 | + | func (c *converter) convertOn(t template.Token) *On { | |
| 77 | + | on := &On{} | |
| 78 | + | switch t := t.(type) { | |
| 79 | + | case *template.String: | |
| 80 | + | on.Events = append(on.Events, &Event{Name: t.Value, Token: t}) | |
| 81 | + | case *template.Sequence: | |
| 82 | + | for _, it := range t.Items { | |
| 83 | + | s := asString(it, "on") | |
| 84 | + | on.Events = append(on.Events, &Event{Name: s.Value, Token: s}) | |
| 85 | + | } | |
| 86 | + | case *template.Mapping: | |
| 87 | + | for _, p := range t.Pairs { | |
| 88 | + | key := asString(p.Key, "event name") | |
| 89 | + | ev := &Event{Name: key.Value, Token: key} | |
| 90 | + | on.Events = append(on.Events, ev) | |
| 91 | + | if p.Value.Kind() == template.KindNull { | |
| 92 | + | continue | |
| 93 | + | } | |
| 94 | + | if key.Value == "schedule" { | |
| 95 | + | ev.Schedule = c.convertSchedule(asSeq(p.Value, "event schedule")) | |
| 96 | + | continue | |
| 97 | + | } | |
| 98 | + | m := asMap(p.Value, "event "+key.Value) | |
| 99 | + | switch key.Value { | |
| 100 | + | case "workflow_call": | |
| 101 | + | c.convertWorkflowCall(ev, m) | |
| 102 | + | continue | |
| 103 | + | case "workflow_dispatch": | |
| 104 | + | c.convertWorkflowDispatch(ev, m) | |
| 105 | + | continue | |
| 106 | + | } | |
| 107 | + | ev.Branches = patternFilter("branches", m) | |
| 108 | + | ev.BranchesIgnore = patternFilter("branches-ignore", m) | |
| 109 | + | ev.Tags = patternFilter("tags", m) | |
| 110 | + | ev.TagsIgnore = patternFilter("tags-ignore", m) | |
| 111 | + | ev.Paths = patternFilter("paths", m) | |
| 112 | + | ev.PathsIgnore = patternFilter("paths-ignore", m) | |
| 113 | + | ev.Types = patternFilter("types", m) | |
| 114 | + | ev.Versions = patternFilter("versions", m) | |
| 115 | + | ev.Names = patternFilter("names", m) | |
| 116 | + | ev.Workflows = patternFilter("workflows", m) | |
| 117 | + | } | |
| 118 | + | default: | |
| 119 | + | c.ctx.ErrorAt(t, "Invalid format for 'on'") | |
| 120 | + | return &On{} | |
| 121 | + | } | |
| 122 | + | return on | |
| 123 | + | } | |
| 124 | + | ||
| 125 | + | // patternFilter reads a filter that is a string or a list of strings; nil | |
| 126 | + | // when the key is absent. | |
| 127 | + | func patternFilter(name string, m *template.Mapping) []string { | |
| 128 | + | var out []string | |
| 129 | + | for _, p := range m.Pairs { | |
| 130 | + | key := asString(p.Key, name+" filter key") | |
| 131 | + | if key.Value != name { | |
| 132 | + | continue | |
| 133 | + | } | |
| 134 | + | if s, ok := p.Value.(*template.String); ok { | |
| 135 | + | out = []string{s.Value} | |
| 136 | + | continue | |
| 137 | + | } | |
| 138 | + | out = []string{} | |
| 139 | + | for _, it := range asSeq(p.Value, name+" list").Items { | |
| 140 | + | out = append(out, asString(it, name+" list item").Value) | |
| 141 | + | } | |
| 142 | + | } | |
| 143 | + | return out | |
| 144 | + | } | |
| 145 | + | ||
| 146 | + | func (c *converter) convertSchedule(seq *template.Sequence) []Schedule { | |
| 147 | + | var out []Schedule | |
| 148 | + | for _, it := range seq.Items { | |
| 149 | + | m := asMap(it, "event schedule") | |
| 150 | + | var cfg Schedule | |
| 151 | + | valid := true | |
| 152 | + | for _, e := range m.Pairs { | |
| 153 | + | key := asString(e.Key, "schedule key") | |
| 154 | + | switch key.Value { | |
| 155 | + | case "cron": | |
| 156 | + | cron := asString(e.Value, "schedule cron") | |
| 157 | + | if !isValidCron(cron.Value) { | |
| 158 | + | c.ctx.ErrorAt(cron, "Invalid cron expression. Expected format: '* * * * *' (minute hour day month weekday)") | |
| 159 | + | } | |
| 160 | + | cfg.Cron = cron.Value | |
| 161 | + | case "timezone": | |
| 162 | + | cfg.Timezone = asString(e.Value, "schedule timezone").Value | |
| 163 | + | default: | |
| 164 | + | c.ctx.ErrorAt(key, "Invalid schedule key") | |
| 165 | + | valid = false | |
| 166 | + | } | |
| 167 | + | } | |
| 168 | + | switch { | |
| 169 | + | case valid && cfg.Cron != "": | |
| 170 | + | out = append(out, cfg) | |
| 171 | + | case valid: | |
| 172 | + | c.ctx.ErrorAt(m, "Missing required key 'cron' in schedule entry") | |
| 173 | + | } | |
| 174 | + | } | |
| 175 | + | return out | |
| 176 | + | } | |
| 177 | + | ||
| 178 | + | func (c *converter) convertWorkflowDispatch(ev *Event, m *template.Mapping) { | |
| 179 | + | for _, p := range m.Pairs { | |
| 180 | + | if asString(p.Key, "workflow dispatch input key").Value == "inputs" { | |
| 181 | + | ev.Inputs = c.convertInputs(asMap(p.Value, "workflow dispatch inputs"), false) | |
| 182 | + | } | |
| 183 | + | } | |
| 184 | + | } | |
| 185 | + | ||
| 186 | + | func (c *converter) convertWorkflowCall(ev *Event, m *template.Mapping) { | |
| 187 | + | for _, p := range m.Pairs { | |
| 188 | + | switch asString(p.Key, "workflow dispatch input key").Value { | |
| 189 | + | case "inputs": | |
| 190 | + | ev.Inputs = c.convertInputs(asMap(p.Value, "workflow dispatch inputs"), true) | |
| 191 | + | case "secrets": | |
| 192 | + | for _, s := range asMap(p.Value, "workflow dispatch inputs").Pairs { | |
| 193 | + | asString(s.Key, "secret name") | |
| 194 | + | if sm, ok := s.Value.(*template.Mapping); ok { | |
| 195 | + | for _, kv := range sm.Pairs { | |
| 196 | + | switch asString(kv.Key, "workflow call secret key").Value { | |
| 197 | + | case "description": | |
| 198 | + | asString(kv.Value, "secret description") | |
| 199 | + | case "required": | |
| 200 | + | asBool(kv.Value, "secret required") | |
| 201 | + | } | |
| 202 | + | } | |
| 203 | + | } | |
| 204 | + | } | |
| 205 | + | } | |
| 206 | + | } | |
| 207 | + | } | |
| 208 | + | ||
| 209 | + | // convertInputs ports convertWorkflowDispatchInputs and convertWorkflowInputs. | |
| 210 | + | // The two differ only in that call inputs skip the default check when the | |
| 211 | + | // default is an expression. | |
| 212 | + | func (c *converter) convertInputs(m *template.Mapping, call bool) []Input { | |
| 213 | + | var out []Input | |
| 214 | + | for _, p := range m.Pairs { | |
| 215 | + | name := asString(p.Key, "input name") | |
| 216 | + | im := asMap(p.Value, "input configuration") | |
| 217 | + | out = append(out, c.convertInput(name.Value, im, call)) | |
| 218 | + | } | |
| 219 | + | return out | |
| 220 | + | } | |
| 221 | + | ||
| 222 | + | func (c *converter) convertInput(name string, m *template.Mapping, call bool) Input { | |
| 223 | + | in := Input{Name: name, Type: "string"} | |
| 224 | + | var def template.Token | |
| 225 | + | var options []string | |
| 226 | + | hasOptions := false | |
| 227 | + | for _, p := range m.Pairs { | |
| 228 | + | key := asString(p.Key, "workflow dispatch input key") | |
| 229 | + | switch key.Value { | |
| 230 | + | case "description": | |
| 231 | + | in.Description = asString(p.Value, "input description").Value | |
| 232 | + | case "required": | |
| 233 | + | in.Required = asBool(p.Value, "input required").Value | |
| 234 | + | case "default": | |
| 235 | + | def = asScalar(p.Value, "input default") | |
| 236 | + | case "type": | |
| 237 | + | in.Type = asString(p.Value, "input type").Value | |
| 238 | + | case "options": | |
| 239 | + | hasOptions = true | |
| 240 | + | options = []string{} | |
| 241 | + | for _, it := range asSeq(p.Value, "input options").Items { | |
| 242 | + | options = append(options, asString(it, "input options item").Value) | |
| 243 | + | } | |
| 244 | + | in.Options = options | |
| 245 | + | default: | |
| 246 | + | c.ctx.Errorf(p.Key, "Invalid key '%s'", key.Value) | |
| 247 | + | } | |
| 248 | + | } | |
| 249 | + | if def != nil { | |
| 250 | + | in.HasDefault = true | |
| 251 | + | in.Default = def.String() | |
| 252 | + | if !(call && template.IsExpression(def)) { | |
| 253 | + | c.handle(def, func() { | |
| 254 | + | switch in.Type { | |
| 255 | + | case "boolean": | |
| 256 | + | asBool(def, "input default") | |
| 257 | + | case "string", "choice", "environment": | |
| 258 | + | asString(def, "input default") | |
| 259 | + | } | |
| 260 | + | }) | |
| 261 | + | } | |
| 262 | + | } | |
| 263 | + | if in.Type == "choice" { | |
| 264 | + | if !hasOptions || len(options) == 0 { | |
| 265 | + | c.ctx.ErrorAt(m, "Missing 'options' for choice input") | |
| 266 | + | } | |
| 267 | + | } else if hasOptions { | |
| 268 | + | c.ctx.ErrorAt(m, "Input type is not 'choice', but 'options' is defined") | |
| 269 | + | } | |
| 270 | + | return in | |
| 271 | + | } | |
A workflow/gen/convert.mjs +48 -0
| @@ -0,0 +1,48 @@ | |||
| 1 | + | // SPDX-License-Identifier: MIT | |
| 2 | + | // | |
| 3 | + | // Generates testdata/convert.json: for each workflow in testdata/convert/*.yml, | |
| 4 | + | // the errors and the model summary that @actions/workflow-parser 0.3.61 (MIT, | |
| 5 | + | // GitHub) produces. convert_test.go replays the files and compares. | |
| 6 | + | // | |
| 7 | + | // node gen/convert.mjs /path/to/@actions/workflow-parser/dist > testdata/convert.json | |
| 8 | + | // | |
| 9 | + | // The package imports "yaml" and "@actions/expressions"; the JSON import of the | |
| 10 | + | // schema needs `with { type: "json" }` in dist/workflows/workflow-schema.js on | |
| 11 | + | // recent Node versions. | |
| 12 | + | ||
| 13 | + | import { pathToFileURL } from "node:url"; | |
| 14 | + | import path from "node:path"; | |
| 15 | + | import fs from "node:fs"; | |
| 16 | + | ||
| 17 | + | const dist = path.resolve(process.argv[2]); | |
| 18 | + | const imp = p => import(pathToFileURL(path.join(dist, p)).href); | |
| 19 | + | const { parseWorkflow } = await imp("workflows/workflow-parser.js"); | |
| 20 | + | const { NoOperationTraceWriter } = await imp("templates/trace-writer.js"); | |
| 21 | + | const { convertWorkflowTemplate } = await imp("model/convert.js"); | |
| 22 | + | ||
| 23 | + | const dir = new URL("../testdata/convert/", import.meta.url); | |
| 24 | + | const out = {}; | |
| 25 | + | for (const f of fs.readdirSync(dir).sort()) { | |
| 26 | + | if (!f.endsWith(".yml")) continue; | |
| 27 | + | const content = fs.readFileSync(new URL(f, dir), "utf8"); | |
| 28 | + | const r = parseWorkflow({ name: f, content }, new NoOperationTraceWriter()); | |
| 29 | + | const res = { errors: [] }; | |
| 30 | + | if (!r.value) { | |
| 31 | + | res.errors = r.context.errors.getErrors().map(e => e.message); | |
| 32 | + | } else { | |
| 33 | + | // Default options: ReturnErrorsOnly, i.e. the converter does not run when the | |
| 34 | + | // schema reported errors. | |
| 35 | + | const t = await convertWorkflowTemplate(r.context, r.value); | |
| 36 | + | res.errors = (t.errors || []).map(e => e.Message); | |
| 37 | + | res.jobs = (t.jobs || []).map(j => ({ | |
| 38 | + | id: j.id.value, | |
| 39 | + | name: String(j.name), | |
| 40 | + | if: j.if?.expression, | |
| 41 | + | needs: (j.needs || []).map(n => n.value), | |
| 42 | + | steps: (j.steps || []).map(s => ({ id: s.id, if: s.if?.expression })), | |
| 43 | + | })); | |
| 44 | + | res.events = t.events; | |
| 45 | + | } | |
| 46 | + | out[f] = res; | |
| 47 | + | } | |
| 48 | + | console.log(JSON.stringify(out, null, 1)); | |
A workflow/ids.go +104 -0
| @@ -0,0 +1,104 @@ | |||
| 1 | + | // SPDX-License-Identifier: MIT | |
| 2 | + | ||
| 3 | + | package workflow | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "fmt" | |
| 7 | + | "strings" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | const ( | |
| 11 | + | idSeparator = "_" | |
| 12 | + | idMaxAttempts = 1000 | |
| 13 | + | idMaxLength = 100 | |
| 14 | + | ) | |
| 15 | + | ||
| 16 | + | // idBuilder is a port of IdBuilder in the reference: it validates the ids | |
| 17 | + | // written in a file and makes unique ids for steps that have none. | |
| 18 | + | type idBuilder struct { | |
| 19 | + | name []rune | |
| 20 | + | distinct map[string]bool | |
| 21 | + | } | |
| 22 | + | ||
| 23 | + | func newIDBuilder() *idBuilder { return &idBuilder{distinct: map[string]bool{}} } | |
| 24 | + | ||
| 25 | + | func isAlpha(c rune) bool { return c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' } | |
| 26 | + | func isNumeric(c rune) bool { return c >= '0' && c <= '9' } | |
| 27 | + | ||
| 28 | + | func (b *idBuilder) appendSegment(value string) { | |
| 29 | + | if value == "" { | |
| 30 | + | return | |
| 31 | + | } | |
| 32 | + | rs := []rune(value) | |
| 33 | + | if len(b.name) == 0 { | |
| 34 | + | first := rs[0] | |
| 35 | + | switch { | |
| 36 | + | case isAlpha(first) || first == '_': | |
| 37 | + | case isNumeric(first) || first == '-': | |
| 38 | + | b.name = append(b.name, '_') | |
| 39 | + | } | |
| 40 | + | } else { | |
| 41 | + | b.name = append(b.name, '_') | |
| 42 | + | } | |
| 43 | + | for _, c := range rs { | |
| 44 | + | if isAlpha(c) || isNumeric(c) || c == '_' || c == '-' { | |
| 45 | + | b.name = append(b.name, c) | |
| 46 | + | } else { | |
| 47 | + | b.name = append(b.name, '_') | |
| 48 | + | } | |
| 49 | + | } | |
| 50 | + | } | |
| 51 | + | ||
| 52 | + | // build returns a unique id from the appended segments, "job" when there are | |
| 53 | + | // none. It returns "" when a thousand ids are taken. | |
| 54 | + | func (b *idBuilder) build() string { | |
| 55 | + | original := "job" | |
| 56 | + | if len(b.name) > 0 { | |
| 57 | + | original = string(b.name) | |
| 58 | + | } | |
| 59 | + | for attempt := 1; attempt < idMaxAttempts; attempt++ { | |
| 60 | + | suffix := "" | |
| 61 | + | if attempt > 1 { | |
| 62 | + | suffix = fmt.Sprintf("_%d", attempt) | |
| 63 | + | } | |
| 64 | + | n := min(len(original), idMaxLength-len(suffix)) | |
| 65 | + | candidate := original[:n] + suffix | |
| 66 | + | if !b.distinct[candidate] { | |
| 67 | + | b.distinct[candidate] = true | |
| 68 | + | b.name = nil | |
| 69 | + | return candidate | |
| 70 | + | } | |
| 71 | + | } | |
| 72 | + | return "" | |
| 73 | + | } | |
| 74 | + | ||
| 75 | + | // tryAddKnownID records an id written in the file. It returns the error | |
| 76 | + | // message when the id is invalid or already used. | |
| 77 | + | func (b *idBuilder) tryAddKnownID(value string) string { | |
| 78 | + | if value == "" || !validID(value) || len(value) >= idMaxLength { | |
| 79 | + | return fmt.Sprintf("The identifier '%s' is invalid. IDs may only contain alphanumeric characters, '_', and '-'. IDs must start with a letter or '_' and and must be less than %d characters.", value, idMaxLength) | |
| 80 | + | } | |
| 81 | + | if strings.HasPrefix(value, idSeparator+idSeparator) { | |
| 82 | + | return fmt.Sprintf("The identifier '%s' is invalid. IDs starting with '__' are reserved.", value) | |
| 83 | + | } | |
| 84 | + | if b.distinct[value] { | |
| 85 | + | return fmt.Sprintf("The identifier '%s' may not be used more than once within the same scope.", value) | |
| 86 | + | } | |
| 87 | + | b.distinct[value] = true | |
| 88 | + | return "" | |
| 89 | + | } | |
| 90 | + | ||
| 91 | + | func validID(name string) bool { | |
| 92 | + | for i, c := range name { | |
| 93 | + | if i == 0 { | |
| 94 | + | if !isAlpha(c) && c != '_' { | |
| 95 | + | return false | |
| 96 | + | } | |
| 97 | + | continue | |
| 98 | + | } | |
| 99 | + | if !isAlpha(c) && !isNumeric(c) && c != '_' && c != '-' { | |
| 100 | + | return false | |
| 101 | + | } | |
| 102 | + | } | |
| 103 | + | return true | |
| 104 | + | } | |
A workflow/model.go +161 -0
| @@ -0,0 +1,161 @@ | |||
| 1 | + | // SPDX-License-Identifier: MIT | |
| 2 | + | ||
| 3 | + | package workflow | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "bnema.dev/ermite-actions/template" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | // Error is a problem found in a workflow file. Its message has the form | |
| 10 | + | // "file.yml (Line: 3, Col: 5): message". | |
| 11 | + | type Error = template.Error | |
| 12 | + | ||
| 13 | + | // Workflow is a parsed and validated workflow file. | |
| 14 | + | type Workflow struct { | |
| 15 | + | // File is the name given to Parse. | |
| 16 | + | File string | |
| 17 | + | // Name is the "name" key, or "". | |
| 18 | + | Name string | |
| 19 | + | // RunName is the "run-name" token (an expression is allowed), or nil. | |
| 20 | + | RunName template.Token | |
| 21 | + | // On lists the events that trigger the workflow, in file order. | |
| 22 | + | On *On | |
| 23 | + | // Env is the workflow-level "env" mapping, or nil. Values may hold | |
| 24 | + | // expressions. | |
| 25 | + | Env *template.Mapping | |
| 26 | + | // Defaults holds the workflow-level "defaults", or nil. | |
| 27 | + | Defaults *Defaults | |
| 28 | + | // Concurrency is the "concurrency" token (a string or a mapping), or nil. | |
| 29 | + | Concurrency template.Token | |
| 30 | + | // Permissions is the "permissions" key, or nil. | |
| 31 | + | Permissions *Permissions | |
| 32 | + | // Jobs are the jobs in file order. | |
| 33 | + | Jobs []*Job | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | // Job returns the job with the given id, or nil. | |
| 37 | + | func (w *Workflow) Job(id string) *Job { | |
| 38 | + | for _, j := range w.Jobs { | |
| 39 | + | if j.ID == id { | |
| 40 | + | return j | |
| 41 | + | } | |
| 42 | + | } | |
| 43 | + | return nil | |
| 44 | + | } | |
| 45 | + | ||
| 46 | + | // Defaults is the "defaults" key. | |
| 47 | + | type Defaults struct { | |
| 48 | + | // Run holds defaults.run; nil when absent. | |
| 49 | + | Run *RunDefaults | |
| 50 | + | } | |
| 51 | + | ||
| 52 | + | // RunDefaults is defaults.run: the shell and working directory of run steps. | |
| 53 | + | type RunDefaults struct { | |
| 54 | + | Shell template.Token | |
| 55 | + | WorkingDirectory template.Token | |
| 56 | + | } | |
| 57 | + | ||
| 58 | + | // Permissions is the "permissions" key: either all permissions read or | |
| 59 | + | // write, or a level per scope. | |
| 60 | + | type Permissions struct { | |
| 61 | + | // All is "read-all" or "write-all", or "" when Scopes is used. | |
| 62 | + | All string | |
| 63 | + | // Scopes lists the scopes in file order ("contents": "read"). | |
| 64 | + | Scopes []PermissionScope | |
| 65 | + | } | |
| 66 | + | ||
| 67 | + | // PermissionScope is one entry of a permissions mapping. | |
| 68 | + | type PermissionScope struct { | |
| 69 | + | Scope string | |
| 70 | + | Level string // "read", "write" or "none" | |
| 71 | + | } | |
| 72 | + | ||
| 73 | + | // Job is a job of a workflow. | |
| 74 | + | type Job struct { | |
| 75 | + | // ID is the key of the job. | |
| 76 | + | ID string | |
| 77 | + | // IDToken is the key token, with its position. | |
| 78 | + | IDToken template.Token | |
| 79 | + | // Name is the "name" token; when the file gives none it is the id. | |
| 80 | + | Name template.Token | |
| 81 | + | // HasName reports whether the file gave a non-empty name. | |
| 82 | + | HasName bool | |
| 83 | + | // Needs are the ids of the jobs this one depends on, with their tokens. | |
| 84 | + | Needs []string | |
| 85 | + | NeedTokens []*template.String | |
| 86 | + | // If is the condition, always with a status function: a condition | |
| 87 | + | // without one is wrapped as "success() && (cond)", and a missing one is | |
| 88 | + | // "success()". | |
| 89 | + | If *template.BasicExpression | |
| 90 | + | // RunsOn is the "runs-on" token (string, sequence, mapping or an | |
| 91 | + | // expression); nil for a reusable workflow job. | |
| 92 | + | RunsOn template.Token | |
| 93 | + | // Strategy is the "strategy" token, or nil. | |
| 94 | + | Strategy template.Token | |
| 95 | + | // Env is the job's "env" mapping, or nil. | |
| 96 | + | Env *template.Mapping | |
| 97 | + | // Defaults holds the job-level "defaults", or nil. | |
| 98 | + | Defaults *Defaults | |
| 99 | + | // Steps are the steps in file order. | |
| 100 | + | Steps []*Step | |
| 101 | + | // Outputs is the "outputs" mapping, or nil. | |
| 102 | + | Outputs *template.Mapping | |
| 103 | + | // TimeoutMinutes, CancelTimeoutMinutes and ContinueOnError are tokens | |
| 104 | + | // (a literal or an expression), or nil. | |
| 105 | + | TimeoutMinutes template.Token | |
| 106 | + | CancelTimeoutMinutes template.Token | |
| 107 | + | ContinueOnError template.Token | |
| 108 | + | // Container, Services, Environment, Concurrency and Snapshot are | |
| 109 | + | // the raw tokens, or nil. | |
| 110 | + | Container template.Token | |
| 111 | + | Services template.Token | |
| 112 | + | Environment template.Token | |
| 113 | + | Concurrency template.Token | |
| 114 | + | Snapshot template.Token | |
| 115 | + | // Permissions is the job's "permissions", or nil. | |
| 116 | + | Permissions *Permissions | |
| 117 | + | ||
| 118 | + | // Uses is set for a job that calls a reusable workflow ("uses"); such a | |
| 119 | + | // job has no steps and no runs-on. | |
| 120 | + | Uses *template.String | |
| 121 | + | // With and Secrets are the inputs and secrets passed to the called | |
| 122 | + | // workflow. InheritSecrets is "secrets: inherit". | |
| 123 | + | With *template.Mapping | |
| 124 | + | Secrets *template.Mapping | |
| 125 | + | InheritSecrets bool | |
| 126 | + | } | |
| 127 | + | ||
| 128 | + | // IsReusable reports whether the job calls a reusable workflow. | |
| 129 | + | func (j *Job) IsReusable() bool { return j.Uses != nil } | |
| 130 | + | ||
| 131 | + | // Step is a step of a job: a run step or a uses step. | |
| 132 | + | type Step struct { | |
| 133 | + | // ID is the id of the step. When the file gives none, one is generated | |
| 134 | + | // the way the reference does: "__run", "__run_2", "__actions_checkout". | |
| 135 | + | ID string | |
| 136 | + | // Explicit reports whether the id came from the file. | |
| 137 | + | Explicit bool | |
| 138 | + | // Pos is where the step starts. | |
| 139 | + | Pos template.Pos | |
| 140 | + | // Name is the "name" token, or nil. | |
| 141 | + | Name template.Token | |
| 142 | + | // If is the condition, with a status function, as for jobs. | |
| 143 | + | If *template.BasicExpression | |
| 144 | + | // Run is the script of a run step, or nil. | |
| 145 | + | Run template.Token | |
| 146 | + | // Shell and WorkingDirectory are the run step's options, or nil. | |
| 147 | + | Shell template.Token | |
| 148 | + | WorkingDirectory template.Token | |
| 149 | + | // Uses is the action of a uses step, or nil. | |
| 150 | + | Uses *template.String | |
| 151 | + | // With holds the inputs of a uses step, or nil. | |
| 152 | + | With *template.Mapping | |
| 153 | + | // Env is the step's "env" mapping, or nil. | |
| 154 | + | Env *template.Mapping | |
| 155 | + | // ContinueOnError and TimeoutMinutes are tokens, or nil. | |
| 156 | + | ContinueOnError template.Token | |
| 157 | + | TimeoutMinutes template.Token | |
| 158 | + | } | |
| 159 | + | ||
| 160 | + | // IsRun reports whether the step runs a script. | |
| 161 | + | func (s *Step) IsRun() bool { return s.Run != nil } | |
A workflow/parse.go +46 -0
| @@ -0,0 +1,46 @@ | |||
| 1 | + | // SPDX-License-Identifier: MIT | |
| 2 | + | ||
| 3 | + | package workflow | |
| 4 | + | ||
| 5 | + | import ( | |
| 6 | + | "bnema.dev/ermite-actions/template" | |
| 7 | + | ) | |
| 8 | + | ||
| 9 | + | // rootType is the schema definition a workflow file is read against; its | |
| 10 | + | // "on" key is required. | |
| 11 | + | const rootType = "workflow-root-strict" | |
| 12 | + | ||
| 13 | + | // Parse reads, validates and converts a workflow file. path is only used in | |
| 14 | + | // error messages, which read "path (Line: l, Col: c): message". | |
| 15 | + | // | |
| 16 | + | // The errors are the reference's: syntax errors, schema violations, unknown | |
| 17 | + | // contexts and functions in expressions, and the checks of the converter (job | |
| 18 | + | // and step ids, needs, filters, inputs). When there is any error the workflow | |
| 19 | + | // is nil. As in the reference, the converter only runs when the file passes | |
| 20 | + | // the schema. | |
| 21 | + | func Parse(path string, content []byte) (*Workflow, []Error) { | |
| 22 | + | schema, err := template.WorkflowSchema() | |
| 23 | + | if err != nil { | |
| 24 | + | return nil, []Error{{Raw: "internal error: " + err.Error()}} | |
| 25 | + | } | |
| 26 | + | ctx := template.NewContext(schema) | |
| 27 | + | root := template.ReadYAML(ctx, rootType, path, content) | |
| 28 | + | w := &Workflow{File: path} | |
| 29 | + | if root != nil && !ctx.HasErrors() { | |
| 30 | + | c := &converter{ctx: ctx, schema: schema, w: w, jobIDs: newIDBuilder()} | |
| 31 | + | c.convertRoot(root) | |
| 32 | + | } | |
| 33 | + | errs := collect(ctx) | |
| 34 | + | if len(errs) > 0 { | |
| 35 | + | return nil, errs | |
| 36 | + | } | |
| 37 | + | return w, nil | |
| 38 | + | } | |
| 39 | + | ||
| 40 | + | func collect(ctx *template.Context) []Error { | |
| 41 | + | var out []Error | |
| 42 | + | for _, e := range ctx.Errors() { | |
| 43 | + | out = append(out, *e) | |
| 44 | + | } | |
| 45 | + | return out | |
| 46 | + | } | |
A workflow/testdata/convert.json +2445 -0
| @@ -0,0 +1,2445 @@ | |||
| 1 | + | { | |
| 2 | + | "alias.yml": { | |
| 3 | + | "errors": [], | |
| 4 | + | "jobs": [ | |
| 5 | + | { | |
| 6 | + | "id": "a", | |
| 7 | + | "name": "a", | |
| 8 | + | "if": "success()", | |
| 9 | + | "needs": [], | |
| 10 | + | "steps": [ | |
| 11 | + | { | |
| 12 | + | "id": "__run", | |
| 13 | + | "if": "success()" | |
| 14 | + | } | |
| 15 | + | ] | |
| 16 | + | }, | |
| 17 | + | { | |
| 18 | + | "id": "b", | |
| 19 | + | "name": "b", | |
| 20 | + | "if": "success()", | |
| 21 | + | "needs": [], | |
| 22 | + | "steps": [ | |
| 23 | + | { | |
| 24 | + | "id": "__run", | |
| 25 | + | "if": "success()" | |
| 26 | + | } | |
| 27 | + | ] | |
| 28 | + | } | |
| 29 | + | ], | |
| 30 | + | "events": { | |
| 31 | + | "push": {} | |
| 32 | + | } | |
| 33 | + | }, | |
| 34 | + | "alias_scalar.yml": { | |
| 35 | + | "errors": [ | |
| 36 | + | "alias_scalar.yml (Line: 2, Col: 1): Unexpected value 'x'" | |
| 37 | + | ], | |
| 38 | + | "jobs": [] | |
| 39 | + | }, | |
| 40 | + | "background_steps.yml": { | |
| 41 | + | "errors": [ | |
| 42 | + | "background_steps.yml (Line: 7, Col: 9): Unexpected value 'background'", | |
| 43 | + | "background_steps.yml (Line: 8, Col: 9): Unexpected value 'wait'" | |
| 44 | + | ], | |
| 45 | + | "jobs": [] | |
| 46 | + | }, | |
| 47 | + | "bad_cron.yml": { | |
| 48 | + | "errors": [ | |
| 49 | + | "bad_cron.yml (Line: 13, Col: 7): Required property is missing: cron", | |
| 50 | + | "bad_cron.yml (Line: 16, Col: 7): Unexpected value 'extra'" | |
| 51 | + | ], | |
| 52 | + | "jobs": [] | |
| 53 | + | }, | |
| 54 | + | "bad_if.yml": { | |
| 55 | + | "errors": [ | |
| 56 | + | "bad_if.yml (Line: 5, Col: 9): Unexpected end of expression: '&&'" | |
| 57 | + | ], | |
| 58 | + | "jobs": [ | |
| 59 | + | { | |
| 60 | + | "id": "a", | |
| 61 | + | "name": "a", | |
| 62 | + | "if": "success()", | |
| 63 | + | "needs": [], | |
| 64 | + | "steps": [ | |
| 65 | + | { | |
| 66 | + | "id": "__run", | |
| 67 | + | "if": "success()" | |
| 68 | + | } | |
| 69 | + | ] | |
| 70 | + | } | |
| 71 | + | ], | |
| 72 | + | "events": { | |
| 73 | + | "push": {} | |
| 74 | + | } | |
| 75 | + | }, | |
| 76 | + | "bad_job_ids.yml": { | |
| 77 | + | "errors": [ | |
| 78 | + | "bad_job_ids.yml (Line: 3, Col: 3): The identifier '1a' is invalid. IDs may only contain alphanumeric characters, '_', and '-'. IDs must start with a letter or '_' and and must be less than 100 characters.", | |
| 79 | + | "bad_job_ids.yml (Line: 6, Col: 3): The identifier '__b' is invalid. IDs starting with '__' are reserved.", | |
| 80 | + | "bad_job_ids.yml (Line: 9, Col: 3): The identifier 'a b' is invalid. IDs may only contain alphanumeric characters, '_', and '-'. IDs must start with a letter or '_' and and must be less than 100 characters." | |
| 81 | + | ], | |
| 82 | + | "jobs": [ | |
| 83 | + | { | |
| 84 | + | "id": "1a", | |
| 85 | + | "name": "1a", | |
| 86 | + | "if": "success()", | |
| 87 | + | "needs": [], | |
| 88 | + | "steps": [ | |
| 89 | + | { | |
| 90 | + | "id": "__run", | |
| 91 | + | "if": "success()" | |
| 92 | + | } | |
| 93 | + | ] | |
| 94 | + | }, | |
| 95 | + | { | |
| 96 | + | "id": "__b", | |
| 97 | + | "name": "__b", | |
| 98 | + | "if": "success()", | |
| 99 | + | "needs": [], | |
| 100 | + | "steps": [ | |
| 101 | + | { | |
| 102 | + | "id": "__run", | |
| 103 | + | "if": "success()" | |
| 104 | + | } | |
| 105 | + | ] | |
| 106 | + | }, | |
| 107 | + | { | |
| 108 | + | "id": "a b", | |
| 109 | + | "name": "a b", | |
| 110 | + | "if": "success()", | |
| 111 | + | "needs": [], | |
| 112 | + | "steps": [ | |
| 113 | + | { | |
| 114 | + | "id": "__run", | |
| 115 | + | "if": "success()" | |
| 116 | + | } | |
| 117 | + | ] | |
| 118 | + | } | |
| 119 | + | ], | |
| 120 | + | "events": { | |
| 121 | + | "push": {} | |
| 122 | + | } | |
| 123 | + | }, | |
| 124 | + | "bad_needs_cycle.yml": { | |
| 125 | + | "errors": [ | |
| 126 | + | "bad_needs_cycle.yml (Line: 3, Col: 3): The workflow must contain at least one job with no dependencies." | |
| 127 | + | ], | |
| 128 | + | "jobs": [ | |
| 129 | + | { | |
| 130 | + | "id": "a", | |
| 131 | + | "name": "a", | |
| 132 | + | "if": "success()", | |
| 133 | + | "needs": [ | |
| 134 | + | "b" | |
| 135 | + | ], | |
| 136 | + | "steps": [ | |
| 137 | + | { | |
| 138 | + | "id": "__run", | |
| 139 | + | "if": "success()" | |
| 140 | + | } | |
| 141 | + | ] | |
| 142 | + | }, | |
| 143 | + | { | |
| 144 | + | "id": "b", | |
| 145 | + | "name": "b", | |
| 146 | + | "if": "success()", | |
| 147 | + | "needs": [ | |
| 148 | + | "a" | |
| 149 | + | ], | |
| 150 | + | "steps": [ | |
| 151 | + | { | |
| 152 | + | "id": "__run", | |
| 153 | + | "if": "success()" | |
| 154 | + | } | |
| 155 | + | ] | |
| 156 | + | } | |
| 157 | + | ], | |
| 158 | + | "events": { | |
| 159 | + | "push": {} | |
| 160 | + | } | |
| 161 | + | }, | |
| 162 | + | "bad_needs_cycle_with_root.yml": { | |
| 163 | + | "errors": [ | |
| 164 | + | "bad_needs_cycle_with_root.yml (Line: 8, Col: 13): Job 'a' depends on job 'b' which creates a cycle in the dependency graph.", | |
| 165 | + | "bad_needs_cycle_with_root.yml (Line: 12, Col: 13): Job 'b' depends on job 'a' which creates a cycle in the dependency graph." | |
| 166 | + | ], | |
| 167 | + | "jobs": [ | |
| 168 | + | { | |
| 169 | + | "id": "r", | |
| 170 | + | "name": "r", | |
| 171 | + | "if": "success()", | |
| 172 | + | "needs": [], | |
| 173 | + | "steps": [ | |
| 174 | + | { | |
| 175 | + | "id": "__run", | |
| 176 | + | "if": "success()" | |
| 177 | + | } | |
| 178 | + | ] | |
| 179 | + | }, | |
| 180 | + | { | |
| 181 | + | "id": "a", | |
| 182 | + | "name": "a", | |
| 183 | + | "if": "success()", | |
| 184 | + | "needs": [ | |
| 185 | + | "b" | |
| 186 | + | ], | |
| 187 | + | "steps": [ | |
| 188 | + | { | |
| 189 | + | "id": "__run", | |
| 190 | + | "if": "success()" | |
| 191 | + | } | |
| 192 | + | ] | |
| 193 | + | }, | |
| 194 | + | { | |
| 195 | + | "id": "b", | |
| 196 | + | "name": "b", | |
| 197 | + | "if": "success()", | |
| 198 | + | "needs": [ | |
| 199 | + | "a", | |
| 200 | + | "r" | |
| 201 | + | ], | |
| 202 | + | "steps": [ | |
| 203 | + | { | |
| 204 | + | "id": "__run", | |
| 205 | + | "if": "success()" | |
| 206 | + | } | |
| 207 | + | ] | |
| 208 | + | } | |
| 209 | + | ], | |
| 210 | + | "events": { | |
| 211 | + | "push": {} | |
| 212 | + | } | |
| 213 | + | }, | |
| 214 | + | "bad_needs_self.yml": { | |
| 215 | + | "errors": [ | |
| 216 | + | "bad_needs_self.yml (Line: 8, Col: 13): Job 'a' depends on job 'a' which creates a cycle in the dependency graph." | |
| 217 | + | ], | |
| 218 | + | "jobs": [ | |
| 219 | + | { | |
| 220 | + | "id": "r", | |
| 221 | + | "name": "r", | |
| 222 | + | "if": "success()", | |
| 223 | + | "needs": [], | |
| 224 | + | "steps": [ | |
| 225 | + | { | |
| 226 | + | "id": "__run", | |
| 227 | + | "if": "success()" | |
| 228 | + | } | |
| 229 | + | ] | |
| 230 | + | }, | |
| 231 | + | { | |
| 232 | + | "id": "a", | |
| 233 | + | "name": "a", | |
| 234 | + | "if": "success()", | |
| 235 | + | "needs": [ | |
| 236 | + | "a" | |
| 237 | + | ], | |
| 238 | + | "steps": [ | |
| 239 | + | { | |
| 240 | + | "id": "__run", | |
| 241 | + | "if": "success()" | |
| 242 | + | } | |
| 243 | + | ] | |
| 244 | + | } | |
| 245 | + | ], | |
| 246 | + | "events": { | |
| 247 | + | "push": {} | |
| 248 | + | } | |
| 249 | + | }, | |
| 250 | + | "bad_needs_unknown.yml": { | |
| 251 | + | "errors": [ | |
| 252 | + | "bad_needs_unknown.yml (Line: 8, Col: 13): Job 'a' depends on unknown job 'zz'." | |
| 253 | + | ], | |
| 254 | + | "jobs": [ | |
| 255 | + | { | |
| 256 | + | "id": "r", | |
| 257 | + | "name": "r", | |
| 258 | + | "if": "success()", | |
| 259 | + | "needs": [], | |
| 260 | + | "steps": [ | |
| 261 | + | { | |
| 262 | + | "id": "__run", | |
| 263 | + | "if": "success()" | |
| 264 | + | } | |
| 265 | + | ] | |
| 266 | + | }, | |
| 267 | + | { | |
| 268 | + | "id": "a", | |
| 269 | + | "name": "a", | |
| 270 | + | "if": "success()", | |
| 271 | + | "needs": [ | |
| 272 | + | "zz", | |
| 273 | + | "r" | |
| 274 | + | ], | |
| 275 | + | "steps": [ | |
| 276 | + | { | |
| 277 | + | "id": "__run", | |
| 278 | + | "if": "success()" | |
| 279 | + | } | |
| 280 | + | ] | |
| 281 | + | } | |
| 282 | + | ], | |
| 283 | + | "events": { | |
| 284 | + | "push": {} | |
| 285 | + | } | |
| 286 | + | }, | |
| 287 | + | "bad_step_id.yml": { | |
| 288 | + | "errors": [ | |
| 289 | + | "bad_step_id.yml (Line: 5, Col: 96): Unexpected value ''" | |
| 290 | + | ], | |
| 291 | + | "jobs": [] | |
| 292 | + | }, | |
| 293 | + | "bad_yaml_flow.yml": { | |
| 294 | + | "errors": [ | |
| 295 | + | "bad_yaml_flow.yml: Flow sequence in block collection must be sufficiently indented and end with a ] at line 2, column 1:\n\non: [push\n\n^\n" | |
| 296 | + | ] | |
| 297 | + | }, | |
| 298 | + | "bad_yaml_tab.yml": { | |
| 299 | + | "errors": [ | |
| 300 | + | "bad_yaml_tab.yml: Tabs are not allowed as indentation at line 3, column 1:\n\njobs:\n\ta: 1\n^\n" | |
| 301 | + | ] | |
| 302 | + | }, | |
| 303 | + | "big_ids.yml": { | |
| 304 | + | "errors": [ | |
| 305 | + | "big_ids.yml (Line: 3, Col: 3): The identifier 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' is invalid. IDs may only contain alphanumeric characters, '_', and '-'. IDs must start with a letter or '_' and and must be less than 100 characters." | |
| 306 | + | ], | |
| 307 | + | "jobs": [ | |
| 308 | + | { | |
| 309 | + | "id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", | |
| 310 | + | "name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", | |
| 311 | + | "if": "success()", | |
| 312 | + | "needs": [], | |
| 313 | + | "steps": [ | |
| 314 | + | { | |
| 315 | + | "id": "__run", | |
| 316 | + | "if": "success()" | |
| 317 | + | } | |
| 318 | + | ] | |
| 319 | + | }, | |
| 320 | + | { | |
| 321 | + | "id": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", | |
| 322 | + | "name": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", | |
| 323 | + | "if": "success()", | |
| 324 | + | "needs": [], | |
| 325 | + | "steps": [ | |
| 326 | + | { | |
| 327 | + | "id": "__run", | |
| 328 | + | "if": "success()" | |
| 329 | + | } | |
| 330 | + | ] | |
| 331 | + | } | |
| 332 | + | ], | |
| 333 | + | "events": { | |
| 334 | + | "push": {} | |
| 335 | + | } | |
| 336 | + | }, | |
| 337 | + | "bool_scalars.yml": { | |
| 338 | + | "errors": [], | |
| 339 | + | "jobs": [ | |
| 340 | + | { | |
| 341 | + | "id": "a", | |
| 342 | + | "name": "a", | |
| 343 | + | "if": "success()", | |
| 344 | + | "needs": [], | |
| 345 | + | "steps": [ | |
| 346 | + | { | |
| 347 | + | "id": "__run", | |
| 348 | + | "if": "success()" | |
| 349 | + | }, | |
| 350 | + | { | |
| 351 | + | "id": "__run_2", | |
| 352 | + | "if": "success()" | |
| 353 | + | }, | |
| 354 | + | { | |
| 355 | + | "id": "__run_3", | |
| 356 | + | "if": "success()" | |
| 357 | + | }, | |
| 358 | + | { | |
| 359 | + | "id": "__run_4", | |
| 360 | + | "if": "success()" | |
| 361 | + | }, | |
| 362 | + | { | |
| 363 | + | "id": "__run_5", | |
| 364 | + | "if": "success()" | |
| 365 | + | } | |
| 366 | + | ] | |
| 367 | + | } | |
| 368 | + | ], | |
| 369 | + | "events": { | |
| 370 | + | "push": {} | |
| 371 | + | } | |
| 372 | + | }, | |
| 373 | + | "call_inputs.yml": { | |
| 374 | + | "errors": [ | |
| 375 | + | "call_inputs.yml (Line: 10, Col: 18): Unexpected type 'NumberToken' encountered while reading 'input default'. The type 'BooleanToken' was expected." | |
| 376 | + | ], | |
| 377 | + | "jobs": [ | |
| 378 | + | { | |
| 379 | + | "id": "a", | |
| 380 | + | "name": "a", | |
| 381 | + | "if": "success()", | |
| 382 | + | "needs": [], | |
| 383 | + | "steps": [ | |
| 384 | + | { | |
| 385 | + | "id": "__run", | |
| 386 | + | "if": "success()" | |
| 387 | + | } | |
| 388 | + | ] | |
| 389 | + | } | |
| 390 | + | ], | |
| 391 | + | "events": { | |
| 392 | + | "workflow_call": { | |
| 393 | + | "inputs": { | |
| 394 | + | "a": { | |
| 395 | + | "type": "string", | |
| 396 | + | "required": true | |
| 397 | + | }, | |
| 398 | + | "b": { | |
| 399 | + | "type": "boolean" | |
| 400 | + | } | |
| 401 | + | }, | |
| 402 | + | "secrets": { | |
| 403 | + | "s": { | |
| 404 | + | "required": true | |
| 405 | + | }, | |
| 406 | + | "t": {} | |
| 407 | + | } | |
| 408 | + | } | |
| 409 | + | } | |
| 410 | + | }, | |
| 411 | + | "call_inputs_expr_default.yml": { | |
| 412 | + | "errors": [ | |
| 413 | + | "call_inputs_expr_default.yml (Line: 9, Col: 18): Unexpected type 'NumberToken' encountered while reading 'input default'. The type 'StringToken' was expected." | |
| 414 | + | ], | |
| 415 | + | "jobs": [ | |
| 416 | + | { | |
| 417 | + | "id": "a", | |
| 418 | + | "name": "a", | |
| 419 | + | "if": "success()", | |
| 420 | + | "needs": [], | |
| 421 | + | "steps": [ | |
| 422 | + | { | |
| 423 | + | "id": "__run", | |
| 424 | + | "if": "success()" | |
| 425 | + | } | |
| 426 | + | ] | |
| 427 | + | } | |
| 428 | + | ], | |
| 429 | + | "events": { | |
| 430 | + | "workflow_call": { | |
| 431 | + | "inputs": { | |
| 432 | + | "b": { | |
| 433 | + | "type": "boolean" | |
| 434 | + | }, | |
| 435 | + | "c": { | |
| 436 | + | "type": "string" | |
| 437 | + | } | |
| 438 | + | } | |
| 439 | + | } | |
| 440 | + | } | |
| 441 | + | }, | |
| 442 | + | "comment_doc.yml": { | |
| 443 | + | "errors": [ | |
| 444 | + | "comment_doc.yml: Expected a scalar value, a sequence, or a mapping" | |
| 445 | + | ] | |
| 446 | + | }, | |
| 447 | + | "concurrency_bad.yml": { | |
| 448 | + | "errors": [ | |
| 449 | + | "concurrency_bad.yml (Line: 2, Col: 25): Unexpected value 'bogus'" | |
| 450 | + | ], | |
| 451 | + | "jobs": [] | |
| 452 | + | }, | |
| 453 | + | "concurrency_converter.yml": { | |
| 454 | + | "errors": [], | |
| 455 | + | "jobs": [ | |
| 456 | + | { | |
| 457 | + | "id": "a", | |
| 458 | + | "name": "a", | |
| 459 | + | "if": "success()", | |
| 460 | + | "needs": [], | |
| 461 | + | "steps": [ | |
| 462 | + | { | |
| 463 | + | "id": "__run", | |
| 464 | + | "if": "success()" | |
| 465 | + | } | |
| 466 | + | ] | |
| 467 | + | } | |
| 468 | + | ], | |
| 469 | + | "events": { | |
| 470 | + | "push": {} | |
| 471 | + | } | |
| 472 | + | }, | |
| 473 | + | "concurrency_job.yml": { | |
| 474 | + | "errors": [ | |
| 475 | + | "concurrency_job.yml (Line: 5, Col: 49): Unexpected value '1'" | |
| 476 | + | ], | |
| 477 | + | "jobs": [] | |
| 478 | + | }, | |
| 479 | + | "concurrency_queue.yml": { | |
| 480 | + | "errors": [], | |
| 481 | + | "jobs": [ | |
| 482 | + | { | |
| 483 | + | "id": "a", | |
| 484 | + | "name": "a", | |
| 485 | + | "if": "success()", | |
| 486 | + | "needs": [], | |
| 487 | + | "steps": [ | |
| 488 | + | { | |
| 489 | + | "id": "__run", | |
| 490 | + | "if": "success()" | |
| 491 | + | } | |
| 492 | + | ] | |
| 493 | + | } | |
| 494 | + | ], | |
| 495 | + | "events": { | |
| 496 | + | "push": {} | |
| 497 | + | } | |
| 498 | + | }, | |
| 499 | + | "concurrency_queue_bad.yml": { | |
| 500 | + | "errors": [ | |
| 501 | + | "concurrency_queue_bad.yml (Line: 5, Col: 36): Unexpected value 'other'" | |
| 502 | + | ], | |
| 503 | + | "jobs": [] | |
| 504 | + | }, | |
| 505 | + | "concurrency_string.yml": { | |
| 506 | + | "errors": [], | |
| 507 | + | "jobs": [ | |
| 508 | + | { | |
| 509 | + | "id": "a", | |
| 510 | + | "name": "a", | |
| 511 | + | "if": "success()", | |
| 512 | + | "needs": [], | |
| 513 | + | "steps": [ | |
| 514 | + | { | |
| 515 | + | "id": "__run", | |
| 516 | + | "if": "success()" | |
| 517 | + | } | |
| 518 | + | ] | |
| 519 | + | } | |
| 520 | + | ], | |
| 521 | + | "events": { | |
| 522 | + | "push": {} | |
| 523 | + | } | |
| 524 | + | }, | |
| 525 | + | "container_bad.yml": { | |
| 526 | + | "errors": [ | |
| 527 | + | "container_bad.yml (Line: 5, Col: 27): Unexpected value 'bogus'" | |
| 528 | + | ], | |
| 529 | + | "jobs": [] | |
| 530 | + | }, | |
| 531 | + | "container_converter.yml": { | |
| 532 | + | "errors": [], | |
| 533 | + | "jobs": [ | |
| 534 | + | { | |
| 535 | + | "id": "a", | |
| 536 | + | "name": "a", | |
| 537 | + | "if": "success()", | |
| 538 | + | "needs": [], | |
| 539 | + | "steps": [ | |
| 540 | + | { | |
| 541 | + | "id": "__run", | |
| 542 | + | "if": "success()" | |
| 543 | + | } | |
| 544 | + | ] | |
| 545 | + | } | |
| 546 | + | ], | |
| 547 | + | "events": { | |
| 548 | + | "push": {} | |
| 549 | + | } | |
| 550 | + | }, | |
| 551 | + | "container_empty_image.yml": { | |
| 552 | + | "errors": [ | |
| 553 | + | "container_empty_image.yml (Line: 5, Col: 24): Unexpected value ''" | |
| 554 | + | ], | |
| 555 | + | "jobs": [] | |
| 556 | + | }, | |
| 557 | + | "container_entrypoint.yml": { | |
| 558 | + | "errors": [ | |
| 559 | + | "container_entrypoint.yml (Line: 5, Col: 27): Unexpected value 'entrypoint'" | |
| 560 | + | ], | |
| 561 | + | "jobs": [] | |
| 562 | + | }, | |
| 563 | + | "container_expr.yml": { | |
| 564 | + | "errors": [], | |
| 565 | + | "jobs": [ | |
| 566 | + | { | |
| 567 | + | "id": "a", | |
| 568 | + | "name": "a", | |
| 569 | + | "if": "success()", | |
| 570 | + | "needs": [], | |
| 571 | + | "steps": [ | |
| 572 | + | { | |
| 573 | + | "id": "__run", | |
| 574 | + | "if": "success()" | |
| 575 | + | } | |
| 576 | + | ] | |
| 577 | + | } | |
| 578 | + | ], | |
| 579 | + | "events": { | |
| 580 | + | "push": {} | |
| 581 | + | } | |
| 582 | + | }, | |
| 583 | + | "container_no_image.yml": { | |
| 584 | + | "errors": [ | |
| 585 | + | "container_no_image.yml (Line: 5, Col: 16): Container image cannot be empty" | |
| 586 | + | ], | |
| 587 | + | "jobs": [ | |
| 588 | + | { | |
| 589 | + | "id": "a", | |
| 590 | + | "name": "a", | |
| 591 | + | "if": "success()", | |
| 592 | + | "needs": [], | |
| 593 | + | "steps": [ | |
| 594 | + | { | |
| 595 | + | "id": "__run", | |
| 596 | + | "if": "success()" | |
| 597 | + | } | |
| 598 | + | ] | |
| 599 | + | } | |
| 600 | + | ], | |
| 601 | + | "events": { | |
| 602 | + | "push": {} | |
| 603 | + | } | |
| 604 | + | }, | |
| 605 | + | "container_string.yml": { | |
| 606 | + | "errors": [], | |
| 607 | + | "jobs": [ | |
| 608 | + | { | |
| 609 | + | "id": "a", | |
| 610 | + | "name": "a", | |
| 611 | + | "if": "success()", | |
| 612 | + | "needs": [], | |
| 613 | + | "steps": [ | |
| 614 | + | { | |
| 615 | + | "id": "__run", | |
| 616 | + | "if": "success()" | |
| 617 | + | } | |
| 618 | + | ] | |
| 619 | + | } | |
| 620 | + | ], | |
| 621 | + | "events": { | |
| 622 | + | "push": {} | |
| 623 | + | } | |
| 624 | + | }, | |
| 625 | + | "context_always_job_env.yml": { | |
| 626 | + | "errors": [ | |
| 627 | + | "context_always_job_env.yml (Line: 6, Col: 10): Unrecognized function: 'always'" | |
| 628 | + | ], | |
| 629 | + | "jobs": [] | |
| 630 | + | }, | |
| 631 | + | "context_defaults_run.yml": { | |
| 632 | + | "errors": [ | |
| 633 | + | "context_defaults_run.yml (Line: 7, Col: 28): Unrecognized named-value: 'steps'" | |
| 634 | + | ], | |
| 635 | + | "jobs": [] | |
| 636 | + | }, | |
| 637 | + | "context_env_secrets.yml": { | |
| 638 | + | "errors": [], | |
| 639 | + | "jobs": [ | |
| 640 | + | { | |
| 641 | + | "id": "a", | |
| 642 | + | "name": "a", | |
| 643 | + | "if": "success()", | |
| 644 | + | "needs": [], | |
| 645 | + | "steps": [ | |
| 646 | + | { | |
| 647 | + | "id": "__run", | |
| 648 | + | "if": "success()" | |
| 649 | + | } | |
| 650 | + | ] | |
| 651 | + | } | |
| 652 | + | ], | |
| 653 | + | "events": { | |
| 654 | + | "push": {} | |
| 655 | + | } | |
| 656 | + | }, | |
| 657 | + | "context_job_name.yml": { | |
| 658 | + | "errors": [ | |
| 659 | + | "context_job_name.yml (Line: 5, Col: 11): Unrecognized named-value: 'steps'" | |
| 660 | + | ], | |
| 661 | + | "jobs": [] | |
| 662 | + | }, | |
| 663 | + | "context_job_outputs.yml": { | |
| 664 | + | "errors": [], | |
| 665 | + | "jobs": [ | |
| 666 | + | { | |
| 667 | + | "id": "a", | |
| 668 | + | "name": "a", | |
| 669 | + | "if": "success()", | |
| 670 | + | "needs": [], | |
| 671 | + | "steps": [ | |
| 672 | + | { | |
| 673 | + | "id": "__run", | |
| 674 | + | "if": "success()" | |
| 675 | + | } | |
| 676 | + | ] | |
| 677 | + | } | |
| 678 | + | ], | |
| 679 | + | "events": { | |
| 680 | + | "push": {} | |
| 681 | + | } | |
| 682 | + | }, | |
| 683 | + | "context_outputs_hash.yml": { | |
| 684 | + | "errors": [ | |
| 685 | + | "context_outputs_hash.yml (Line: 6, Col: 10): Unrecognized function: 'hashFiles'" | |
| 686 | + | ], | |
| 687 | + | "jobs": [] | |
| 688 | + | }, | |
| 689 | + | "context_run_name.yml": { | |
| 690 | + | "errors": [ | |
| 691 | + | "context_run_name.yml (Line: 2, Col: 11): Unrecognized named-value: 'matrix'" | |
| 692 | + | ], | |
| 693 | + | "jobs": [] | |
| 694 | + | }, | |
| 695 | + | "context_runs_on_secrets.yml": { | |
| 696 | + | "errors": [ | |
| 697 | + | "context_runs_on_secrets.yml (Line: 5, Col: 5): 'runs-on' is already defined" | |
| 698 | + | ], | |
| 699 | + | "jobs": [] | |
| 700 | + | }, | |
| 701 | + | "context_step_run.yml": { | |
| 702 | + | "errors": [ | |
| 703 | + | "context_step_run.yml (Line: 5, Col: 19): Unexpected symbol: '\"a\"'. Located at position 11 within expression: hashFiles(\"a\")" | |
| 704 | + | ], | |
| 705 | + | "jobs": [] | |
| 706 | + | }, | |
| 707 | + | "context_success_in_run.yml": { | |
| 708 | + | "errors": [ | |
| 709 | + | "context_success_in_run.yml (Line: 5, Col: 19): Unrecognized function: 'success'" | |
| 710 | + | ], | |
| 711 | + | "jobs": [] | |
| 712 | + | }, | |
| 713 | + | "context_unknown_func.yml": { | |
| 714 | + | "errors": [ | |
| 715 | + | "context_unknown_func.yml (Line: 5, Col: 19): Unrecognized function: 'nope'" | |
| 716 | + | ], | |
| 717 | + | "jobs": [] | |
| 718 | + | }, | |
| 719 | + | "context_workflow_env.yml": { | |
| 720 | + | "errors": [ | |
| 721 | + | "context_workflow_env.yml (Line: 3, Col: 6): Unrecognized named-value: 'matrix'" | |
| 722 | + | ], | |
| 723 | + | "jobs": [] | |
| 724 | + | }, | |
| 725 | + | "credentials_bad.yml": { | |
| 726 | + | "errors": [ | |
| 727 | + | "credentials_bad.yml (Line: 5, Col: 54): Unexpected value 'token'" | |
| 728 | + | ], | |
| 729 | + | "jobs": [] | |
| 730 | + | }, | |
| 731 | + | "cron_only_bad.yml": { | |
| 732 | + | "errors": [ | |
| 733 | + | "cron_only_bad.yml (Line: 3, Col: 13): Invalid cron expression. Expected format: '* * * * *' (minute hour day month weekday)", | |
| 734 | + | "cron_only_bad.yml (Line: 4, Col: 13): Invalid cron expression. Expected format: '* * * * *' (minute hour day month weekday)", | |
| 735 | + | "cron_only_bad.yml (Line: 5, Col: 13): Invalid cron expression. Expected format: '* * * * *' (minute hour day month weekday)", | |
| 736 | + | "cron_only_bad.yml (Line: 6, Col: 13): Invalid cron expression. Expected format: '* * * * *' (minute hour day month weekday)", | |
| 737 | + | "cron_only_bad.yml (Line: 8, Col: 13): Invalid cron expression. Expected format: '* * * * *' (minute hour day month weekday)", | |
| 738 | + | "cron_only_bad.yml (Line: 9, Col: 13): Invalid cron expression. Expected format: '* * * * *' (minute hour day month weekday)", | |
| 739 | + | "cron_only_bad.yml (Line: 14, Col: 13): Invalid cron expression. Expected format: '* * * * *' (minute hour day month weekday)", | |
| 740 | + | "cron_only_bad.yml (Line: 15, Col: 13): Invalid cron expression. Expected format: '* * * * *' (minute hour day month weekday)", | |
| 741 | + | "cron_only_bad.yml (Line: 16, Col: 13): Invalid cron expression. Expected format: '* * * * *' (minute hour day month weekday)", | |
| 742 | + | "cron_only_bad.yml (Line: 17, Col: 13): Invalid cron expression. Expected format: '* * * * *' (minute hour day month weekday)", | |
| 743 | + | "cron_only_bad.yml (Line: 18, Col: 13): Invalid cron expression. Expected format: '* * * * *' (minute hour day month weekday)", | |
| 744 | + | "cron_only_bad.yml (Line: 19, Col: 13): Invalid cron expression. Expected format: '* * * * *' (minute hour day month weekday)" | |
| 745 | + | ], | |
| 746 | + | "jobs": [ | |
| 747 | + | { | |
| 748 | + | "id": "a", | |
| 749 | + | "name": "a", | |
| 750 | + | "if": "success()", | |
| 751 | + | "needs": [], | |
| 752 | + | "steps": [ | |
| 753 | + | { | |
| 754 | + | "id": "__run", | |
| 755 | + | "if": "success()" | |
| 756 | + | } | |
| 757 | + | ] | |
| 758 | + | } | |
| 759 | + | ], | |
| 760 | + | "events": { | |
| 761 | + | "schedule": [ | |
| 762 | + | { | |
| 763 | + | "cron": "bad" | |
| 764 | + | }, | |
| 765 | + | { | |
| 766 | + | "cron": "61 * * * *" | |
| 767 | + | }, | |
| 768 | + | { | |
| 769 | + | "cron": "*/0 * * * *" | |
| 770 | + | }, | |
| 771 | + | { | |
| 772 | + | "cron": "0 0 * * 7" | |
| 773 | + | }, | |
| 774 | + | { | |
| 775 | + | "cron": "0 0 * jan-mar mon" | |
| 776 | + | }, | |
| 777 | + | { | |
| 778 | + | "cron": "0 0 * mar-jan mon" | |
| 779 | + | }, | |
| 780 | + | { | |
| 781 | + | "cron": "* * * * * *" | |
| 782 | + | }, | |
| 783 | + | { | |
| 784 | + | "cron": "1-5/2 * * * *" | |
| 785 | + | }, | |
| 786 | + | { | |
| 787 | + | "cron": "*/5 */2 1,15 * *" | |
| 788 | + | }, | |
| 789 | + | { | |
| 790 | + | "cron": "5 4 * * sun" | |
| 791 | + | }, | |
| 792 | + | { | |
| 793 | + | "cron": "0 0 * * *" | |
| 794 | + | }, | |
| 795 | + | { | |
| 796 | + | "cron": " 0 0 * * *" | |
| 797 | + | }, | |
| 798 | + | { | |
| 799 | + | "cron": "1,,2 * * * *" | |
| 800 | + | }, | |
| 801 | + | { | |
| 802 | + | "cron": "*/5/2 * * * *" | |
| 803 | + | }, | |
| 804 | + | { | |
| 805 | + | "cron": "1-2-3 * * * *" | |
| 806 | + | }, | |
| 807 | + | { | |
| 808 | + | "cron": "*-5 * * * *" | |
| 809 | + | }, | |
| 810 | + | { | |
| 811 | + | "cron": "5-1 * * * *" | |
| 812 | + | }, | |
| 813 | + | { | |
| 814 | + | "cron": "0 0 1-31/2 * *" | |
| 815 | + | }, | |
| 816 | + | { | |
| 817 | + | "cron": "0 0 * * MON" | |
| 818 | + | }, | |
| 819 | + | { | |
| 820 | + | "cron": "0 0 * Feb *" | |
| 821 | + | } | |
| 822 | + | ] | |
| 823 | + | } | |
| 824 | + | }, | |
| 825 | + | "description_key.yml": { | |
| 826 | + | "errors": [], | |
| 827 | + | "jobs": [ | |
| 828 | + | { | |
| 829 | + | "id": "a", | |
| 830 | + | "name": "a", | |
| 831 | + | "if": "success()", | |
| 832 | + | "needs": [], | |
| 833 | + | "steps": [ | |
| 834 | + | { | |
| 835 | + | "id": "__run", | |
| 836 | + | "if": "success()" | |
| 837 | + | } | |
| 838 | + | ] | |
| 839 | + | } | |
| 840 | + | ], | |
| 841 | + | "events": { | |
| 842 | + | "push": {} | |
| 843 | + | } | |
| 844 | + | }, | |
| 845 | + | "dispatch_inputs.yml": { | |
| 846 | + | "errors": [ | |
| 847 | + | "dispatch_inputs.yml (Line: 5, Col: 9): Missing 'options' for choice input", | |
| 848 | + | "dispatch_inputs.yml (Line: 7, Col: 9): Input type is not 'choice', but 'options' is defined", | |
| 849 | + | "dispatch_inputs.yml (Line: 11, Col: 18): Unexpected type 'StringToken' encountered while reading 'input default'. The type 'BooleanToken' was expected." | |
| 850 | + | ], | |
| 851 | + | "jobs": [ | |
| 852 | + | { | |
| 853 | + | "id": "a", | |
| 854 | + | "name": "a", | |
| 855 | + | "if": "success()", | |
| 856 | + | "needs": [], | |
| 857 | + | "steps": [ | |
| 858 | + | { | |
| 859 | + | "id": "__run", | |
| 860 | + | "if": "success()" | |
| 861 | + | } | |
| 862 | + | ] | |
| 863 | + | } | |
| 864 | + | ], | |
| 865 | + | "events": { | |
| 866 | + | "workflow_dispatch": { | |
| 867 | + | "inputs": { | |
| 868 | + | "a": { | |
| 869 | + | "type": "choice" | |
| 870 | + | }, | |
| 871 | + | "b": { | |
| 872 | + | "type": "string", | |
| 873 | + | "options": [ | |
| 874 | + | "x" | |
| 875 | + | ] | |
| 876 | + | }, | |
| 877 | + | "c": { | |
| 878 | + | "type": "boolean" | |
| 879 | + | }, | |
| 880 | + | "d": { | |
| 881 | + | "type": "environment" | |
| 882 | + | }, | |
| 883 | + | "e": { | |
| 884 | + | "type": "string", | |
| 885 | + | "description": "x", | |
| 886 | + | "required": true, | |
| 887 | + | "default": "y" | |
| 888 | + | }, | |
| 889 | + | "f": {} | |
| 890 | + | } | |
| 891 | + | } | |
| 892 | + | } | |
| 893 | + | }, | |
| 894 | + | "dup_job_ids_case.yml": { | |
| 895 | + | "errors": [ | |
| 896 | + | "dup_job_ids_case.yml (Line: 6, Col: 3): 'A' is already defined" | |
| 897 | + | ], | |
| 898 | + | "jobs": [] | |
| 899 | + | }, | |
| 900 | + | "dup_keys.yml": { | |
| 901 | + | "errors": [ | |
| 902 | + | "dup_keys.yml (Line: 2, Col: 1): 'on' is already defined", | |
| 903 | + | "dup_keys.yml (Line: 3, Col: 1): Unexpected value 'Name'", | |
| 904 | + | "dup_keys.yml (Line: 4, Col: 1): 'name' is already defined", | |
| 905 | + | "dup_keys.yml (Line: 8, Col: 5): 'runs-on' is already defined" | |
| 906 | + | ], | |
| 907 | + | "jobs": [] | |
| 908 | + | }, | |
| 909 | + | "empty_doc.yml": { | |
| 910 | + | "errors": [ | |
| 911 | + | "empty_doc.yml: Expected a scalar value, a sequence, or a mapping" | |
| 912 | + | ] | |
| 913 | + | }, | |
| 914 | + | "env_nonmap_converter.yml": { | |
| 915 | + | "errors": [ | |
| 916 | + | "env_nonmap_converter.yml (Line: 2, Col: 6): Unexpected value 'x'" | |
| 917 | + | ], | |
| 918 | + | "jobs": [] | |
| 919 | + | }, | |
| 920 | + | "environment_converter.yml": { | |
| 921 | + | "errors": [ | |
| 922 | + | "environment_converter.yml (Line: 5, Col: 40): Unexpected value '1'" | |
| 923 | + | ], | |
| 924 | + | "jobs": [] | |
| 925 | + | }, | |
| 926 | + | "environment_forms.yml": { | |
| 927 | + | "errors": [], | |
| 928 | + | "jobs": [ | |
| 929 | + | { | |
| 930 | + | "id": "a", | |
| 931 | + | "name": "a", | |
| 932 | + | "if": "success()", | |
| 933 | + | "needs": [], | |
| 934 | + | "steps": [ | |
| 935 | + | { | |
| 936 | + | "id": "__run", | |
| 937 | + | "if": "success()" | |
| 938 | + | } | |
| 939 | + | ] | |
| 940 | + | } | |
| 941 | + | ], | |
| 942 | + | "events": { | |
| 943 | + | "push": {} | |
| 944 | + | } | |
| 945 | + | }, | |
| 946 | + | "environment_map.yml": { | |
| 947 | + | "errors": [], | |
| 948 | + | "jobs": [ | |
| 949 | + | { | |
| 950 | + | "id": "a", | |
| 951 | + | "name": "a", | |
| 952 | + | "if": "success()", | |
| 953 | + | "needs": [], | |
| 954 | + | "steps": [ | |
| 955 | + | { | |
| 956 | + | "id": "__run", | |
| 957 | + | "if": "success()" | |
| 958 | + | } | |
| 959 | + | ] | |
| 960 | + | } | |
| 961 | + | ], | |
| 962 | + | "events": { | |
| 963 | + | "push": {} | |
| 964 | + | } | |
| 965 | + | }, | |
| 966 | + | "environment_no_name.yml": { | |
| 967 | + | "errors": [ | |
| 968 | + | "environment_no_name.yml (Line: 5, Col: 18): Required property is missing: name" | |
| 969 | + | ], | |
| 970 | + | "jobs": [] | |
| 971 | + | }, | |
| 972 | + | "expr_empty.yml": { | |
| 973 | + | "errors": [ | |
| 974 | + | "expr_empty.yml (Line: 6, Col: 14): An expression was expected" | |
| 975 | + | ], | |
| 976 | + | "jobs": [] | |
| 977 | + | }, | |
| 978 | + | "expr_in_key_env.yml": { | |
| 979 | + | "errors": [], | |
| 980 | + | "jobs": [ | |
| 981 | + | { | |
| 982 | + | "id": "a", | |
| 983 | + | "name": "a", | |
| 984 | + | "if": "success()", | |
| 985 | + | "needs": [], | |
| 986 | + | "steps": [ | |
| 987 | + | { | |
| 988 | + | "id": "__run", | |
| 989 | + | "if": "success()" | |
| 990 | + | } | |
| 991 | + | ] | |
| 992 | + | } | |
| 993 | + | ], | |
| 994 | + | "events": { | |
| 995 | + | "push": {} | |
| 996 | + | } | |
| 997 | + | }, | |
| 998 | + | "expr_insert_embedded.yml": { | |
| 999 | + | "errors": [ | |
| 1000 | + | "expr_insert_embedded.yml (Line: 7, Col: 12): The directive 'insert' is not allowed in this context. Directives are not supported for expressions that are embedded within a string. Directives are only supported when the entire value is an expression.", | |
| 1001 | + | "expr_insert_embedded.yml (Line: 7, Col: 12): Unexpected value 'x ${{ insert }}'" | |
| 1002 | + | ], | |
| 1003 | + | "jobs": [] | |
| 1004 | + | }, | |
| 1005 | + | "expr_insert_key.yml": { | |
| 1006 | + | "errors": [ | |
| 1007 | + | "expr_insert_key.yml (Line: 6, Col: 15): Unexpected value '${{ insert }}'" | |
| 1008 | + | ], | |
| 1009 | + | "jobs": [] | |
| 1010 | + | }, | |
| 1011 | + | "expr_insert_matrix.yml": { | |
| 1012 | + | "errors": [], | |
| 1013 | + | "jobs": [ | |
| 1014 | + | { | |
| 1015 | + | "id": "a", | |
| 1016 | + | "name": "a", | |
| 1017 | + | "if": "success()", | |
| 1018 | + | "needs": [], | |
| 1019 | + | "steps": [ | |
| 1020 | + | { | |
| 1021 | + | "id": "__run", | |
| 1022 | + | "if": "success()" | |
| 1023 | + | } | |
| 1024 | + | ] | |
| 1025 | + | } | |
| 1026 | + | ], | |
| 1027 | + | "events": { | |
| 1028 | + | "push": {} | |
| 1029 | + | } | |
| 1030 | + | }, | |
| 1031 | + | "expr_insert_params.yml": { | |
| 1032 | + | "errors": [ | |
| 1033 | + | "expr_insert_params.yml (Line: 7, Col: 9): Exactly 0 parameter(s) were expected following the directive 'insert'. Actual parameter count: 1", | |
| 1034 | + | "expr_insert_params.yml (Line: 7, Col: 26): Unexpected value '1'" | |
| 1035 | + | ], | |
| 1036 | + | "jobs": [] | |
| 1037 | + | }, | |
| 1038 | + | "expr_key_not_allowed.yml": { | |
| 1039 | + | "errors": [ | |
| 1040 | + | "expr_key_not_allowed.yml (Line: 6, Col: 7): Unrecognized named-value: 'github'" | |
| 1041 | + | ], | |
| 1042 | + | "jobs": [] | |
| 1043 | + | }, | |
| 1044 | + | "expr_literal.yml": { | |
| 1045 | + | "errors": [], | |
| 1046 | + | "jobs": [ | |
| 1047 | + | { | |
| 1048 | + | "id": "a", | |
| 1049 | + | "name": "a", | |
| 1050 | + | "if": "success()", | |
| 1051 | + | "needs": [], | |
| 1052 | + | "steps": [ | |
| 1053 | + | { | |
| 1054 | + | "id": "__run", | |
| 1055 | + | "if": "success()" | |
| 1056 | + | } | |
| 1057 | + | ] | |
| 1058 | + | } | |
| 1059 | + | ], | |
| 1060 | + | "events": { | |
| 1061 | + | "push": {} | |
| 1062 | + | } | |
| 1063 | + | }, | |
| 1064 | + | "expr_multi.yml": { | |
| 1065 | + | "errors": [ | |
| 1066 | + | "expr_multi.yml (Line: 6, Col: 14): Unrecognized named-value: 'foo'", | |
| 1067 | + | "expr_multi.yml (Line: 6, Col: 14): Unexpected symbol: '+'. Located at position 3 within expression: 1 +" | |
| 1068 | + | ], | |
| 1069 | + | "jobs": [] | |
| 1070 | + | }, | |
| 1071 | + | "expr_not_closed.yml": { | |
| 1072 | + | "errors": [ | |
| 1073 | + | "expr_not_closed.yml (Line: 6, Col: 14): The expression is not closed. An unescaped ${{ sequence was found, but the closing }} sequence was not found." | |
| 1074 | + | ], | |
| 1075 | + | "jobs": [] | |
| 1076 | + | }, | |
| 1077 | + | "expr_string_braces.yml": { | |
| 1078 | + | "errors": [], | |
| 1079 | + | "jobs": [ | |
| 1080 | + | { | |
| 1081 | + | "id": "a", | |
| 1082 | + | "name": "a", | |
| 1083 | + | "if": "success()", | |
| 1084 | + | "needs": [], | |
| 1085 | + | "steps": [ | |
| 1086 | + | { | |
| 1087 | + | "id": "__run", | |
| 1088 | + | "if": "success()" | |
| 1089 | + | } | |
| 1090 | + | ] | |
| 1091 | + | } | |
| 1092 | + | ], | |
| 1093 | + | "events": { | |
| 1094 | + | "push": {} | |
| 1095 | + | } | |
| 1096 | + | }, | |
| 1097 | + | "if_bad_arity.yml": { | |
| 1098 | + | "errors": [ | |
| 1099 | + | "if_bad_arity.yml (Line: 5, Col: 26): Too many parameters supplied: 'failure'" | |
| 1100 | + | ], | |
| 1101 | + | "jobs": [ | |
| 1102 | + | { | |
| 1103 | + | "id": "a", | |
| 1104 | + | "name": "a", | |
| 1105 | + | "if": "success()", | |
| 1106 | + | "needs": [], | |
| 1107 | + | "steps": [ | |
| 1108 | + | { | |
| 1109 | + | "id": "__run", | |
| 1110 | + | "if": "success()" | |
| 1111 | + | } | |
| 1112 | + | ] | |
| 1113 | + | } | |
| 1114 | + | ], | |
| 1115 | + | "events": { | |
| 1116 | + | "push": {} | |
| 1117 | + | } | |
| 1118 | + | }, | |
| 1119 | + | "if_job_ctx.yml": { | |
| 1120 | + | "errors": [ | |
| 1121 | + | "if_job_ctx.yml (Line: 5, Col: 9): Unrecognized named-value: 'steps'" | |
| 1122 | + | ], | |
| 1123 | + | "jobs": [ | |
| 1124 | + | { | |
| 1125 | + | "id": "a", | |
| 1126 | + | "name": "a", | |
| 1127 | + | "if": "success()", | |
| 1128 | + | "needs": [], | |
| 1129 | + | "steps": [ | |
| 1130 | + | { | |
| 1131 | + | "id": "__run", | |
| 1132 | + | "if": "success()" | |
| 1133 | + | } | |
| 1134 | + | ] | |
| 1135 | + | } | |
| 1136 | + | ], | |
| 1137 | + | "events": { | |
| 1138 | + | "push": {} | |
| 1139 | + | } | |
| 1140 | + | }, | |
| 1141 | + | "if_step_job_funcs.yml": { | |
| 1142 | + | "errors": [], | |
| 1143 | + | "jobs": [ | |
| 1144 | + | { | |
| 1145 | + | "id": "a", | |
| 1146 | + | "name": "a", | |
| 1147 | + | "if": "success(1)", | |
| 1148 | + | "needs": [], | |
| 1149 | + | "steps": [ | |
| 1150 | + | { | |
| 1151 | + | "id": "__run", | |
| 1152 | + | "if": "success()" | |
| 1153 | + | } | |
| 1154 | + | ] | |
| 1155 | + | } | |
| 1156 | + | ], | |
| 1157 | + | "events": { | |
| 1158 | + | "push": {} | |
| 1159 | + | } | |
| 1160 | + | }, | |
| 1161 | + | "if_wrapping.yml": { | |
| 1162 | + | "errors": [], | |
| 1163 | + | "jobs": [ | |
| 1164 | + | { | |
| 1165 | + | "id": "a", | |
| 1166 | + | "name": "a", | |
| 1167 | + | "if": "success() && (github.ref == 'x')", | |
| 1168 | + | "needs": [], | |
| 1169 | + | "steps": [ | |
| 1170 | + | { | |
| 1171 | + | "id": "__run", | |
| 1172 | + | "if": "success() && (hashFiles('a'))" | |
| 1173 | + | }, | |
| 1174 | + | { | |
| 1175 | + | "id": "__run_2", | |
| 1176 | + | "if": "always()" | |
| 1177 | + | }, | |
| 1178 | + | { | |
| 1179 | + | "id": "__run_3", | |
| 1180 | + | "if": "failure()" | |
| 1181 | + | }, | |
| 1182 | + | { | |
| 1183 | + | "id": "__run_4", | |
| 1184 | + | "if": "success()" | |
| 1185 | + | }, | |
| 1186 | + | { | |
| 1187 | + | "id": "__run_5", | |
| 1188 | + | "if": "!cancelled()" | |
| 1189 | + | }, | |
| 1190 | + | { | |
| 1191 | + | "id": "__run_6", | |
| 1192 | + | "if": "contains(github.ref, 'a') || success()" | |
| 1193 | + | }, | |
| 1194 | + | { | |
| 1195 | + | "id": "__run_7", | |
| 1196 | + | "if": "toJSON(success())" | |
| 1197 | + | }, | |
| 1198 | + | { | |
| 1199 | + | "id": "__run_8", | |
| 1200 | + | "if": "success() && (true)" | |
| 1201 | + | }, | |
| 1202 | + | { | |
| 1203 | + | "id": "__run_9", | |
| 1204 | + | "if": "success() && (format('{0} == ''x''', github.ref))" | |
| 1205 | + | } | |
| 1206 | + | ] | |
| 1207 | + | } | |
| 1208 | + | ], | |
| 1209 | + | "events": { | |
| 1210 | + | "push": {} | |
| 1211 | + | } | |
| 1212 | + | }, | |
| 1213 | + | "if_wrapping_nonstring.yml": { | |
| 1214 | + | "errors": [], | |
| 1215 | + | "jobs": [ | |
| 1216 | + | { | |
| 1217 | + | "id": "a", | |
| 1218 | + | "name": "a", | |
| 1219 | + | "if": "success() && (5)", | |
| 1220 | + | "needs": [], | |
| 1221 | + | "steps": [ | |
| 1222 | + | { | |
| 1223 | + | "id": "__run", | |
| 1224 | + | "if": "success()" | |
| 1225 | + | }, | |
| 1226 | + | { | |
| 1227 | + | "id": "__run_2", | |
| 1228 | + | "if": "success() && (false)" | |
| 1229 | + | } | |
| 1230 | + | ] | |
| 1231 | + | } | |
| 1232 | + | ], | |
| 1233 | + | "events": { | |
| 1234 | + | "push": {} | |
| 1235 | + | } | |
| 1236 | + | }, | |
| 1237 | + | "inputs_converter_errors.yml": { | |
| 1238 | + | "errors": [ | |
| 1239 | + | "inputs_converter_errors.yml (Line: 21, Col: 18): Unrecognized named-value: 'github'" | |
| 1240 | + | ], | |
| 1241 | + | "jobs": [] | |
| 1242 | + | }, | |
| 1243 | + | "job_env_nonmap.yml": { | |
| 1244 | + | "errors": [ | |
| 1245 | + | "job_env_nonmap.yml (Line: 5, Col: 10): Unexpected value 'x'" | |
| 1246 | + | ], | |
| 1247 | + | "jobs": [] | |
| 1248 | + | }, | |
| 1249 | + | "job_name_empty.yml": { | |
| 1250 | + | "errors": [], | |
| 1251 | + | "jobs": [ | |
| 1252 | + | { | |
| 1253 | + | "id": "a", | |
| 1254 | + | "name": "a", | |
| 1255 | + | "if": "success()", | |
| 1256 | + | "needs": [], | |
| 1257 | + | "steps": [ | |
| 1258 | + | { | |
| 1259 | + | "id": "__run", | |
| 1260 | + | "if": "success()" | |
| 1261 | + | } | |
| 1262 | + | ] | |
| 1263 | + | } | |
| 1264 | + | ], | |
| 1265 | + | "events": { | |
| 1266 | + | "push": {} | |
| 1267 | + | } | |
| 1268 | + | }, | |
| 1269 | + | "job_name_expr.yml": { | |
| 1270 | + | "errors": [], | |
| 1271 | + | "jobs": [ | |
| 1272 | + | { | |
| 1273 | + | "id": "a", | |
| 1274 | + | "name": "${{ format('x-{0}', matrix.a) }}", | |
| 1275 | + | "if": "success()", | |
| 1276 | + | "needs": [], | |
| 1277 | + | "steps": [ | |
| 1278 | + | { | |
| 1279 | + | "id": "__run", | |
| 1280 | + | "if": "success()" | |
| 1281 | + | } | |
| 1282 | + | ] | |
| 1283 | + | } | |
| 1284 | + | ], | |
| 1285 | + | "events": { | |
| 1286 | + | "push": {} | |
| 1287 | + | } | |
| 1288 | + | }, | |
| 1289 | + | "job_not_map.yml": { | |
| 1290 | + | "errors": [ | |
| 1291 | + | "job_not_map.yml (Line: 3, Col: 6): Unexpected value 'x'" | |
| 1292 | + | ], | |
| 1293 | + | "jobs": [] | |
| 1294 | + | }, | |
| 1295 | + | "jobs_dropped_job.yml": { | |
| 1296 | + | "errors": [ | |
| 1297 | + | "jobs_dropped_job.yml (Line: 8, Col: 10): Unexpected value 'x'", | |
| 1298 | + | "jobs_dropped_job.yml (Line: 9, Col: 14): Unexpected value 'y'" | |
| 1299 | + | ], | |
| 1300 | + | "jobs": [] | |
| 1301 | + | }, | |
| 1302 | + | "jobs_empty.yml": { | |
| 1303 | + | "errors": [ | |
| 1304 | + | "jobs_empty.yml (Line: 2, Col: 7): The workflow must contain at least one job with no dependencies." | |
| 1305 | + | ], | |
| 1306 | + | "jobs": [], | |
| 1307 | + | "events": { | |
| 1308 | + | "push": {} | |
| 1309 | + | } | |
| 1310 | + | }, | |
| 1311 | + | "jobs_not_map.yml": { | |
| 1312 | + | "errors": [ | |
| 1313 | + | "jobs_not_map.yml (Line: 2, Col: 7): A sequence was not expected" | |
| 1314 | + | ], | |
| 1315 | + | "jobs": [] | |
| 1316 | + | }, | |
| 1317 | + | "multiline_folded.yml": { | |
| 1318 | + | "errors": [ | |
| 1319 | + | "multiline_folded.yml (Line: 6, Col: 14): Unrecognized named-value: 'bar'" | |
| 1320 | + | ], | |
| 1321 | + | "jobs": [] | |
| 1322 | + | }, | |
| 1323 | + | "multiline_plain.yml": { | |
| 1324 | + | "errors": [ | |
| 1325 | + | "multiline_plain.yml (Line: 6, Col: 14): Unrecognized named-value: 'bar'" | |
| 1326 | + | ], | |
| 1327 | + | "jobs": [] | |
| 1328 | + | }, | |
| 1329 | + | "needs_bad_type.yml": { | |
| 1330 | + | "errors": [ | |
| 1331 | + | "needs_bad_type.yml (Line: 3, Col: 3): The workflow must contain at least one job with no dependencies." | |
| 1332 | + | ], | |
| 1333 | + | "jobs": [ | |
| 1334 | + | { | |
| 1335 | + | "id": "a", | |
| 1336 | + | "name": "a", | |
| 1337 | + | "if": "success()", | |
| 1338 | + | "needs": [ | |
| 1339 | + | "3" | |
| 1340 | + | ], | |
| 1341 | + | "steps": [ | |
| 1342 | + | { | |
| 1343 | + | "id": "__run", | |
| 1344 | + | "if": "success()" | |
| 1345 | + | } | |
| 1346 | + | ] | |
| 1347 | + | } | |
| 1348 | + | ], | |
| 1349 | + | "events": { | |
| 1350 | + | "push": {} | |
| 1351 | + | } | |
| 1352 | + | }, | |
| 1353 | + | "needs_case.yml": { | |
| 1354 | + | "errors": [ | |
| 1355 | + | "needs_case.yml (Line: 7, Col: 13): Job 'b' depends on unknown job 'A'." | |
| 1356 | + | ], | |
| 1357 | + | "jobs": [ | |
| 1358 | + | { | |
| 1359 | + | "id": "a", | |
| 1360 | + | "name": "a", | |
| 1361 | + | "if": "success()", | |
| 1362 | + | "needs": [], | |
| 1363 | + | "steps": [ | |
| 1364 | + | { | |
| 1365 | + | "id": "__run", | |
| 1366 | + | "if": "success()" | |
| 1367 | + | } | |
| 1368 | + | ] | |
| 1369 | + | }, | |
| 1370 | + | { | |
| 1371 | + | "id": "b", | |
| 1372 | + | "name": "b", | |
| 1373 | + | "if": "success()", | |
| 1374 | + | "needs": [ | |
| 1375 | + | "A" | |
| 1376 | + | ], | |
| 1377 | + | "steps": [ | |
| 1378 | + | { | |
| 1379 | + | "id": "__run", | |
| 1380 | + | "if": "success()" | |
| 1381 | + | } | |
| 1382 | + | ] | |
| 1383 | + | } | |
| 1384 | + | ], | |
| 1385 | + | "events": { | |
| 1386 | + | "push": {} | |
| 1387 | + | } | |
| 1388 | + | }, | |
| 1389 | + | "needs_dup.yml": { | |
| 1390 | + | "errors": [], | |
| 1391 | + | "jobs": [ | |
| 1392 | + | { | |
| 1393 | + | "id": "a", | |
| 1394 | + | "name": "a", | |
| 1395 | + | "if": "success()", | |
| 1396 | + | "needs": [], | |
| 1397 | + | "steps": [ | |
| 1398 | + | { | |
| 1399 | + | "id": "__run", | |
| 1400 | + | "if": "success()" | |
| 1401 | + | } | |
| 1402 | + | ] | |
| 1403 | + | }, | |
| 1404 | + | { | |
| 1405 | + | "id": "b", | |
| 1406 | + | "name": "b", | |
| 1407 | + | "if": "success()", | |
| 1408 | + | "needs": [ | |
| 1409 | + | "a", | |
| 1410 | + | "a" | |
| 1411 | + | ], | |
| 1412 | + | "steps": [ | |
| 1413 | + | { | |
| 1414 | + | "id": "__run", | |
| 1415 | + | "if": "success()" | |
| 1416 | + | } | |
| 1417 | + | ] | |
| 1418 | + | } | |
| 1419 | + | ], | |
| 1420 | + | "events": { | |
| 1421 | + | "push": {} | |
| 1422 | + | } | |
| 1423 | + | }, | |
| 1424 | + | "needs_empty.yml": { | |
| 1425 | + | "errors": [], | |
| 1426 | + | "jobs": [ | |
| 1427 | + | { | |
| 1428 | + | "id": "a", | |
| 1429 | + | "name": "a", | |
| 1430 | + | "if": "success()", | |
| 1431 | + | "needs": [], | |
| 1432 | + | "steps": [ | |
| 1433 | + | { | |
| 1434 | + | "id": "__run", | |
| 1435 | + | "if": "success()" | |
| 1436 | + | } | |
| 1437 | + | ] | |
| 1438 | + | } | |
| 1439 | + | ], | |
| 1440 | + | "events": { | |
| 1441 | + | "push": {} | |
| 1442 | + | } | |
| 1443 | + | }, | |
| 1444 | + | "needs_string.yml": { | |
| 1445 | + | "errors": [], | |
| 1446 | + | "jobs": [ | |
| 1447 | + | { | |
| 1448 | + | "id": "a", | |
| 1449 | + | "name": "a", | |
| 1450 | + | "if": "success()", | |
| 1451 | + | "needs": [], | |
| 1452 | + | "steps": [ | |
| 1453 | + | { | |
| 1454 | + | "id": "__run", | |
| 1455 | + | "if": "success()" | |
| 1456 | + | } | |
| 1457 | + | ] | |
| 1458 | + | }, | |
| 1459 | + | { | |
| 1460 | + | "id": "b", | |
| 1461 | + | "name": "b", | |
| 1462 | + | "if": "success()", | |
| 1463 | + | "needs": [ | |
| 1464 | + | "a" | |
| 1465 | + | ], | |
| 1466 | + | "steps": [ | |
| 1467 | + | { | |
| 1468 | + | "id": "__run", | |
| 1469 | + | "if": "success()" | |
| 1470 | + | } | |
| 1471 | + | ] | |
| 1472 | + | } | |
| 1473 | + | ], | |
| 1474 | + | "events": { | |
| 1475 | + | "push": {} | |
| 1476 | + | } | |
| 1477 | + | }, | |
| 1478 | + | "no_jobs.yml": { | |
| 1479 | + | "errors": [ | |
| 1480 | + | "no_jobs.yml (Line: 1, Col: 1): Required property is missing: jobs" | |
| 1481 | + | ], | |
| 1482 | + | "jobs": [] | |
| 1483 | + | }, | |
| 1484 | + | "no_on.yml": { | |
| 1485 | + | "errors": [ | |
| 1486 | + | "no_on.yml (Line: 1, Col: 1): Required property is missing: on" | |
| 1487 | + | ], | |
| 1488 | + | "jobs": [] | |
| 1489 | + | }, | |
| 1490 | + | "no_runs_on.yml": { | |
| 1491 | + | "errors": [ | |
| 1492 | + | "no_runs_on.yml (Line: 4, Col: 5): Required property is missing: runs-on" | |
| 1493 | + | ], | |
| 1494 | + | "jobs": [] | |
| 1495 | + | }, | |
| 1496 | + | "no_steps.yml": { | |
| 1497 | + | "errors": [], | |
| 1498 | + | "jobs": [ | |
| 1499 | + | { | |
| 1500 | + | "id": "a", | |
| 1501 | + | "name": "a", | |
| 1502 | + | "if": "success()", | |
| 1503 | + | "needs": [], | |
| 1504 | + | "steps": [] | |
| 1505 | + | } | |
| 1506 | + | ], | |
| 1507 | + | "events": { | |
| 1508 | + | "push": {} | |
| 1509 | + | } | |
| 1510 | + | }, | |
| 1511 | + | "null_doc.yml": { | |
| 1512 | + | "errors": [ | |
| 1513 | + | "null_doc.yml (Line: 1, Col: 1): Unexpected value ''" | |
| 1514 | + | ], | |
| 1515 | + | "jobs": [] | |
| 1516 | + | }, | |
| 1517 | + | "on_bad_type.yml": { | |
| 1518 | + | "errors": [ | |
| 1519 | + | "on_bad_type.yml (Line: 1, Col: 5): Unexpected value '3'" | |
| 1520 | + | ], | |
| 1521 | + | "jobs": [] | |
| 1522 | + | }, | |
| 1523 | + | "on_call_secrets.yml": { | |
| 1524 | + | "errors": [ | |
| 1525 | + | "on_call_secrets.yml (Line: 6, Col: 19): Unexpected value '3'" | |
| 1526 | + | ], | |
| 1527 | + | "jobs": [] | |
| 1528 | + | }, | |
| 1529 | + | "on_dispatch_input_unknown_key.yml": { | |
| 1530 | + | "errors": [ | |
| 1531 | + | "on_dispatch_input_unknown_key.yml (Line: 5, Col: 9): Unexpected value 'bogus'" | |
| 1532 | + | ], | |
| 1533 | + | "jobs": [] | |
| 1534 | + | }, | |
| 1535 | + | "on_event_non_mapping.yml": { | |
| 1536 | + | "errors": [ | |
| 1537 | + | "on_event_non_mapping.yml (Line: 2, Col: 9): Unexpected value 'x'" | |
| 1538 | + | ], | |
| 1539 | + | "jobs": [] | |
| 1540 | + | }, | |
| 1541 | + | "on_expression.yml": { | |
| 1542 | + | "errors": [ | |
| 1543 | + | "on_expression.yml (Line: 1, Col: 5): Unrecognized named-value: 'x'", | |
| 1544 | + | "on_expression.yml (Line: 1, Col: 5): Unexpected value '${{ x }}'" | |
| 1545 | + | ], | |
| 1546 | + | "jobs": [] | |
| 1547 | + | }, | |
| 1548 | + | "on_filter_empty_item.yml": { | |
| 1549 | + | "errors": [ | |
| 1550 | + | "on_filter_empty_item.yml (Line: 3, Col: 16): Unexpected value ''" | |
| 1551 | + | ], | |
| 1552 | + | "jobs": [] | |
| 1553 | + | }, | |
| 1554 | + | "on_filter_item_types.yml": { | |
| 1555 | + | "errors": [ | |
| 1556 | + | "on_filter_item_types.yml (Line: 4, Col: 13): A sequence was not expected" | |
| 1557 | + | ], | |
| 1558 | + | "jobs": [] | |
| 1559 | + | }, | |
| 1560 | + | "on_filter_types.yml": { | |
| 1561 | + | "errors": [], | |
| 1562 | + | "jobs": [ | |
| 1563 | + | { | |
| 1564 | + | "id": "a", | |
| 1565 | + | "name": "a", | |
| 1566 | + | "if": "success()", | |
| 1567 | + | "needs": [], | |
| 1568 | + | "steps": [ | |
| 1569 | + | { | |
| 1570 | + | "id": "__run", | |
| 1571 | + | "if": "success()" | |
| 1572 | + | } | |
| 1573 | + | ] | |
| 1574 | + | } | |
| 1575 | + | ], | |
| 1576 | + | "events": { | |
| 1577 | + | "push": { | |
| 1578 | + | "branches": [ | |
| 1579 | + | "1" | |
| 1580 | + | ] | |
| 1581 | + | } | |
| 1582 | + | } | |
| 1583 | + | }, | |
| 1584 | + | "on_filters_both.yml": { | |
| 1585 | + | "errors": [], | |
| 1586 | + | "jobs": [ | |
| 1587 | + | { | |
| 1588 | + | "id": "a", | |
| 1589 | + | "name": "a", | |
| 1590 | + | "if": "success()", | |
| 1591 | + | "needs": [], | |
| 1592 | + | "steps": [ | |
| 1593 | + | { | |
| 1594 | + | "id": "__run", | |
| 1595 | + | "if": "success()" | |
| 1596 | + | } | |
| 1597 | + | ] | |
| 1598 | + | } | |
| 1599 | + | ], | |
| 1600 | + | "events": { | |
| 1601 | + | "push": { | |
| 1602 | + | "branches": [ | |
| 1603 | + | "a" | |
| 1604 | + | ], | |
| 1605 | + | "branches-ignore": [ | |
| 1606 | + | "b" | |
| 1607 | + | ], | |
| 1608 | + | "tags": [ | |
| 1609 | + | "v*" | |
| 1610 | + | ], | |
| 1611 | + | "tags-ignore": [ | |
| 1612 | + | "x" | |
| 1613 | + | ], | |
| 1614 | + | "paths": [ | |
| 1615 | + | "**.go" | |
| 1616 | + | ], | |
| 1617 | + | "paths-ignore": [] | |
| 1618 | + | } | |
| 1619 | + | } | |
| 1620 | + | }, | |
| 1621 | + | "on_forms.yml": { | |
| 1622 | + | "errors": [], | |
| 1623 | + | "jobs": [ | |
| 1624 | + | { | |
| 1625 | + | "id": "a", | |
| 1626 | + | "name": "a", | |
| 1627 | + | "if": "success()", | |
| 1628 | + | "needs": [], | |
| 1629 | + | "steps": [ | |
| 1630 | + | { | |
| 1631 | + | "id": "__run", | |
| 1632 | + | "if": "success()" | |
| 1633 | + | } | |
| 1634 | + | ] | |
| 1635 | + | } | |
| 1636 | + | ], | |
| 1637 | + | "events": { | |
| 1638 | + | "push": {}, | |
| 1639 | + | "pull_request": {}, | |
| 1640 | + | "issues": { | |
| 1641 | + | "types": [ | |
| 1642 | + | "opened" | |
| 1643 | + | ] | |
| 1644 | + | } | |
| 1645 | + | } | |
| 1646 | + | }, | |
| 1647 | + | "on_schedule_non_seq.yml": { | |
| 1648 | + | "errors": [ | |
| 1649 | + | "on_schedule_non_seq.yml (Line: 2, Col: 13): Unexpected value 'x'" | |
| 1650 | + | ], | |
| 1651 | + | "jobs": [] | |
| 1652 | + | }, | |
| 1653 | + | "on_unknown_event.yml": { | |
| 1654 | + | "errors": [ | |
| 1655 | + | "on_unknown_event.yml (Line: 1, Col: 12): Unexpected value 'bogus'" | |
| 1656 | + | ], | |
| 1657 | + | "jobs": [] | |
| 1658 | + | }, | |
| 1659 | + | "on_unknown_key.yml": { | |
| 1660 | + | "errors": [ | |
| 1661 | + | "on_unknown_key.yml (Line: 3, Col: 5): Unexpected value 'foo'" | |
| 1662 | + | ], | |
| 1663 | + | "jobs": [] | |
| 1664 | + | }, | |
| 1665 | + | "outputs_nonmap.yml": { | |
| 1666 | + | "errors": [ | |
| 1667 | + | "outputs_nonmap.yml (Line: 5, Col: 14): Unexpected value 'x'" | |
| 1668 | + | ], | |
| 1669 | + | "jobs": [] | |
| 1670 | + | }, | |
| 1671 | + | "permissions_bad.yml": { | |
| 1672 | + | "errors": [ | |
| 1673 | + | "permissions_bad.yml (Line: 3, Col: 3): Unexpected value 'bogus'", | |
| 1674 | + | "permissions_bad.yml (Line: 4, Col: 13): Unexpected value 'maybe'" | |
| 1675 | + | ], | |
| 1676 | + | "jobs": [] | |
| 1677 | + | }, | |
| 1678 | + | "permissions_forms.yml": { | |
| 1679 | + | "errors": [ | |
| 1680 | + | "permissions_forms.yml (Line: 9, Col: 17): Unexpected value 'read'" | |
| 1681 | + | ], | |
| 1682 | + | "jobs": [] | |
| 1683 | + | }, | |
| 1684 | + | "quoted_multiline.yml": { | |
| 1685 | + | "errors": [ | |
| 1686 | + | "quoted_multiline.yml (Line: 6, Col: 14): Unrecognized named-value: 'bar'" | |
| 1687 | + | ], | |
| 1688 | + | "jobs": [] | |
| 1689 | + | }, | |
| 1690 | + | "reusable_needs.yml": { | |
| 1691 | + | "errors": [], | |
| 1692 | + | "jobs": [ | |
| 1693 | + | { | |
| 1694 | + | "id": "a", | |
| 1695 | + | "name": "a", | |
| 1696 | + | "if": "success()", | |
| 1697 | + | "needs": [], | |
| 1698 | + | "steps": [ | |
| 1699 | + | { | |
| 1700 | + | "id": "__run", | |
| 1701 | + | "if": "success()" | |
| 1702 | + | } | |
| 1703 | + | ] | |
| 1704 | + | }, | |
| 1705 | + | { | |
| 1706 | + | "id": "b", | |
| 1707 | + | "name": "b", | |
| 1708 | + | "if": "success()", | |
| 1709 | + | "needs": [ | |
| 1710 | + | "a" | |
| 1711 | + | ], | |
| 1712 | + | "steps": [] | |
| 1713 | + | } | |
| 1714 | + | ], | |
| 1715 | + | "events": { | |
| 1716 | + | "push": {} | |
| 1717 | + | } | |
| 1718 | + | }, | |
| 1719 | + | "reusable_ok.yml": { | |
| 1720 | + | "errors": [], | |
| 1721 | + | "jobs": [ | |
| 1722 | + | { | |
| 1723 | + | "id": "a", | |
| 1724 | + | "name": "a", | |
| 1725 | + | "if": "success()", | |
| 1726 | + | "needs": [], | |
| 1727 | + | "steps": [] | |
| 1728 | + | } | |
| 1729 | + | ], | |
| 1730 | + | "events": { | |
| 1731 | + | "push": {} | |
| 1732 | + | } | |
| 1733 | + | }, | |
| 1734 | + | "reusable_with_runs_on.yml": { | |
| 1735 | + | "errors": [ | |
| 1736 | + | "reusable_with_runs_on.yml (Line: 5, Col: 5): Unexpected value 'runs-on'" | |
| 1737 | + | ], | |
| 1738 | + | "jobs": [] | |
| 1739 | + | }, | |
| 1740 | + | "reusable_with_steps.yml": { | |
| 1741 | + | "errors": [ | |
| 1742 | + | "reusable_with_steps.yml (Line: 5, Col: 5): Unexpected value 'steps'" | |
| 1743 | + | ], | |
| 1744 | + | "jobs": [] | |
| 1745 | + | }, | |
| 1746 | + | "runs_on_bad.yml": { | |
| 1747 | + | "errors": [ | |
| 1748 | + | "runs_on_bad.yml (Line: 4, Col: 15): A sequence was not expected", | |
| 1749 | + | "runs_on_bad.yml (Line: 10, Col: 14): Unexpected value ''" | |
| 1750 | + | ], | |
| 1751 | + | "jobs": [] | |
| 1752 | + | }, | |
| 1753 | + | "runs_on_converter_labels.yml": { | |
| 1754 | + | "errors": [ | |
| 1755 | + | "runs_on_converter_labels.yml (Line: 4, Col: 24): A sequence was not expected" | |
| 1756 | + | ], | |
| 1757 | + | "jobs": [] | |
| 1758 | + | }, | |
| 1759 | + | "runs_on_forms.yml": { | |
| 1760 | + | "errors": [], | |
| 1761 | + | "jobs": [ | |
| 1762 | + | { | |
| 1763 | + | "id": "a", | |
| 1764 | + | "name": "a", | |
| 1765 | + | "if": "success()", | |
| 1766 | + | "needs": [], | |
| 1767 | + | "steps": [ | |
| 1768 | + | { | |
| 1769 | + | "id": "__run", | |
| 1770 | + | "if": "success()" | |
| 1771 | + | } | |
| 1772 | + | ] | |
| 1773 | + | }, | |
| 1774 | + | { | |
| 1775 | + | "id": "b", | |
| 1776 | + | "name": "b", | |
| 1777 | + | "if": "success()", | |
| 1778 | + | "needs": [], | |
| 1779 | + | "steps": [ | |
| 1780 | + | { | |
| 1781 | + | "id": "__run", | |
| 1782 | + | "if": "success()" | |
| 1783 | + | } | |
| 1784 | + | ] | |
| 1785 | + | }, | |
| 1786 | + | { | |
| 1787 | + | "id": "c", | |
| 1788 | + | "name": "c", | |
| 1789 | + | "if": "success()", | |
| 1790 | + | "needs": [], | |
| 1791 | + | "steps": [ | |
| 1792 | + | { | |
| 1793 | + | "id": "__run", | |
| 1794 | + | "if": "success()" | |
| 1795 | + | } | |
| 1796 | + | ] | |
| 1797 | + | } | |
| 1798 | + | ], | |
| 1799 | + | "events": { | |
| 1800 | + | "push": {} | |
| 1801 | + | } | |
| 1802 | + | }, | |
| 1803 | + | "runs_on_group_bad.yml": { | |
| 1804 | + | "errors": [ | |
| 1805 | + | "runs_on_group_bad.yml (Line: 4, Col: 22): Invalid runs-on group name 'foo/bar/baz. Please use 'organization/' or 'enterprise/' prefix to target a single runner group.'", | |
| 1806 | + | "runs_on_group_bad.yml (Line: 7, Col: 22): Invalid runs-on group name 'org/'.", | |
| 1807 | + | "runs_on_group_bad.yml (Line: 10, Col: 22): Invalid runs-on group name 'bogus/x. Please use 'organization/' or 'enterprise/' prefix to target a single runner group.'" | |
| 1808 | + | ], | |
| 1809 | + | "jobs": [ | |
| 1810 | + | { | |
| 1811 | + | "id": "a", | |
| 1812 | + | "name": "a", | |
| 1813 | + | "if": "success()", | |
| 1814 | + | "needs": [], | |
| 1815 | + | "steps": [ | |
| 1816 | + | { | |
| 1817 | + | "id": "__run", | |
| 1818 | + | "if": "success()" | |
| 1819 | + | } | |
| 1820 | + | ] | |
| 1821 | + | }, | |
| 1822 | + | { | |
| 1823 | + | "id": "b", | |
| 1824 | + | "name": "b", | |
| 1825 | + | "if": "success()", | |
| 1826 | + | "needs": [], | |
| 1827 | + | "steps": [ | |
| 1828 | + | { | |
| 1829 | + | "id": "__run", | |
| 1830 | + | "if": "success()" | |
| 1831 | + | } | |
| 1832 | + | ] | |
| 1833 | + | }, | |
| 1834 | + | { | |
| 1835 | + | "id": "c", | |
| 1836 | + | "name": "c", | |
| 1837 | + | "if": "success()", | |
| 1838 | + | "needs": [], | |
| 1839 | + | "steps": [ | |
| 1840 | + | { | |
| 1841 | + | "id": "__run", | |
| 1842 | + | "if": "success()" | |
| 1843 | + | } | |
| 1844 | + | ] | |
| 1845 | + | }, | |
| 1846 | + | { | |
| 1847 | + | "id": "d", | |
| 1848 | + | "name": "d", | |
| 1849 | + | "if": "success()", | |
| 1850 | + | "needs": [], | |
| 1851 | + | "steps": [ | |
| 1852 | + | { | |
| 1853 | + | "id": "__run", | |
| 1854 | + | "if": "success()" | |
| 1855 | + | } | |
| 1856 | + | ] | |
| 1857 | + | } | |
| 1858 | + | ], | |
| 1859 | + | "events": { | |
| 1860 | + | "push": {} | |
| 1861 | + | } | |
| 1862 | + | }, | |
| 1863 | + | "scalar_doc.yml": { | |
| 1864 | + | "errors": [ | |
| 1865 | + | "scalar_doc.yml (Line: 1, Col: 1): Unexpected value 'hello'" | |
| 1866 | + | ], | |
| 1867 | + | "jobs": [] | |
| 1868 | + | }, | |
| 1869 | + | "seq_doc.yml": { | |
| 1870 | + | "errors": [ | |
| 1871 | + | "seq_doc.yml (Line: 1, Col: 1): A sequence was not expected" | |
| 1872 | + | ], | |
| 1873 | + | "jobs": [] | |
| 1874 | + | }, | |
| 1875 | + | "services_bad.yml": { | |
| 1876 | + | "errors": [ | |
| 1877 | + | "services_bad.yml (Line: 5, Col: 30): Unexpected value 'bogus'" | |
| 1878 | + | ], | |
| 1879 | + | "jobs": [] | |
| 1880 | + | }, | |
| 1881 | + | "services_not_map.yml": { | |
| 1882 | + | "errors": [ | |
| 1883 | + | "services_not_map.yml (Line: 5, Col: 15): A sequence was not expected" | |
| 1884 | + | ], | |
| 1885 | + | "jobs": [] | |
| 1886 | + | }, | |
| 1887 | + | "services_ok.yml": { | |
| 1888 | + | "errors": [], | |
| 1889 | + | "jobs": [ | |
| 1890 | + | { | |
| 1891 | + | "id": "a", | |
| 1892 | + | "name": "a", | |
| 1893 | + | "if": "success()", | |
| 1894 | + | "needs": [], | |
| 1895 | + | "steps": [ | |
| 1896 | + | { | |
| 1897 | + | "id": "__run", | |
| 1898 | + | "if": "success()" | |
| 1899 | + | } | |
| 1900 | + | ] | |
| 1901 | + | } | |
| 1902 | + | ], | |
| 1903 | + | "events": { | |
| 1904 | + | "push": {} | |
| 1905 | + | } | |
| 1906 | + | }, | |
| 1907 | + | "snapshot.yml": { | |
| 1908 | + | "errors": [], | |
| 1909 | + | "jobs": [ | |
| 1910 | + | { | |
| 1911 | + | "id": "a", | |
| 1912 | + | "name": "a", | |
| 1913 | + | "if": "success()", | |
| 1914 | + | "needs": [], | |
| 1915 | + | "steps": [ | |
| 1916 | + | { | |
| 1917 | + | "id": "__run", | |
| 1918 | + | "if": "success()" | |
| 1919 | + | } | |
| 1920 | + | ] | |
| 1921 | + | } | |
| 1922 | + | ], | |
| 1923 | + | "events": { | |
| 1924 | + | "push": {} | |
| 1925 | + | } | |
| 1926 | + | }, | |
| 1927 | + | "step_both.yml": { | |
| 1928 | + | "errors": [ | |
| 1929 | + | "step_both.yml (Line: 7, Col: 9): Unexpected value 'uses'" | |
| 1930 | + | ], | |
| 1931 | + | "jobs": [] | |
| 1932 | + | }, | |
| 1933 | + | "step_coe_bad.yml": { | |
| 1934 | + | "errors": [ | |
| 1935 | + | "step_coe_bad.yml (Line: 5, Col: 41): Unexpected value 'yes'" | |
| 1936 | + | ], | |
| 1937 | + | "jobs": [] | |
| 1938 | + | }, | |
| 1939 | + | "step_coe_expr.yml": { | |
| 1940 | + | "errors": [], | |
| 1941 | + | "jobs": [ | |
| 1942 | + | { | |
| 1943 | + | "id": "a", | |
| 1944 | + | "name": "a", | |
| 1945 | + | "if": "success()", | |
| 1946 | + | "needs": [], | |
| 1947 | + | "steps": [ | |
| 1948 | + | { | |
| 1949 | + | "id": "__run", | |
| 1950 | + | "if": "success()" | |
| 1951 | + | } | |
| 1952 | + | ] | |
| 1953 | + | } | |
| 1954 | + | ], | |
| 1955 | + | "events": { | |
| 1956 | + | "push": {} | |
| 1957 | + | } | |
| 1958 | + | }, | |
| 1959 | + | "step_continue_on_error_num.yml": { | |
| 1960 | + | "errors": [ | |
| 1961 | + | "step_continue_on_error_num.yml (Line: 5, Col: 41): Unexpected value '1'" | |
| 1962 | + | ], | |
| 1963 | + | "jobs": [] | |
| 1964 | + | }, | |
| 1965 | + | "step_empty_uses.yml": { | |
| 1966 | + | "errors": [ | |
| 1967 | + | "step_empty_uses.yml (Line: 6, Col: 15): Unexpected value ''" | |
| 1968 | + | ], | |
| 1969 | + | "jobs": [] | |
| 1970 | + | }, | |
| 1971 | + | "step_env_nonmap.yml": { | |
| 1972 | + | "errors": [ | |
| 1973 | + | "step_env_nonmap.yml (Line: 5, Col: 27): Unexpected value 'x'" | |
| 1974 | + | ], | |
| 1975 | + | "jobs": [] | |
| 1976 | + | }, | |
| 1977 | + | "step_id_100.yml": { | |
| 1978 | + | "errors": [ | |
| 1979 | + | "step_id_100.yml (Line: 5, Col: 141): The identifier 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' is invalid. IDs may only contain alphanumeric characters, '_', and '-'. IDs must start with a letter or '_' and and must be less than 100 characters." | |
| 1980 | + | ], | |
| 1981 | + | "jobs": [ | |
| 1982 | + | { | |
| 1983 | + | "id": "a", | |
| 1984 | + | "name": "a", | |
| 1985 | + | "if": "success()", | |
| 1986 | + | "needs": [], | |
| 1987 | + | "steps": [ | |
| 1988 | + | { | |
| 1989 | + | "id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", | |
| 1990 | + | "if": "success()" | |
| 1991 | + | }, | |
| 1992 | + | { | |
| 1993 | + | "id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", | |
| 1994 | + | "if": "success()" | |
| 1995 | + | } | |
| 1996 | + | ] | |
| 1997 | + | } | |
| 1998 | + | ], | |
| 1999 | + | "events": { | |
| 2000 | + | "push": {} | |
| 2001 | + | } | |
| 2002 | + | }, | |
| 2003 | + | "step_ids_only_bad.yml": { | |
| 2004 | + | "errors": [ | |
| 2005 | + | "step_ids_only_bad.yml (Line: 5, Col: 26): The identifier '1x' is invalid. IDs may only contain alphanumeric characters, '_', and '-'. IDs must start with a letter or '_' and and must be less than 100 characters.", | |
| 2006 | + | "step_ids_only_bad.yml (Line: 5, Col: 60): The identifier 's' may not be used more than once within the same scope.", | |
| 2007 | + | "step_ids_only_bad.yml (Line: 5, Col: 77): The identifier '__x' is invalid. IDs starting with '__' are reserved.", | |
| 2008 | + | "step_ids_only_bad.yml (Line: 5, Col: 113): The identifier 'a b' is invalid. IDs may only contain alphanumeric characters, '_', and '-'. IDs must start with a letter or '_' and and must be less than 100 characters.", | |
| 2009 | + | "step_ids_only_bad.yml (Line: 5, Col: 134): The identifier 'x.y' is invalid. IDs may only contain alphanumeric characters, '_', and '-'. IDs must start with a letter or '_' and and must be less than 100 characters." | |
| 2010 | + | ], | |
| 2011 | + | "jobs": [ | |
| 2012 | + | { | |
| 2013 | + | "id": "a", | |
| 2014 | + | "name": "a", | |
| 2015 | + | "if": "success()", | |
| 2016 | + | "needs": [], | |
| 2017 | + | "steps": [ | |
| 2018 | + | { | |
| 2019 | + | "id": "1x", | |
| 2020 | + | "if": "success()" | |
| 2021 | + | }, | |
| 2022 | + | { | |
| 2023 | + | "id": "s", | |
| 2024 | + | "if": "success()" | |
| 2025 | + | }, | |
| 2026 | + | { | |
| 2027 | + | "id": "s", | |
| 2028 | + | "if": "success()" | |
| 2029 | + | }, | |
| 2030 | + | { | |
| 2031 | + | "id": "__x", | |
| 2032 | + | "if": "success()" | |
| 2033 | + | }, | |
| 2034 | + | { | |
| 2035 | + | "id": "S", | |
| 2036 | + | "if": "success()" | |
| 2037 | + | }, | |
| 2038 | + | { | |
| 2039 | + | "id": "a b", | |
| 2040 | + | "if": "success()" | |
| 2041 | + | }, | |
| 2042 | + | { | |
| 2043 | + | "id": "x.y", | |
| 2044 | + | "if": "success()" | |
| 2045 | + | } | |
| 2046 | + | ] | |
| 2047 | + | } | |
| 2048 | + | ], | |
| 2049 | + | "events": { | |
| 2050 | + | "push": {} | |
| 2051 | + | } | |
| 2052 | + | }, | |
| 2053 | + | "step_neither.yml": { | |
| 2054 | + | "errors": [ | |
| 2055 | + | "step_neither.yml (Line: 6, Col: 9): There's not enough info to determine what you meant. Add one of these properties: run, shell, uses, with, working-directory" | |
| 2056 | + | ], | |
| 2057 | + | "jobs": [] | |
| 2058 | + | }, | |
| 2059 | + | "step_run_names_collide.yml": { | |
| 2060 | + | "errors": [ | |
| 2061 | + | "step_run_names_collide.yml (Line: 5, Col: 26): The identifier '__run' is invalid. IDs starting with '__' are reserved." | |
| 2062 | + | ], | |
| 2063 | + | "jobs": [ | |
| 2064 | + | { | |
| 2065 | + | "id": "a", | |
| 2066 | + | "name": "a", | |
| 2067 | + | "if": "success()", | |
| 2068 | + | "needs": [], | |
| 2069 | + | "steps": [ | |
| 2070 | + | { | |
| 2071 | + | "id": "__run", | |
| 2072 | + | "if": "success()" | |
| 2073 | + | }, | |
| 2074 | + | { | |
| 2075 | + | "id": "_run", | |
| 2076 | + | "if": "success()" | |
| 2077 | + | }, | |
| 2078 | + | { | |
| 2079 | + | "id": "__run", | |
| 2080 | + | "if": "success()" | |
| 2081 | + | }, | |
| 2082 | + | { | |
| 2083 | + | "id": "__run_2", | |
| 2084 | + | "if": "success()" | |
| 2085 | + | }, | |
| 2086 | + | { | |
| 2087 | + | "id": "___x__y", | |
| 2088 | + | "if": "success()" | |
| 2089 | + | } | |
| 2090 | + | ] | |
| 2091 | + | } | |
| 2092 | + | ], | |
| 2093 | + | "events": { | |
| 2094 | + | "push": {} | |
| 2095 | + | } | |
| 2096 | + | }, | |
| 2097 | + | "step_shell_empty.yml": { | |
| 2098 | + | "errors": [ | |
| 2099 | + | "step_shell_empty.yml (Line: 5, Col: 29): Unexpected value ''" | |
| 2100 | + | ], | |
| 2101 | + | "jobs": [] | |
| 2102 | + | }, | |
| 2103 | + | "step_wd_empty.yml": { | |
| 2104 | + | "errors": [], | |
| 2105 | + | "jobs": [ | |
| 2106 | + | { | |
| 2107 | + | "id": "a", | |
| 2108 | + | "name": "a", | |
| 2109 | + | "if": "success()", | |
| 2110 | + | "needs": [], | |
| 2111 | + | "steps": [ | |
| 2112 | + | { | |
| 2113 | + | "id": "__run", | |
| 2114 | + | "if": "success()" | |
| 2115 | + | } | |
| 2116 | + | ] | |
| 2117 | + | } | |
| 2118 | + | ], | |
| 2119 | + | "events": { | |
| 2120 | + | "push": {} | |
| 2121 | + | } | |
| 2122 | + | }, | |
| 2123 | + | "step_with_nonstring.yml": { | |
| 2124 | + | "errors": [ | |
| 2125 | + | "step_with_nonstring.yml (Line: 5, Col: 53): A sequence was not expected" | |
| 2126 | + | ], | |
| 2127 | + | "jobs": [] | |
| 2128 | + | }, | |
| 2129 | + | "steps_empty_list.yml": { | |
| 2130 | + | "errors": [], | |
| 2131 | + | "jobs": [ | |
| 2132 | + | { | |
| 2133 | + | "id": "a", | |
| 2134 | + | "name": "a", | |
| 2135 | + | "if": "success()", | |
| 2136 | + | "needs": [], | |
| 2137 | + | "steps": [] | |
| 2138 | + | } | |
| 2139 | + | ], | |
| 2140 | + | "events": { | |
| 2141 | + | "push": {} | |
| 2142 | + | } | |
| 2143 | + | }, | |
| 2144 | + | "steps_not_seq.yml": { | |
| 2145 | + | "errors": [ | |
| 2146 | + | "steps_not_seq.yml (Line: 5, Col: 12): Unexpected value 'a'" | |
| 2147 | + | ], | |
| 2148 | + | "jobs": [] | |
| 2149 | + | }, | |
| 2150 | + | "strategy_bad_key.yml": { | |
| 2151 | + | "errors": [ | |
| 2152 | + | "strategy_bad_key.yml (Line: 6, Col: 7): Unexpected value 'bogus'" | |
| 2153 | + | ], | |
| 2154 | + | "jobs": [] | |
| 2155 | + | }, | |
| 2156 | + | "strategy_ctx.yml": { | |
| 2157 | + | "errors": [ | |
| 2158 | + | "strategy_ctx.yml (Line: 7, Col: 12): Unrecognized named-value: 'steps'", | |
| 2159 | + | "strategy_ctx.yml (Line: 7, Col: 12): Unexpected value '${{ steps.x.outputs.y }}'" | |
| 2160 | + | ], | |
| 2161 | + | "jobs": [] | |
| 2162 | + | }, | |
| 2163 | + | "strategy_matrix_bad.yml": { | |
| 2164 | + | "errors": [ | |
| 2165 | + | "strategy_matrix_bad.yml (Line: 7, Col: 13): Unexpected value 'foo'" | |
| 2166 | + | ], | |
| 2167 | + | "jobs": [] | |
| 2168 | + | }, | |
| 2169 | + | "strategy_matrix_expr.yml": { | |
| 2170 | + | "errors": [], | |
| 2171 | + | "jobs": [ | |
| 2172 | + | { | |
| 2173 | + | "id": "a", | |
| 2174 | + | "name": "a", | |
| 2175 | + | "if": "success()", | |
| 2176 | + | "needs": [], | |
| 2177 | + | "steps": [ | |
| 2178 | + | { | |
| 2179 | + | "id": "__run", | |
| 2180 | + | "if": "success()" | |
| 2181 | + | } | |
| 2182 | + | ] | |
| 2183 | + | } | |
| 2184 | + | ], | |
| 2185 | + | "events": { | |
| 2186 | + | "push": {} | |
| 2187 | + | } | |
| 2188 | + | }, | |
| 2189 | + | "strategy_matrix_ok.yml": { | |
| 2190 | + | "errors": [], | |
| 2191 | + | "jobs": [ | |
| 2192 | + | { | |
| 2193 | + | "id": "a", | |
| 2194 | + | "name": "a", | |
| 2195 | + | "if": "success()", | |
| 2196 | + | "needs": [], | |
| 2197 | + | "steps": [ | |
| 2198 | + | { | |
| 2199 | + | "id": "__run", | |
| 2200 | + | "if": "success()" | |
| 2201 | + | } | |
| 2202 | + | ] | |
| 2203 | + | } | |
| 2204 | + | ], | |
| 2205 | + | "events": { | |
| 2206 | + | "push": {} | |
| 2207 | + | } | |
| 2208 | + | }, | |
| 2209 | + | "timeout_expr.yml": { | |
| 2210 | + | "errors": [], | |
| 2211 | + | "jobs": [ | |
| 2212 | + | { | |
| 2213 | + | "id": "a", | |
| 2214 | + | "name": "a", | |
| 2215 | + | "if": "success()", | |
| 2216 | + | "needs": [], | |
| 2217 | + | "steps": [ | |
| 2218 | + | { | |
| 2219 | + | "id": "__run", | |
| 2220 | + | "if": "success()" | |
| 2221 | + | } | |
| 2222 | + | ] | |
| 2223 | + | } | |
| 2224 | + | ], | |
| 2225 | + | "events": { | |
| 2226 | + | "push": {} | |
| 2227 | + | } | |
| 2228 | + | }, | |
| 2229 | + | "timeout_str.yml": { | |
| 2230 | + | "errors": [ | |
| 2231 | + | "timeout_str.yml (Line: 5, Col: 22): Unexpected value 'abc'" | |
| 2232 | + | ], | |
| 2233 | + | "jobs": [] | |
| 2234 | + | }, | |
| 2235 | + | "two_docs.yml": { | |
| 2236 | + | "errors": [ | |
| 2237 | + | "two_docs.yml: Source contains multiple documents; please use YAML.parseAllDocuments() at line 3, column 1:\n\njobs: {}\n---\n^\n" | |
| 2238 | + | ] | |
| 2239 | + | }, | |
| 2240 | + | "unicode.yml": { | |
| 2241 | + | "errors": [ | |
| 2242 | + | "unicode.yml (Line: 6, Col: 19): Unrecognized named-value: 'foo'" | |
| 2243 | + | ], | |
| 2244 | + | "jobs": [] | |
| 2245 | + | }, | |
| 2246 | + | "unknown_job_key.yml": { | |
| 2247 | + | "errors": [ | |
| 2248 | + | "unknown_job_key.yml (Line: 5, Col: 5): Unexpected value 'bogus'" | |
| 2249 | + | ], | |
| 2250 | + | "jobs": [] | |
| 2251 | + | }, | |
| 2252 | + | "unknown_top_key.yml": { | |
| 2253 | + | "errors": [ | |
| 2254 | + | "unknown_top_key.yml (Line: 2, Col: 1): Unexpected value 'bogus'" | |
| 2255 | + | ], | |
| 2256 | + | "jobs": [] | |
| 2257 | + | }, | |
| 2258 | + | "uses_ids.yml": { | |
| 2259 | + | "errors": [ | |
| 2260 | + | "uses_ids.yml (Line: 5, Col: 240): The identifier '__run' is invalid. IDs starting with '__' are reserved." | |
| 2261 | + | ], | |
| 2262 | + | "jobs": [ | |
| 2263 | + | { | |
| 2264 | + | "id": "a", | |
| 2265 | + | "name": "a", | |
| 2266 | + | "if": "success()", | |
| 2267 | + | "needs": [], | |
| 2268 | + | "steps": [ | |
| 2269 | + | { | |
| 2270 | + | "id": "__actions_checkout", | |
| 2271 | + | "if": "success()" | |
| 2272 | + | }, | |
| 2273 | + | { | |
| 2274 | + | "id": "__self", | |
| 2275 | + | "if": "success()" | |
| 2276 | + | }, | |
| 2277 | + | { | |
| 2278 | + | "id": "__alpine_3", | |
| 2279 | + | "if": "success()" | |
| 2280 | + | }, | |
| 2281 | + | { | |
| 2282 | + | "id": "__run", | |
| 2283 | + | "if": "success()" | |
| 2284 | + | }, | |
| 2285 | + | { | |
| 2286 | + | "id": "__run_2", | |
| 2287 | + | "if": "success()" | |
| 2288 | + | }, | |
| 2289 | + | { | |
| 2290 | + | "id": "__actions_checkout_2", | |
| 2291 | + | "if": "success()" | |
| 2292 | + | }, | |
| 2293 | + | { | |
| 2294 | + | "id": "__run_3", | |
| 2295 | + | "if": "success()" | |
| 2296 | + | }, | |
| 2297 | + | { | |
| 2298 | + | "id": "__a_b", | |
| 2299 | + | "if": "success()" | |
| 2300 | + | }, | |
| 2301 | + | { | |
| 2302 | + | "id": "__self_2", | |
| 2303 | + | "if": "success()" | |
| 2304 | + | }, | |
| 2305 | + | { | |
| 2306 | + | "id": "__run_4", | |
| 2307 | + | "if": "success()" | |
| 2308 | + | }, | |
| 2309 | + | { | |
| 2310 | + | "id": "__a_b_2", | |
| 2311 | + | "if": "success()" | |
| 2312 | + | }, | |
| 2313 | + | { | |
| 2314 | + | "id": "__self_3", | |
| 2315 | + | "if": "success()" | |
| 2316 | + | }, | |
| 2317 | + | { | |
| 2318 | + | "id": "__run", | |
| 2319 | + | "if": "success()" | |
| 2320 | + | } | |
| 2321 | + | ] | |
| 2322 | + | } | |
| 2323 | + | ], | |
| 2324 | + | "events": { | |
| 2325 | + | "push": {} | |
| 2326 | + | } | |
| 2327 | + | }, | |
| 2328 | + | "valid_full.yml": { | |
| 2329 | + | "errors": [], | |
| 2330 | + | "jobs": [ | |
| 2331 | + | { | |
| 2332 | + | "id": "build", | |
| 2333 | + | "name": "${{ format('Build {0}', matrix.os) }}", | |
| 2334 | + | "if": "success()", | |
| 2335 | + | "needs": [], | |
| 2336 | + | "steps": [ | |
| 2337 | + | { | |
| 2338 | + | "id": "__actions_checkout", | |
| 2339 | + | "if": "success()" | |
| 2340 | + | }, | |
| 2341 | + | { | |
| 2342 | + | "id": "s", | |
| 2343 | + | "if": "success() && (github.ref == 'refs/heads/main')" | |
| 2344 | + | }, | |
| 2345 | + | { | |
| 2346 | + | "id": "__run", | |
| 2347 | + | "if": "always()" | |
| 2348 | + | } | |
| 2349 | + | ] | |
| 2350 | + | }, | |
| 2351 | + | { | |
| 2352 | + | "id": "test", | |
| 2353 | + | "name": "test", | |
| 2354 | + | "if": "success() && (needs.build.result == 'success')", | |
| 2355 | + | "needs": [ | |
| 2356 | + | "build" | |
| 2357 | + | ], | |
| 2358 | + | "steps": [ | |
| 2359 | + | { | |
| 2360 | + | "id": "__run", | |
| 2361 | + | "if": "success()" | |
| 2362 | + | } | |
| 2363 | + | ] | |
| 2364 | + | }, | |
| 2365 | + | { | |
| 2366 | + | "id": "deploy", | |
| 2367 | + | "name": "deploy", | |
| 2368 | + | "if": "success()", | |
| 2369 | + | "needs": [ | |
| 2370 | + | "build", | |
| 2371 | + | "test" | |
| 2372 | + | ], | |
| 2373 | + | "steps": [] | |
| 2374 | + | } | |
| 2375 | + | ], | |
| 2376 | + | "events": { | |
| 2377 | + | "push": { | |
| 2378 | + | "branches": [ | |
| 2379 | + | "main", | |
| 2380 | + | "release/**" | |
| 2381 | + | ], | |
| 2382 | + | "tags": [ | |
| 2383 | + | "v*" | |
| 2384 | + | ], | |
| 2385 | + | "paths": [ | |
| 2386 | + | "src/**" | |
| 2387 | + | ] | |
| 2388 | + | }, | |
| 2389 | + | "pull_request": { | |
| 2390 | + | "branches-ignore": [ | |
| 2391 | + | "wip" | |
| 2392 | + | ] | |
| 2393 | + | }, | |
| 2394 | + | "schedule": [ | |
| 2395 | + | { | |
| 2396 | + | "cron": "30 5,17 * * 1-5" | |
| 2397 | + | } | |
| 2398 | + | ], | |
| 2399 | + | "workflow_dispatch": { | |
| 2400 | + | "inputs": { | |
| 2401 | + | "level": { | |
| 2402 | + | "type": "choice", | |
| 2403 | + | "options": [ | |
| 2404 | + | "a", | |
| 2405 | + | "b" | |
| 2406 | + | ], | |
| 2407 | + | "default": "a" | |
| 2408 | + | } | |
| 2409 | + | } | |
| 2410 | + | } | |
| 2411 | + | } | |
| 2412 | + | }, | |
| 2413 | + | "valid_minimal.yml": { | |
| 2414 | + | "errors": [], | |
| 2415 | + | "jobs": [ | |
| 2416 | + | { | |
| 2417 | + | "id": "a", | |
| 2418 | + | "name": "a", | |
| 2419 | + | "if": "success()", | |
| 2420 | + | "needs": [], | |
| 2421 | + | "steps": [ | |
| 2422 | + | { | |
| 2423 | + | "id": "__run", | |
| 2424 | + | "if": "success()" | |
| 2425 | + | } | |
| 2426 | + | ] | |
| 2427 | + | } | |
| 2428 | + | ], | |
| 2429 | + | "events": { | |
| 2430 | + | "push": {} | |
| 2431 | + | } | |
| 2432 | + | }, | |
| 2433 | + | "workflow_defaults_bad.yml": { | |
| 2434 | + | "errors": [ | |
| 2435 | + | "workflow_defaults_bad.yml (Line: 4, Col: 5): Unexpected value 'bogus'" | |
| 2436 | + | ], | |
| 2437 | + | "jobs": [] | |
| 2438 | + | }, | |
| 2439 | + | "yes_no.yml": { | |
| 2440 | + | "errors": [ | |
| 2441 | + | "yes_no.yml (Line: 5, Col: 24): Unexpected value 'yes'" | |
| 2442 | + | ], | |
| 2443 | + | "jobs": [] | |
| 2444 | + | } | |
| 2445 | + | } | |
A workflow/testdata/convert/alias.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: &x | |
| 4 | + | runs-on: x | |
| 5 | + | steps: [{run: a}] | |
| 6 | + | b: *x | |
A workflow/testdata/convert/alias_scalar.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | x: &v x | |
| 3 | + | jobs: | |
| 4 | + | a: | |
| 5 | + | runs-on: *v | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/background_steps.yml +8 -0
| @@ -0,0 +1,8 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: | |
| 6 | + | - run: a | |
| 7 | + | background: true | |
| 8 | + | - wait: a | |
A workflow/testdata/convert/bad_cron.yml +20 -0
| @@ -0,0 +1,20 @@ | |||
| 1 | + | on: | |
| 2 | + | schedule: | |
| 3 | + | - cron: bad | |
| 4 | + | - cron: '61 * * * *' | |
| 5 | + | - cron: '*/0 * * * *' | |
| 6 | + | - cron: '0 0 * * 7' | |
| 7 | + | - cron: '0 0 * jan-mar mon' | |
| 8 | + | - cron: '0 0 * mar-jan mon' | |
| 9 | + | - cron: '* * * * * *' | |
| 10 | + | - cron: '1-5/2 * * * *' | |
| 11 | + | - cron: '*/5 */2 1,15 * *' | |
| 12 | + | - cron: '5 4 * * sun' | |
| 13 | + | - timezone: UTC | |
| 14 | + | - cron: '0 0 * * *' | |
| 15 | + | timezone: Europe/Paris | |
| 16 | + | extra: 1 | |
| 17 | + | jobs: | |
| 18 | + | a: | |
| 19 | + | runs-on: x | |
| 20 | + | steps: [{run: a}] | |
A workflow/testdata/convert/bad_if.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | if: github.ref == 1 && | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/bad_job_ids.yml +11 -0
| @@ -0,0 +1,11 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | 1a: | |
| 4 | + | runs-on: x | |
| 5 | + | steps: [{run: a}] | |
| 6 | + | __b: | |
| 7 | + | runs-on: x | |
| 8 | + | steps: [{run: a}] | |
| 9 | + | a b: | |
| 10 | + | runs-on: x | |
| 11 | + | steps: [{run: a}] | |
A workflow/testdata/convert/bad_needs_cycle.yml +10 -0
| @@ -0,0 +1,10 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: x | |
| 5 | + | needs: [b] | |
| 6 | + | steps: [{run: a}] | |
| 7 | + | b: | |
| 8 | + | runs-on: x | |
| 9 | + | needs: a | |
| 10 | + | steps: [{run: a}] | |
A workflow/testdata/convert/bad_needs_cycle_with_root.yml +13 -0
| @@ -0,0 +1,13 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | r: | |
| 4 | + | runs-on: x | |
| 5 | + | steps: [{run: a}] | |
| 6 | + | a: | |
| 7 | + | runs-on: x | |
| 8 | + | needs: [b] | |
| 9 | + | steps: [{run: a}] | |
| 10 | + | b: | |
| 11 | + | runs-on: x | |
| 12 | + | needs: [a, r] | |
| 13 | + | steps: [{run: a}] | |
A workflow/testdata/convert/bad_needs_self.yml +9 -0
| @@ -0,0 +1,9 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | r: | |
| 4 | + | runs-on: x | |
| 5 | + | steps: [{run: a}] | |
| 6 | + | a: | |
| 7 | + | runs-on: x | |
| 8 | + | needs: [a] | |
| 9 | + | steps: [{run: a}] | |
A workflow/testdata/convert/bad_needs_unknown.yml +9 -0
| @@ -0,0 +1,9 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | r: | |
| 4 | + | runs-on: x | |
| 5 | + | steps: [{run: a}] | |
| 6 | + | a: | |
| 7 | + | runs-on: x | |
| 8 | + | needs: [zz, r] | |
| 9 | + | steps: [{run: a}] | |
A workflow/testdata/convert/bad_step_id.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: [{run: a, id: 1x}, {run: b, id: s},{run: b, id: s}, {run: c, id: __x}, {run: d, id: ''}] | |
A workflow/testdata/convert/bad_yaml_flow.yml +1 -0
| @@ -0,0 +1,1 @@ | |||
| 1 | + | on: [push | |
A workflow/testdata/convert/bad_yaml_tab.yml +3 -0
| @@ -0,0 +1,3 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: 1 | |
A workflow/testdata/convert/big_ids.yml +8 -0
| @@ -0,0 +1,8 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: | |
| 4 | + | runs-on: x | |
| 5 | + | steps: [{run: a}] | |
| 6 | + | bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb: | |
| 7 | + | runs-on: x | |
| 8 | + | steps: [{run: a}] | |
A workflow/testdata/convert/bool_scalars.yml +7 -0
| @@ -0,0 +1,7 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: x | |
| 5 | + | continue-on-error: True | |
| 6 | + | timeout-minutes: 0x10 | |
| 7 | + | steps: [{run: true}, {run: 12}, {run: 1.5}, {run: null}, {run: ~}] | |
A workflow/testdata/convert/call_inputs.yml +22 -0
| @@ -0,0 +1,22 @@ | |||
| 1 | + | on: | |
| 2 | + | workflow_call: | |
| 3 | + | inputs: | |
| 4 | + | a: | |
| 5 | + | type: string | |
| 6 | + | required: true | |
| 7 | + | default: ${{ github.ref }} | |
| 8 | + | b: | |
| 9 | + | type: boolean | |
| 10 | + | default: 1 | |
| 11 | + | secrets: | |
| 12 | + | s: | |
| 13 | + | required: true | |
| 14 | + | t: | |
| 15 | + | outputs: | |
| 16 | + | o: | |
| 17 | + | value: ${{ jobs.a.outputs.x }} | |
| 18 | + | jobs: | |
| 19 | + | a: | |
| 20 | + | runs-on: x | |
| 21 | + | outputs: {x: 1} | |
| 22 | + | steps: [{run: a}] | |
A workflow/testdata/convert/call_inputs_expr_default.yml +13 -0
| @@ -0,0 +1,13 @@ | |||
| 1 | + | on: | |
| 2 | + | workflow_call: | |
| 3 | + | inputs: | |
| 4 | + | b: | |
| 5 | + | type: boolean | |
| 6 | + | default: ${{ github.ref }} | |
| 7 | + | c: | |
| 8 | + | type: string | |
| 9 | + | default: 5 | |
| 10 | + | jobs: | |
| 11 | + | a: | |
| 12 | + | runs-on: x | |
| 13 | + | steps: [{run: a}] | |
A workflow/testdata/convert/comment_doc.yml +1 -0
| @@ -0,0 +1,1 @@ | |||
| 1 | + | # only | |
A workflow/testdata/convert/concurrency_bad.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | concurrency: {group: x, bogus: 1} | |
| 3 | + | jobs: | |
| 4 | + | a: | |
| 5 | + | runs-on: x | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/concurrency_converter.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | concurrency: {group: x, cancel-in-progress: true, queue: single} | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/concurrency_job.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | concurrency: {group: x, cancel-in-progress: 1} | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/concurrency_queue.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | concurrency: {group: x, queue: max} | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/concurrency_queue_bad.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | concurrency: {group: x, queue: other} | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/concurrency_string.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | concurrency: g-${{ github.ref }} | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/container_bad.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | container: {image: a, bogus: 1} | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/container_converter.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | container: {image: a, env: {A: 1}, ports: [80, '81'], volumes: [x], options: 1, credentials: {username: 1}} | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/container_empty_image.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | container: {image: ''} | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/container_entrypoint.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | container: {image: a, entrypoint: b} | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/container_expr.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | container: ${{ matrix.c }} | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/container_no_image.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | container: {env: {A: b}} | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/container_string.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | container: node:20 | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/context_always_job_env.yml +7 -0
| @@ -0,0 +1,7 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | env: | |
| 6 | + | A: ${{ always() }} | |
| 7 | + | steps: [{run: a}] | |
A workflow/testdata/convert/context_defaults_run.yml +8 -0
| @@ -0,0 +1,8 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | defaults: | |
| 6 | + | run: | |
| 7 | + | working-directory: ${{ steps.x.outputs.y }} | |
| 8 | + | steps: [{run: a}] | |
A workflow/testdata/convert/context_env_secrets.yml +7 -0
| @@ -0,0 +1,7 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | env: | |
| 6 | + | A: ${{ secrets.x }} | |
| 7 | + | steps: [{run: a}] | |
A workflow/testdata/convert/context_job_name.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | name: ${{ steps.x.outputs.y }} | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/context_job_outputs.yml +8 -0
| @@ -0,0 +1,8 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | outputs: | |
| 6 | + | a: ${{ steps.x.outputs.y }} | |
| 7 | + | b: ${{ secrets.z }} | |
| 8 | + | steps: [{run: a}] | |
A workflow/testdata/convert/context_outputs_hash.yml +7 -0
| @@ -0,0 +1,7 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | outputs: | |
| 6 | + | a: ${{ hashFiles('x') }} | |
| 7 | + | steps: [{run: a}] | |
A workflow/testdata/convert/context_run_name.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | run-name: ${{ matrix.x }} | |
| 3 | + | jobs: | |
| 4 | + | a: | |
| 5 | + | runs-on: x | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/context_runs_on_secrets.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | runs-on: ${{ secrets.x }} | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/context_step_run.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: [{run: 'echo ${{ matrix.a }} ${{ secrets.b }} ${{ job.status }} ${{ runner.os }} ${{ steps.x.outputs.y }} ${{ hashFiles("a") }} ${{ vars.q }} ${{ inputs.i }} ${{ env.x }}'}] | |
A workflow/testdata/convert/context_success_in_run.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: [{run: '${{ success() }}'}] | |
A workflow/testdata/convert/context_unknown_func.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: [{run: '${{ nope(1) }}'}] | |
A workflow/testdata/convert/context_workflow_env.yml +7 -0
| @@ -0,0 +1,7 @@ | |||
| 1 | + | on: push | |
| 2 | + | env: | |
| 3 | + | A: ${{ matrix.x }} | |
| 4 | + | jobs: | |
| 5 | + | a: | |
| 6 | + | runs-on: x | |
| 7 | + | steps: [{run: a}] | |
A workflow/testdata/convert/credentials_bad.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | container: {image: a, credentials: {username: u, token: t}} | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/cron_only_bad.yml +26 -0
| @@ -0,0 +1,26 @@ | |||
| 1 | + | on: | |
| 2 | + | schedule: | |
| 3 | + | - cron: bad | |
| 4 | + | - cron: '61 * * * *' | |
| 5 | + | - cron: '*/0 * * * *' | |
| 6 | + | - cron: '0 0 * * 7' | |
| 7 | + | - cron: '0 0 * jan-mar mon' | |
| 8 | + | - cron: '0 0 * mar-jan mon' | |
| 9 | + | - cron: '* * * * * *' | |
| 10 | + | - cron: '1-5/2 * * * *' | |
| 11 | + | - cron: '*/5 */2 1,15 * *' | |
| 12 | + | - cron: '5 4 * * sun' | |
| 13 | + | - cron: '0 0 * * *' | |
| 14 | + | - cron: ' 0 0 * * *' | |
| 15 | + | - cron: '1,,2 * * * *' | |
| 16 | + | - cron: '*/5/2 * * * *' | |
| 17 | + | - cron: '1-2-3 * * * *' | |
| 18 | + | - cron: '*-5 * * * *' | |
| 19 | + | - cron: '5-1 * * * *' | |
| 20 | + | - cron: '0 0 1-31/2 * *' | |
| 21 | + | - cron: '0 0 * * MON' | |
| 22 | + | - cron: '0 0 * Feb *' | |
| 23 | + | jobs: | |
| 24 | + | a: | |
| 25 | + | runs-on: x | |
| 26 | + | steps: [{run: a}] | |
A workflow/testdata/convert/description_key.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | description: hi | |
| 3 | + | jobs: | |
| 4 | + | a: | |
| 5 | + | runs-on: x | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/dispatch_inputs.yml +23 -0
| @@ -0,0 +1,23 @@ | |||
| 1 | + | on: | |
| 2 | + | workflow_dispatch: | |
| 3 | + | inputs: | |
| 4 | + | a: | |
| 5 | + | type: choice | |
| 6 | + | b: | |
| 7 | + | type: string | |
| 8 | + | options: [x] | |
| 9 | + | c: | |
| 10 | + | type: boolean | |
| 11 | + | default: foo | |
| 12 | + | d: | |
| 13 | + | type: environment | |
| 14 | + | e: | |
| 15 | + | description: x | |
| 16 | + | required: true | |
| 17 | + | default: y | |
| 18 | + | f: | |
| 19 | + | type: number | |
| 20 | + | jobs: | |
| 21 | + | a: | |
| 22 | + | runs-on: x | |
| 23 | + | steps: [{run: a}] | |
A workflow/testdata/convert/dup_job_ids_case.yml +8 -0
| @@ -0,0 +1,8 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: x | |
| 5 | + | steps: [{run: a}] | |
| 6 | + | A: | |
| 7 | + | runs-on: x | |
| 8 | + | steps: [{run: a}] | |
A workflow/testdata/convert/dup_keys.yml +9 -0
| @@ -0,0 +1,9 @@ | |||
| 1 | + | on: push | |
| 2 | + | on: pull_request | |
| 3 | + | Name: a | |
| 4 | + | name: b | |
| 5 | + | jobs: | |
| 6 | + | a: | |
| 7 | + | runs-on: x | |
| 8 | + | runs-on: y | |
| 9 | + | steps: [{run: a}] | |
A workflow/testdata/convert/empty_doc.yml +0 -0
no textual change.
A workflow/testdata/convert/env_nonmap_converter.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | env: x | |
| 3 | + | jobs: | |
| 4 | + | a: | |
| 5 | + | runs-on: x | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/environment_converter.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | environment: {name: a, deployment: 1} | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/environment_forms.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | environment: prod | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/environment_map.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | environment: {name: prod, url: 'https://x', deployment: false} | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/environment_no_name.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | environment: {url: x} | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/expr_empty.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: | |
| 6 | + | - run: echo ${{ }} | |
A workflow/testdata/convert/expr_in_key_env.yml +7 -0
| @@ -0,0 +1,7 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | env: | |
| 6 | + | ${{ matrix.k }}: v | |
| 7 | + | steps: [{run: a}] | |
A workflow/testdata/convert/expr_insert_embedded.yml +8 -0
| @@ -0,0 +1,8 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | strategy: | |
| 6 | + | matrix: | |
| 7 | + | a: x ${{ insert }} | |
| 8 | + | steps: [{run: a}] | |
A workflow/testdata/convert/expr_insert_key.yml +7 -0
| @@ -0,0 +1,7 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: | |
| 6 | + | - name: ${{ insert }} | |
| 7 | + | run: x | |
A workflow/testdata/convert/expr_insert_matrix.yml +8 -0
| @@ -0,0 +1,8 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | strategy: | |
| 6 | + | matrix: | |
| 7 | + | ${{ insert }}: ${{ fromJSON(needs.x.outputs.m) }} | |
| 8 | + | steps: [{run: a}] | |
A workflow/testdata/convert/expr_insert_params.yml +8 -0
| @@ -0,0 +1,8 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | strategy: | |
| 6 | + | matrix: | |
| 7 | + | ${{ insert x }}: 1 | |
| 8 | + | steps: [{run: a}] | |
A workflow/testdata/convert/expr_key_not_allowed.yml +7 -0
| @@ -0,0 +1,7 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | outputs: | |
| 6 | + | ${{ github.ref }}: 1 | |
| 7 | + | steps: [{run: a}] | |
A workflow/testdata/convert/expr_literal.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: [{run: "${{ '${{ x }}' }}"}] | |
A workflow/testdata/convert/expr_multi.yml +9 -0
| @@ -0,0 +1,9 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: | |
| 6 | + | - run: | | |
| 7 | + | echo ${{ github.ref }} | |
| 8 | + | echo ${{ foo.bar }} | |
| 9 | + | echo ${{ 1 + }} | |
A workflow/testdata/convert/expr_not_closed.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: | |
| 6 | + | - run: echo ${{ 1 | |
A workflow/testdata/convert/expr_string_braces.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: [{run: "echo '${{ format('{0}}}', 1) }}'"}] | |
A workflow/testdata/convert/if_bad_arity.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: [{run: c, if: failure(1)}] | |
A workflow/testdata/convert/if_job_ctx.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | if: steps.a.outputs.x | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/if_step_job_funcs.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | if: success(1) | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/if_wrapping.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | if: github.ref == 'x' | |
| 6 | + | steps: [{run: a, if: "hashFiles('a')"}, {run: b, if: "${{ always() }}"}, {run: c, if: failure()}, {run: d, if: ''}, {run: e, if: '!cancelled()'}, {run: f, if: "contains(github.ref, 'a') || success()"}, {run: g, if: "toJSON(success())"}, {run: h, if: true}, {run: i, if: "${{ github.ref }} == 'x'"}] | |
A workflow/testdata/convert/if_wrapping_nonstring.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | if: 5 | |
| 6 | + | steps: [{run: a, if: null}, {run: b, if: false}] | |
A workflow/testdata/convert/inputs_converter_errors.yml +25 -0
| @@ -0,0 +1,25 @@ | |||
| 1 | + | on: | |
| 2 | + | workflow_dispatch: | |
| 3 | + | inputs: | |
| 4 | + | a: | |
| 5 | + | type: choice | |
| 6 | + | options: [] | |
| 7 | + | b: | |
| 8 | + | type: string | |
| 9 | + | options: [x] | |
| 10 | + | c: | |
| 11 | + | type: boolean | |
| 12 | + | default: foo | |
| 13 | + | d: | |
| 14 | + | type: string | |
| 15 | + | default: 5 | |
| 16 | + | e: | |
| 17 | + | type: number | |
| 18 | + | default: 'x' | |
| 19 | + | f: | |
| 20 | + | type: boolean | |
| 21 | + | default: ${{ github.ref }} | |
| 22 | + | jobs: | |
| 23 | + | a: | |
| 24 | + | runs-on: x | |
| 25 | + | steps: [{run: a}] | |
A workflow/testdata/convert/job_env_nonmap.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | env: x | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/job_name_empty.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | name: '' | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/job_name_expr.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | name: x-${{ matrix.a }} | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/job_not_map.yml +3 -0
| @@ -0,0 +1,3 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: x | |
A workflow/testdata/convert/jobs_dropped_job.yml +14 -0
| @@ -0,0 +1,14 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: x | |
| 5 | + | steps: [{run: a}] | |
| 6 | + | b: | |
| 7 | + | runs-on: x | |
| 8 | + | env: x | |
| 9 | + | outputs: y | |
| 10 | + | steps: [{run: a}] | |
| 11 | + | c: | |
| 12 | + | needs: b | |
| 13 | + | runs-on: x | |
| 14 | + | steps: [{run: a}] | |
A workflow/testdata/convert/jobs_empty.yml +2 -0
| @@ -0,0 +1,2 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: {} | |
A workflow/testdata/convert/jobs_not_map.yml +2 -0
| @@ -0,0 +1,2 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: [a] | |
A workflow/testdata/convert/multiline_folded.yml +8 -0
| @@ -0,0 +1,8 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: | |
| 6 | + | - run: >- | |
| 7 | + | a ${{ bar }} | |
| 8 | + | b | |
A workflow/testdata/convert/multiline_plain.yml +7 -0
| @@ -0,0 +1,7 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: | |
| 6 | + | - run: a | |
| 7 | + | ${{ bar }} | |
A workflow/testdata/convert/needs_bad_type.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: x | |
| 5 | + | needs: 3 | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/needs_case.yml +9 -0
| @@ -0,0 +1,9 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: x | |
| 5 | + | steps: [{run: a}] | |
| 6 | + | b: | |
| 7 | + | needs: [A] | |
| 8 | + | runs-on: x | |
| 9 | + | steps: [{run: a}] | |
A workflow/testdata/convert/needs_dup.yml +9 -0
| @@ -0,0 +1,9 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: x | |
| 5 | + | steps: [{run: a}] | |
| 6 | + | b: | |
| 7 | + | needs: [a, a] | |
| 8 | + | runs-on: x | |
| 9 | + | steps: [{run: a}] | |
A workflow/testdata/convert/needs_empty.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: x | |
| 5 | + | needs: [] | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/needs_string.yml +9 -0
| @@ -0,0 +1,9 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: x | |
| 5 | + | steps: [{run: a}] | |
| 6 | + | b: | |
| 7 | + | needs: a | |
| 8 | + | runs-on: x | |
| 9 | + | steps: [{run: a}] | |
A workflow/testdata/convert/no_jobs.yml +1 -0
| @@ -0,0 +1,1 @@ | |||
| 1 | + | on: push | |
A workflow/testdata/convert/no_on.yml +4 -0
| @@ -0,0 +1,4 @@ | |||
| 1 | + | jobs: | |
| 2 | + | a: | |
| 3 | + | runs-on: x | |
| 4 | + | steps: [{run: a}] | |
A workflow/testdata/convert/no_runs_on.yml +4 -0
| @@ -0,0 +1,4 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | steps: [{run: a}] | |
A workflow/testdata/convert/no_steps.yml +4 -0
| @@ -0,0 +1,4 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
A workflow/testdata/convert/null_doc.yml +1 -0
| @@ -0,0 +1,1 @@ | |||
| 1 | + | ~ | |
A workflow/testdata/convert/on_bad_type.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: 3 | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: x | |
| 5 | + | steps: [{run: a}] | |
A workflow/testdata/convert/on_call_secrets.yml +10 -0
| @@ -0,0 +1,10 @@ | |||
| 1 | + | on: | |
| 2 | + | workflow_call: | |
| 3 | + | secrets: | |
| 4 | + | a: | |
| 5 | + | description: x | |
| 6 | + | required: 3 | |
| 7 | + | jobs: | |
| 8 | + | a: | |
| 9 | + | runs-on: x | |
| 10 | + | steps: [{run: a}] | |
A workflow/testdata/convert/on_dispatch_input_unknown_key.yml +9 -0
| @@ -0,0 +1,9 @@ | |||
| 1 | + | on: | |
| 2 | + | workflow_dispatch: | |
| 3 | + | inputs: | |
| 4 | + | a: | |
| 5 | + | bogus: 1 | |
| 6 | + | jobs: | |
| 7 | + | a: | |
| 8 | + | runs-on: x | |
| 9 | + | steps: [{run: a}] | |
A workflow/testdata/convert/on_event_non_mapping.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: | |
| 2 | + | push: x | |
| 3 | + | jobs: | |
| 4 | + | a: | |
| 5 | + | runs-on: x | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/on_expression.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: ${{ x }} | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: x | |
| 5 | + | steps: [{run: a}] | |
A workflow/testdata/convert/on_filter_empty_item.yml +7 -0
| @@ -0,0 +1,7 @@ | |||
| 1 | + | on: | |
| 2 | + | push: | |
| 3 | + | branches: [''] | |
| 4 | + | jobs: | |
| 5 | + | a: | |
| 6 | + | runs-on: x | |
| 7 | + | steps: [{run: a}] | |
A workflow/testdata/convert/on_filter_item_types.yml +8 -0
| @@ -0,0 +1,8 @@ | |||
| 1 | + | on: | |
| 2 | + | push: | |
| 3 | + | branches: [1, a] | |
| 4 | + | paths: [[x]] | |
| 5 | + | jobs: | |
| 6 | + | a: | |
| 7 | + | runs-on: x | |
| 8 | + | steps: [{run: a}] | |
A workflow/testdata/convert/on_filter_types.yml +7 -0
| @@ -0,0 +1,7 @@ | |||
| 1 | + | on: | |
| 2 | + | push: | |
| 3 | + | branches: 1 | |
| 4 | + | jobs: | |
| 5 | + | a: | |
| 6 | + | runs-on: x | |
| 7 | + | steps: [{run: a}] | |
A workflow/testdata/convert/on_filters_both.yml +12 -0
| @@ -0,0 +1,12 @@ | |||
| 1 | + | on: | |
| 2 | + | push: | |
| 3 | + | branches: [a] | |
| 4 | + | branches-ignore: [b] | |
| 5 | + | tags: v* | |
| 6 | + | tags-ignore: [x] | |
| 7 | + | paths: ['**.go'] | |
| 8 | + | paths-ignore: [] | |
| 9 | + | jobs: | |
| 10 | + | a: | |
| 11 | + | runs-on: x | |
| 12 | + | steps: [{run: a}] | |
A workflow/testdata/convert/on_forms.yml +9 -0
| @@ -0,0 +1,9 @@ | |||
| 1 | + | on: | |
| 2 | + | push: | |
| 3 | + | pull_request: {} | |
| 4 | + | issues: | |
| 5 | + | types: opened | |
| 6 | + | jobs: | |
| 7 | + | a: | |
| 8 | + | runs-on: x | |
| 9 | + | steps: [{run: a}] | |
A workflow/testdata/convert/on_schedule_non_seq.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: | |
| 2 | + | schedule: x | |
| 3 | + | jobs: | |
| 4 | + | a: | |
| 5 | + | runs-on: x | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/on_unknown_event.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: [push, bogus] | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: x | |
| 5 | + | steps: [{run: a}] | |
A workflow/testdata/convert/on_unknown_key.yml +7 -0
| @@ -0,0 +1,7 @@ | |||
| 1 | + | on: | |
| 2 | + | push: | |
| 3 | + | foo: 1 | |
| 4 | + | jobs: | |
| 5 | + | a: | |
| 6 | + | runs-on: x | |
| 7 | + | steps: [{run: a}] | |
A workflow/testdata/convert/outputs_nonmap.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | outputs: x | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/permissions_bad.yml +8 -0
| @@ -0,0 +1,8 @@ | |||
| 1 | + | on: push | |
| 2 | + | permissions: | |
| 3 | + | bogus: read | |
| 4 | + | contents: maybe | |
| 5 | + | jobs: | |
| 6 | + | a: | |
| 7 | + | runs-on: x | |
| 8 | + | steps: [{run: a}] | |
A workflow/testdata/convert/permissions_forms.yml +10 -0
| @@ -0,0 +1,10 @@ | |||
| 1 | + | on: push | |
| 2 | + | permissions: read-all | |
| 3 | + | jobs: | |
| 4 | + | a: | |
| 5 | + | runs-on: x | |
| 6 | + | permissions: | |
| 7 | + | contents: write | |
| 8 | + | packages: none | |
| 9 | + | id-token: read | |
| 10 | + | steps: [{run: a}] | |
A workflow/testdata/convert/quoted_multiline.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: | |
| 6 | + | - run: "a\n${{ bar }}" | |
A workflow/testdata/convert/reusable_needs.yml +8 -0
| @@ -0,0 +1,8 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: x | |
| 5 | + | steps: [{run: a}] | |
| 6 | + | b: | |
| 7 | + | needs: a | |
| 8 | + | uses: ./x.yml | |
A workflow/testdata/convert/reusable_ok.yml +8 -0
| @@ -0,0 +1,8 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | uses: org/repo/.github/workflows/x.yml@v1 | |
| 5 | + | with: | |
| 6 | + | a: 1 | |
| 7 | + | secrets: | |
| 8 | + | s: ${{ secrets.t }} | |
A workflow/testdata/convert/reusable_with_runs_on.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | uses: ./x.yml | |
| 5 | + | runs-on: x | |
A workflow/testdata/convert/reusable_with_steps.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | uses: ./x.yml | |
| 5 | + | steps: [] | |
A workflow/testdata/convert/runs_on_bad.yml +11 -0
| @@ -0,0 +1,11 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: [[a]] | |
| 5 | + | steps: [{run: a}] | |
| 6 | + | b: | |
| 7 | + | runs-on: 3 | |
| 8 | + | steps: [{run: a}] | |
| 9 | + | c: | |
| 10 | + | runs-on: '' | |
| 11 | + | steps: [{run: a}] | |
A workflow/testdata/convert/runs_on_converter_labels.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: {labels: [[a]]} | |
| 5 | + | steps: [{run: a}] | |
A workflow/testdata/convert/runs_on_forms.yml +11 -0
| @@ -0,0 +1,11 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: {group: g, labels: [a, b]} | |
| 5 | + | steps: [{run: a}] | |
| 6 | + | b: | |
| 7 | + | runs-on: {labels: c} | |
| 8 | + | steps: [{run: a}] | |
| 9 | + | c: | |
| 10 | + | runs-on: [a, '${{ matrix.x }}'] | |
| 11 | + | steps: [{run: a}] | |
A workflow/testdata/convert/runs_on_group_bad.yml +14 -0
| @@ -0,0 +1,14 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: {group: foo/bar/baz} | |
| 5 | + | steps: [{run: a}] | |
| 6 | + | b: | |
| 7 | + | runs-on: {group: org/} | |
| 8 | + | steps: [{run: a}] | |
| 9 | + | c: | |
| 10 | + | runs-on: {group: bogus/x} | |
| 11 | + | steps: [{run: a}] | |
| 12 | + | d: | |
| 13 | + | runs-on: {group: org/x} | |
| 14 | + | steps: [{run: a}] | |
A workflow/testdata/convert/scalar_doc.yml +1 -0
| @@ -0,0 +1,1 @@ | |||
| 1 | + | hello | |
A workflow/testdata/convert/seq_doc.yml +1 -0
| @@ -0,0 +1,1 @@ | |||
| 1 | + | [1,2] | |
A workflow/testdata/convert/services_bad.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | services: {s: {image: a, bogus: 1}} | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/services_not_map.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | services: [a] | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/services_ok.yml +12 -0
| @@ -0,0 +1,12 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | services: | |
| 6 | + | s: | |
| 7 | + | image: a | |
| 8 | + | entrypoint: b | |
| 9 | + | command: c | |
| 10 | + | options: --x | |
| 11 | + | volumes: [a:b] | |
| 12 | + | steps: [{run: a}] | |
A workflow/testdata/convert/snapshot.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | snapshot: img | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/step_both.yml +7 -0
| @@ -0,0 +1,7 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: | |
| 6 | + | - run: a | |
| 7 | + | uses: b | |
A workflow/testdata/convert/step_coe_bad.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: [{run: a, continue-on-error: yes}] | |
A workflow/testdata/convert/step_coe_expr.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: [{run: a, continue-on-error: '${{ true }}'}] | |
A workflow/testdata/convert/step_continue_on_error_num.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: [{run: a, continue-on-error: 1}] | |
A workflow/testdata/convert/step_empty_uses.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: | |
| 6 | + | - uses: '' | |
A workflow/testdata/convert/step_env_nonmap.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: [{run: a, env: x}] | |
A workflow/testdata/convert/step_id_100.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: [{run: a, id: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}, {run: a, id: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa}] | |
A workflow/testdata/convert/step_ids_only_bad.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: [{run: a, id: 1x}, {run: b, id: s},{run: b, id: s}, {run: c, id: __x}, {run: d, id: S}, {run: e, id: 'a b'}, {run: f, id: x.y}] | |
A workflow/testdata/convert/step_neither.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: | |
| 6 | + | - name: a | |
A workflow/testdata/convert/step_run_names_collide.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: [{run: a, id: __run}, {run: b, id: _run}, {run: c}, {run: d}, {uses: _x/_y@v1}] | |
A workflow/testdata/convert/step_shell_empty.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: [{run: a, shell: ''}] | |
A workflow/testdata/convert/step_wd_empty.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: [{run: a, working-directory: ''}] | |
A workflow/testdata/convert/step_with_nonstring.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: [{uses: a/b@v1, with: {a: 1, b: true, c: [1]}}] | |
A workflow/testdata/convert/steps_empty_list.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: [] | |
A workflow/testdata/convert/steps_not_seq.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: a | |
A workflow/testdata/convert/strategy_bad_key.yml +7 -0
| @@ -0,0 +1,7 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | strategy: | |
| 6 | + | bogus: 1 | |
| 7 | + | steps: [{run: a}] | |
A workflow/testdata/convert/strategy_ctx.yml +8 -0
| @@ -0,0 +1,8 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | strategy: | |
| 6 | + | matrix: | |
| 7 | + | a: ${{ steps.x.outputs.y }} | |
| 8 | + | steps: [{run: a}] | |
A workflow/testdata/convert/strategy_matrix_bad.yml +8 -0
| @@ -0,0 +1,8 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | strategy: | |
| 6 | + | matrix: | |
| 7 | + | os: foo | |
| 8 | + | steps: [{run: a}] | |
A workflow/testdata/convert/strategy_matrix_expr.yml +7 -0
| @@ -0,0 +1,7 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | strategy: | |
| 6 | + | matrix: ${{ fromJSON(needs.a.outputs.m) }} | |
| 7 | + | steps: [{run: a}] | |
A workflow/testdata/convert/strategy_matrix_ok.yml +11 -0
| @@ -0,0 +1,11 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | strategy: | |
| 6 | + | matrix: | |
| 7 | + | a: [1, 2] | |
| 8 | + | include: | |
| 9 | + | - a: 3 | |
| 10 | + | exclude: [] | |
| 11 | + | steps: [{run: a}] | |
A workflow/testdata/convert/timeout_expr.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | timeout-minutes: ${{ matrix.t }} | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/timeout_str.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | timeout-minutes: abc | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/two_docs.yml +4 -0
| @@ -0,0 +1,4 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: {} | |
| 3 | + | --- | |
| 4 | + | x: 1 | |
A workflow/testdata/convert/unicode.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | name: héllo ${{ github.ref }} 日本 | |
| 5 | + | runs-on: x | |
| 6 | + | steps: [{run: 'é${{ foo }}'}] | |
A workflow/testdata/convert/unknown_job_key.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | bogus: 1 | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/unknown_top_key.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | bogus: 1 | |
| 3 | + | jobs: | |
| 4 | + | a: | |
| 5 | + | runs-on: x | |
| 6 | + | steps: [{run: a}] | |
A workflow/testdata/convert/uses_ids.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: [{uses: actions/checkout@v4}, {uses: ./x}, {uses: docker://alpine:3}, {run: a}, {run: b}, {uses: actions/checkout@v4}, {uses: a}, {uses: 'a/b/c@v1'}, {uses: '$/x'}, {uses: 'a@b@c'}, {uses: '/a/b@v'}, {uses: '.\x'}, {run: 1, id: __run}] | |
A workflow/testdata/convert/valid_full.yml +86 -0
| @@ -0,0 +1,86 @@ | |||
| 1 | + | name: CI | |
| 2 | + | run-name: Run ${{ github.actor }} | |
| 3 | + | on: | |
| 4 | + | push: | |
| 5 | + | branches: [main, 'release/**'] | |
| 6 | + | tags: ['v*'] | |
| 7 | + | paths: ['src/**'] | |
| 8 | + | pull_request: | |
| 9 | + | branches-ignore: [wip] | |
| 10 | + | schedule: | |
| 11 | + | - cron: '30 5,17 * * 1-5' | |
| 12 | + | workflow_dispatch: | |
| 13 | + | inputs: | |
| 14 | + | level: | |
| 15 | + | type: choice | |
| 16 | + | options: [a, b] | |
| 17 | + | default: a | |
| 18 | + | env: | |
| 19 | + | A: 1 | |
| 20 | + | B: ${{ github.ref }} | |
| 21 | + | defaults: | |
| 22 | + | run: | |
| 23 | + | shell: bash | |
| 24 | + | working-directory: ./x | |
| 25 | + | permissions: | |
| 26 | + | contents: read | |
| 27 | + | id-token: write | |
| 28 | + | concurrency: | |
| 29 | + | group: ${{ github.workflow }}-${{ github.ref }} | |
| 30 | + | cancel-in-progress: true | |
| 31 | + | jobs: | |
| 32 | + | build: | |
| 33 | + | name: Build ${{ matrix.os }} | |
| 34 | + | runs-on: ${{ matrix.os }} | |
| 35 | + | timeout-minutes: 10 | |
| 36 | + | strategy: | |
| 37 | + | fail-fast: false | |
| 38 | + | max-parallel: 2 | |
| 39 | + | matrix: | |
| 40 | + | os: [ubuntu-latest, macos-latest] | |
| 41 | + | node: [18, 20] | |
| 42 | + | include: | |
| 43 | + | - os: ubuntu-latest | |
| 44 | + | extra: yes | |
| 45 | + | exclude: | |
| 46 | + | - os: macos-latest | |
| 47 | + | node: 18 | |
| 48 | + | outputs: | |
| 49 | + | v: ${{ steps.s.outputs.v }} | |
| 50 | + | env: | |
| 51 | + | X: ${{ matrix.node }} | |
| 52 | + | steps: | |
| 53 | + | - uses: actions/checkout@v4 | |
| 54 | + | with: | |
| 55 | + | fetch-depth: 0 | |
| 56 | + | - id: s | |
| 57 | + | run: echo "v=1" >> "$GITHUB_OUTPUT" | |
| 58 | + | shell: bash | |
| 59 | + | working-directory: sub | |
| 60 | + | if: github.ref == 'refs/heads/main' | |
| 61 | + | continue-on-error: ${{ matrix.experimental }} | |
| 62 | + | timeout-minutes: 3 | |
| 63 | + | env: | |
| 64 | + | Y: ${{ steps.s.outputs.v }} | |
| 65 | + | - run: echo | |
| 66 | + | if: always() | |
| 67 | + | test: | |
| 68 | + | needs: build | |
| 69 | + | runs-on: [self-hosted, linux] | |
| 70 | + | if: ${{ needs.build.result == 'success' }} | |
| 71 | + | container: | |
| 72 | + | image: node:20 | |
| 73 | + | env: {A: b} | |
| 74 | + | ports: ['80'] | |
| 75 | + | services: | |
| 76 | + | db: | |
| 77 | + | image: postgres | |
| 78 | + | credentials: {username: u, password: p} | |
| 79 | + | steps: | |
| 80 | + | - run: echo ${{ needs.build.outputs.v }} | |
| 81 | + | deploy: | |
| 82 | + | needs: [build, test] | |
| 83 | + | uses: ./.github/workflows/d.yml | |
| 84 | + | with: | |
| 85 | + | a: ${{ needs.build.outputs.v }} | |
| 86 | + | secrets: inherit | |
A workflow/testdata/convert/valid_minimal.yml +5 -0
| @@ -0,0 +1,5 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | steps: [{run: a}] | |
A workflow/testdata/convert/workflow_defaults_bad.yml +8 -0
| @@ -0,0 +1,8 @@ | |||
| 1 | + | on: push | |
| 2 | + | defaults: | |
| 3 | + | run: | |
| 4 | + | bogus: 1 | |
| 5 | + | jobs: | |
| 6 | + | a: | |
| 7 | + | runs-on: x | |
| 8 | + | steps: [{run: a}] | |
A workflow/testdata/convert/yes_no.yml +6 -0
| @@ -0,0 +1,6 @@ | |||
| 1 | + | on: push | |
| 2 | + | jobs: | |
| 3 | + | a: | |
| 4 | + | runs-on: ubuntu-latest | |
| 5 | + | continue-on-error: yes | |
| 6 | + | steps: [{run: a}] | |