v0.30.0 c899179

ci_validate.go RawHistoryFolder

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package usecase
4
5import (
6	"strings"
7
8	"bnema.dev/ermite-actions/template"
9	"bnema.dev/ermite-actions/workflow"
10	"bnema.dev/ermite/internal/domain"
11)
12
13// parseWorkflow is workflow.Parse plus the checks GitHub makes when it reads a
14// workflow that the parser of ermite-actions does not: a schedule timezone that
15// is no IANA time zone name, and queue: max with cancel-in-progress: true (CI-4
16// D6). A workflow with such an error is invalid like one that does not parse.
17func parseWorkflow(path string, content []byte) (*workflow.Workflow, []workflow.Error) {
18	wf, errs := workflow.Parse(path, content)
19	if len(errs) > 0 || wf == nil {
20		return wf, errs
21	}
22	return wf, validateWorkflow(path, wf)
23}
24
25func validateWorkflow(path string, wf *workflow.Workflow) []workflow.Error {
26	var errs []workflow.Error
27	if wf.On != nil {
28		if ev := wf.On.Event(eventSchedule); ev != nil {
29			for _, sc := range ev.Schedule {
30				if _, err := domain.ParseCronTimezone(sc.Timezone); err != nil {
31					errs = append(errs, workflow.Error{File: path, Raw: strings.TrimPrefix(err.Error(), domain.ErrInvalid.Error()+": ")})
32				}
33			}
34		}
35	}
36	check := func(tok template.Token) {
37		if pos, bad := queueMaxWithCancel(tok); bad {
38			errs = append(errs, workflow.Error{File: path, Pos: pos, Raw: errQueueMaxCancel.Error()})
39		}
40	}
41	check(wf.Concurrency)
42	for _, j := range wf.Jobs {
43		check(j.Concurrency)
44	}
45	return errs
46}
47
48// queueMaxWithCancel reports a concurrency mapping that writes queue: max and
49// cancel-in-progress: true as literals, the pair GitHub refuses as a workflow
50// validation error (docs.github.com, "Control the concurrency of workflows and
51// jobs": "The combination of `queue: max` and `cancel-in-progress: true` is not
52// allowed and will result in a workflow validation error"; languageservices
53// languageservice/src/validate.ts:702-743 checks the literal pair). An
54// expression is checked when it is evaluated (evalConcurrency).
55func queueMaxWithCancel(tok template.Token) (template.Pos, bool) {
56	m, ok := tok.(*template.Mapping)
57	if !ok {
58		return template.Pos{}, false
59	}
60	q, ok := m.Find("queue").(*template.String)
61	if !ok || q.Value != "max" {
62		return template.Pos{}, false
63	}
64	c, ok := m.Find("cancel-in-progress").(*template.Boolean)
65	if !ok || !c.Value {
66		return template.Pos{}, false
67	}
68	return c.Pos(), true
69}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 3

Tags 44

↑↓ moveenter openesc close