fun_ci 2.2.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +141 -0
- data/README.md +19 -11
- data/lib/fun_ci/agent/cancel_command.rb +52 -0
- data/lib/fun_ci/agent/commands.rb +3 -1
- data/lib/fun_ci/agent/job_events.rb +26 -7
- data/lib/fun_ci/agent/job_json.rb +10 -1
- data/lib/fun_ci/agent/job_report.rb +10 -6
- data/lib/fun_ci/agent/job_why_text.rb +11 -3
- data/lib/fun_ci/agent/jobs_text.rb +8 -3
- data/lib/fun_ci/agent/live_pipeline.rb +7 -4
- data/lib/fun_ci/agent/snapshots.rb +5 -1
- data/lib/fun_ci/agent/starts_in.rb +14 -0
- data/lib/fun_ci/agent/status_json.rb +1 -1
- data/lib/fun_ci/agent/status_text.rb +15 -3
- data/lib/fun_ci/agent/trunk_reading.rb +2 -1
- data/lib/fun_ci/agent/wait_command.rb +2 -1
- data/lib/fun_ci/cli.rb +3 -1
- data/lib/fun_ci/cli_command_help.rb +51 -0
- data/lib/fun_ci/cli_help.rb +11 -10
- data/lib/fun_ci/console/board_data.rb +7 -2
- data/lib/fun_ci/console/job_message.rb +12 -6
- data/lib/fun_ci/console/job_order.rb +5 -4
- data/lib/fun_ci/console/key_handler.rb +3 -1
- data/lib/fun_ci/console/trunk_marks.rb +3 -1
- data/lib/fun_ci/evidence/about_stage.rb +1 -1
- data/lib/fun_ci/evidence/collector.rb +6 -3
- data/lib/fun_ci/evidence/outcome.rb +6 -3
- data/lib/fun_ci/evidence/settings.rb +1 -1
- data/lib/fun_ci/jobs/due.rb +4 -3
- data/lib/fun_ci/jobs/due_jobs.rb +26 -2
- data/lib/fun_ci/jobs/job_fork.rb +19 -12
- data/lib/fun_ci/jobs/job_run.rb +26 -7
- data/lib/fun_ci/jobs/locks.rb +1 -1
- data/lib/fun_ci/jobs/schedule.rb +25 -0
- data/lib/fun_ci/jobs/site.rb +10 -3
- data/lib/fun_ci/jobs/standings.rb +4 -0
- data/lib/fun_ci/jobs/state.rb +3 -2
- data/lib/fun_ci/jobs/wall_clock_wait.rb +29 -0
- data/lib/fun_ci/persistence/active_jobs.rb +15 -18
- data/lib/fun_ci/persistence/active_runs.rb +5 -3
- data/lib/fun_ci/persistence/died.rb +21 -0
- data/lib/fun_ci/persistence/job_recorder.rb +1 -0
- data/lib/fun_ci/persistence/job_runs.rb +12 -0
- data/lib/fun_ci/persistence/null_recorder.rb +1 -0
- data/lib/fun_ci/persistence/pipeline_recorder.rb +1 -0
- data/lib/fun_ci/persistence/pipeline_run.rb +13 -9
- data/lib/fun_ci/persistence/stage_job.rb +12 -0
- data/lib/fun_ci/pipeline/budgets.rb +9 -0
- data/lib/fun_ci/pipeline/group_kill.rb +38 -0
- data/lib/fun_ci/pipeline/pipeline_forker.rb +14 -9
- data/lib/fun_ci/pipeline/priorities.rb +23 -0
- data/lib/fun_ci/pipeline/process_runner.rb +13 -6
- data/lib/fun_ci/pipeline/process_tree.rb +37 -0
- data/lib/fun_ci/pipeline/progress_reporter.rb +5 -0
- data/lib/fun_ci/pipeline/run_canceller.rb +15 -3
- data/lib/fun_ci/pipeline/slot.rb +11 -0
- data/lib/fun_ci/pipeline/slot_run.rb +19 -4
- data/lib/fun_ci/pipeline/stage_end.rb +2 -1
- data/lib/fun_ci/pipeline/trigger.rb +17 -6
- data/lib/fun_ci/pipeline/trigger_command.rb +6 -2
- data/lib/fun_ci/pipeline/trigger_params.rb +20 -9
- data/lib/fun_ci/pipeline/workspaces.rb +2 -1
- data/lib/fun_ci/pipeline/worktree_pool.rb +22 -5
- data/lib/fun_ci/pipeline/worktree_prune.rb +9 -4
- data/lib/fun_ci/pipeline/worktrees.rb +39 -6
- data/lib/fun_ci/setup/agent_instructions.rb +21 -5
- data/lib/fun_ci/setup/commands.rb +19 -8
- data/lib/fun_ci/setup/eslint_detection.rb +38 -0
- data/lib/fun_ci/setup/hook_script.rb +9 -4
- data/lib/fun_ci/setup/hook_writer.rb +13 -5
- data/lib/fun_ci/setup/init_options.rb +34 -0
- data/lib/fun_ci/setup/installed_hooks.rb +45 -0
- data/lib/fun_ci/setup/installer.rb +44 -9
- data/lib/fun_ci/setup/lfs_hook.rb +50 -0
- data/lib/fun_ci/setup/placeholders.rb +30 -0
- data/lib/fun_ci/setup/project_config.rb +8 -1
- data/lib/fun_ci/setup/settings.rb +59 -10
- data/lib/fun_ci/setup/setup_checker.rb +16 -3
- data/lib/fun_ci/setup/stage_templates/javascript.rb +14 -4
- data/lib/fun_ci/setup/stage_templates/placeholders.rb +21 -0
- data/lib/fun_ci/setup/stage_templates.rb +6 -4
- data/lib/fun_ci/setup/template_writer.rb +3 -5
- data/lib/fun_ci/trunk/shown.rb +7 -4
- data/lib/fun_ci/version.rb +1 -1
- metadata +17 -1
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
require_relative "project_detector"
|
|
4
4
|
require_relative "template_writer"
|
|
5
5
|
require_relative "lint_detection"
|
|
6
|
+
require_relative "eslint_detection"
|
|
7
|
+
require_relative "stage_templates"
|
|
6
8
|
require_relative "agent_instructions"
|
|
7
9
|
|
|
8
10
|
module FunCi
|
|
@@ -14,13 +16,18 @@ module FunCi
|
|
|
14
16
|
"Then fast.sh runs beside slow.sh, both on what build.sh built: have build.sh " \
|
|
15
17
|
"compile everything they need, test code included."].freeze
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
PLACEHOLDERS = "Could not detect the project type, so .fun-ci/ has placeholder scripts that pass and " \
|
|
20
|
+
"test nothing: put the project's own commands in them, or start again with --stack NAME."
|
|
21
|
+
|
|
22
|
+
# stack: a name of ProjectDetector::STACKS to write in place of the one detected.
|
|
23
|
+
def self.run(project_root:, stdout: $stdout, stack: nil)
|
|
24
|
+
new(project_root: project_root, stdout: stdout, stack: stack).run
|
|
19
25
|
end
|
|
20
26
|
|
|
21
|
-
def initialize(project_root:, stdout:)
|
|
27
|
+
def initialize(project_root:, stdout:, stack:)
|
|
22
28
|
@project_root = project_root
|
|
23
29
|
@stdout = stdout
|
|
30
|
+
@stack = stack
|
|
24
31
|
end
|
|
25
32
|
|
|
26
33
|
# A project set up for fun-ci, new or not, also tells agents what to do.
|
|
@@ -35,20 +42,37 @@ module FunCi
|
|
|
35
42
|
def set_up
|
|
36
43
|
return report(".fun-ci/ already exists, so init left it as it is.", 0) if initialised?
|
|
37
44
|
|
|
38
|
-
|
|
39
|
-
return
|
|
45
|
+
stack = chosen_stack
|
|
46
|
+
return unknown_stack if stack.nil?
|
|
47
|
+
return write_placeholders if stack == :unknown
|
|
48
|
+
|
|
49
|
+
write_templates(stack)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def chosen_stack
|
|
53
|
+
return ProjectDetector.new(Dir.children(@project_root)).detect if @stack.nil?
|
|
54
|
+
|
|
55
|
+
ProjectDetector::STACKS.keys.find { |stack| stack.to_s == @stack }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def unknown_stack
|
|
59
|
+
report("fun-ci init: no stack named #{@stack}; the stacks are #{ProjectDetector::STACKS.keys.join(", ")}", 64)
|
|
60
|
+
end
|
|
40
61
|
|
|
41
|
-
|
|
62
|
+
def write_placeholders
|
|
63
|
+
TemplateWriter.new(StageTemplates::PLACEHOLDERS, @project_root).write
|
|
64
|
+
report(PLACEHOLDERS, 0)
|
|
42
65
|
end
|
|
43
66
|
|
|
44
67
|
def tell_agents
|
|
45
68
|
file = AgentInstructions.file_for(Dir.children(@project_root))
|
|
46
69
|
path = File.join(@project_root, file)
|
|
47
|
-
|
|
70
|
+
told = File.exist?(path) ? File.read(path) : ""
|
|
71
|
+
merged = AgentInstructions.merged(told)
|
|
48
72
|
return unless merged
|
|
49
73
|
|
|
50
74
|
File.write(path, merged)
|
|
51
|
-
@stdout.puts
|
|
75
|
+
@stdout.puts AgentInstructions.said(told, file)
|
|
52
76
|
end
|
|
53
77
|
|
|
54
78
|
def initialised?
|
|
@@ -57,11 +81,22 @@ module FunCi
|
|
|
57
81
|
|
|
58
82
|
def write_templates(detected)
|
|
59
83
|
@stdout.puts "Detected: #{detected.to_s.tr("_", " ")}"
|
|
60
|
-
TemplateWriter.new(
|
|
84
|
+
TemplateWriter.new(scripts_for(detected), @project_root).write
|
|
61
85
|
@stdout.puts "Created .fun-ci/ with template scripts.", *SIDE_BY_SIDE
|
|
62
86
|
0
|
|
63
87
|
end
|
|
64
88
|
|
|
89
|
+
def scripts_for(detected)
|
|
90
|
+
StageTemplates.scripts(detected, lint_override: LintDetection.command(detected, @project_root),
|
|
91
|
+
build_override: EslintDetection.build(detected, Dir.children(@project_root),
|
|
92
|
+
package_json))
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def package_json
|
|
96
|
+
path = File.join(@project_root, "package.json")
|
|
97
|
+
File.exist?(path) ? File.read(path) : "{}"
|
|
98
|
+
end
|
|
99
|
+
|
|
65
100
|
def report(message, exit_code)
|
|
66
101
|
@stdout.puts message
|
|
67
102
|
exit_code
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "hook_script"
|
|
4
|
+
|
|
5
|
+
module FunCi
|
|
6
|
+
module Setup
|
|
7
|
+
# git-lfs's own post-commit and pre-push hooks, as `git lfs install`
|
|
8
|
+
# writes them, and the hook fun-ci writes in their place, which runs
|
|
9
|
+
# fun-ci's, then git-lfs's. A push reads its ref list from git once, so
|
|
10
|
+
# the pre-push hook keeps it in a file and gives it to both.
|
|
11
|
+
module LfsHook
|
|
12
|
+
GUARD = %r{\Acommand -v git-lfs >/dev/null 2>&1 \|\| \{.*; exit 2; \}\z}
|
|
13
|
+
MISSING = %(command -v git-lfs >/dev/null 2>&1 || { echo "This repository is configured for Git LFS ) +
|
|
14
|
+
%(but 'git-lfs' was not found on your path." >&2; exit 2; })
|
|
15
|
+
|
|
16
|
+
RUN = {
|
|
17
|
+
"post-commit" => "if #{HookScript::INSTALLED}; then fun_ci; else #{HookScript::NOT_INSTALLED}; fi\n",
|
|
18
|
+
"pre-push" => %(if #{HookScript::INSTALLED}; then fun_ci < "$refs" || exit 1; ) +
|
|
19
|
+
"else #{HookScript::NOT_INSTALLED}; fi\n"
|
|
20
|
+
}.freeze
|
|
21
|
+
|
|
22
|
+
KEEP_REFS = { "post-commit" => "", "pre-push" => <<~SH }.freeze
|
|
23
|
+
refs=$(mktemp) || exit 1
|
|
24
|
+
trap 'rm -f "$refs"' EXIT
|
|
25
|
+
cat > "$refs"
|
|
26
|
+
SH
|
|
27
|
+
|
|
28
|
+
FUN_CI = { "post-commit" => HookScript::POST_COMMIT, "pre-push" => "#{HookScript::PRE_PUSH_WAITS}return $status\n" }
|
|
29
|
+
.freeze
|
|
30
|
+
LFS_INPUT = { "post-commit" => "", "pre-push" => %( < "$refs") }.freeze
|
|
31
|
+
|
|
32
|
+
# Whether `script` is the hook `git lfs install` writes for `hook_type`
|
|
33
|
+
# and nothing more.
|
|
34
|
+
def self.stock?(script, hook_type)
|
|
35
|
+
lines = script.lines(chomp: true).reject { |line| line.strip.empty? }
|
|
36
|
+
lines.size == 3 && lines.first == "#!/bin/sh" && GUARD.match?(lines[1]) && lines.last == call(hook_type)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.runs_lfs?(script, hook_type) = script.include?(call(hook_type))
|
|
40
|
+
|
|
41
|
+
def self.script(hook_type)
|
|
42
|
+
"#!/bin/sh\n#{HookScript::MARKER}\n# fun-ci's #{hook_type} hook, then git-lfs's, which it replaced.\n" \
|
|
43
|
+
"#{KEEP_REFS.fetch(hook_type)}fun_ci() {\n#{FUN_CI.fetch(hook_type)}}\n" \
|
|
44
|
+
"#{RUN.fetch(hook_type)}#{MISSING}\n#{call(hook_type)}#{LFS_INPUT.fetch(hook_type)}\n"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.call(hook_type) = %(git lfs #{hook_type} "$@")
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "stage_templates"
|
|
4
|
+
|
|
5
|
+
module FunCi
|
|
6
|
+
module Setup
|
|
7
|
+
# The stage scripts in .fun-ci/ that are still init's placeholders, as
|
|
8
|
+
# `fun-ci check`'s warnings.
|
|
9
|
+
class Placeholders
|
|
10
|
+
STAGES = %w[lint build fast slow].freeze
|
|
11
|
+
|
|
12
|
+
def initialize(project_root)
|
|
13
|
+
@dir = File.join(project_root, ".fun-ci")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def warnings = STAGES.select { |stage| placeholder?(stage) }.map { |stage| warning(stage) }
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def placeholder?(stage)
|
|
21
|
+
path = File.join(@dir, "#{stage}.sh")
|
|
22
|
+
File.file?(path) && File.read(path).include?(StageTemplates::MARKER)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def warning(stage)
|
|
26
|
+
".fun-ci/#{stage}.sh is init's placeholder, so the #{stage} stage tests nothing: put the project's own in it"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -20,12 +20,17 @@ module FunCi
|
|
|
20
20
|
Dir.exist?(@fun_ci_dir)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
# What stops a pipeline: no .fun-ci/, or a stage script it can't run.
|
|
23
24
|
def validate
|
|
24
25
|
return ["No .fun-ci/ folder found in #{@project_root}"] unless folder_exists?
|
|
25
26
|
|
|
26
|
-
REQUIRED_SCRIPTS.flat_map { |script| script_errors(script) }
|
|
27
|
+
REQUIRED_SCRIPTS.flat_map { |script| script_errors(script) }
|
|
27
28
|
end
|
|
28
29
|
|
|
30
|
+
# Mistakes in .fun-ci/config, which never stop a pipeline: the setting
|
|
31
|
+
# takes its default instead.
|
|
32
|
+
def settings_errors = settings.errors
|
|
33
|
+
|
|
29
34
|
# Mistakes in the `evidence` key, which `fun-ci check` reports but which
|
|
30
35
|
# never stop a pipeline: the entry with the mistake is left out instead.
|
|
31
36
|
def evidence_errors = Evidence::Settings.load(File.join(@fun_ci_dir, "config")).errors(root: @project_root)
|
|
@@ -44,8 +49,10 @@ module FunCi
|
|
|
44
49
|
def job_errors = Jobs::Folders.new(@project_root).errors
|
|
45
50
|
|
|
46
51
|
def worktree_slots = settings.worktree_slots
|
|
52
|
+
def clean = settings.clean
|
|
47
53
|
def trunk = settings.trunk
|
|
48
54
|
def trunk_fetch = settings.trunk_fetch
|
|
55
|
+
def job_spacing = settings.job_spacing
|
|
49
56
|
|
|
50
57
|
def script_path(stage)
|
|
51
58
|
File.join(@fun_ci_dir, "#{stage}.sh")
|
|
@@ -6,7 +6,8 @@ module FunCi
|
|
|
6
6
|
module Setup
|
|
7
7
|
# .fun-ci/config: settings for this machine's runs, as YAML. Each one left
|
|
8
8
|
# out takes its default; one that is wrong is reported and the default used,
|
|
9
|
-
# as are all of them when the file
|
|
9
|
+
# as are all of them when the file can't be read as YAML. Anchors and
|
|
10
|
+
# aliases are YAML, so they are read.
|
|
10
11
|
class Settings
|
|
11
12
|
DEFAULTS = { "worktree_slots" => 2 }.freeze
|
|
12
13
|
FETCH_EVERY = 300
|
|
@@ -20,7 +21,7 @@ module FunCi
|
|
|
20
21
|
@text = text
|
|
21
22
|
end
|
|
22
23
|
|
|
23
|
-
def worktree_slots =
|
|
24
|
+
def worktree_slots = slots || DEFAULTS.fetch("worktree_slots")
|
|
24
25
|
|
|
25
26
|
# The trunk's name, or nil when none is given or it is no name. A mistake
|
|
26
27
|
# in the trunk settings never stops a pipeline, so none is among `errors`.
|
|
@@ -37,22 +38,71 @@ module FunCi
|
|
|
37
38
|
seconds(given) || FETCH_EVERY
|
|
38
39
|
end
|
|
39
40
|
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
# Seconds between the starts of the jobs a commit starts, 0 for all at
|
|
42
|
+
# once (Jobs::Schedule); nil when none is given, or none it can read,
|
|
43
|
+
# and the jobs share the day evenly (Jobs::DueJobs).
|
|
44
|
+
def job_spacing = seconds(setting("job_spacing"))
|
|
42
45
|
|
|
43
|
-
|
|
44
|
-
|
|
46
|
+
# The paths, inside the project, whose git-ignored files a slot loses
|
|
47
|
+
# when it moves off its line of history (Pipeline::Worktrees); none
|
|
48
|
+
# when none are given, or what is given is no such list.
|
|
49
|
+
def clean = cleaned || []
|
|
45
50
|
|
|
46
|
-
|
|
51
|
+
def errors
|
|
52
|
+
setting_errors
|
|
47
53
|
rescue Psych::SyntaxError => e
|
|
48
54
|
[".fun-ci/config is not YAML: #{e.problem} (line #{e.line})"]
|
|
55
|
+
rescue Psych::DisallowedClass => e
|
|
56
|
+
[".fun-ci/config can't be read: #{e.message} (quote a value such as a date to read it as text)"]
|
|
57
|
+
rescue Psych::Exception => e
|
|
58
|
+
[".fun-ci/config can't be read: #{e.message}"]
|
|
49
59
|
end
|
|
50
60
|
|
|
51
61
|
private
|
|
52
62
|
|
|
63
|
+
def setting_errors
|
|
64
|
+
return [".fun-ci/config must be a mapping such as `worktree_slots: 2`"] unless raw.is_a?(Hash)
|
|
65
|
+
|
|
66
|
+
[slots_error, spacing_error, clean_error].compact
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# The slots the file asks for, when that is a whole number above 0.
|
|
70
|
+
def slots
|
|
71
|
+
given = setting("worktree_slots")
|
|
72
|
+
given if given.is_a?(Integer) && given.positive?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def slots_error
|
|
76
|
+
given = setting("worktree_slots")
|
|
77
|
+
".fun-ci/config: worktree_slots must be a whole number above 0, not #{given.inspect}" unless given.nil? || slots
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Unlike the trunk's, since taking turns is the jobs' whole point (AT-13.28).
|
|
81
|
+
def spacing_error
|
|
82
|
+
given = setting("job_spacing")
|
|
83
|
+
return nil if given.nil? || seconds(given)
|
|
84
|
+
|
|
85
|
+
".fun-ci/config: job_spacing must be seconds, or a number with s, m or h, not #{given.inspect}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def cleaned
|
|
89
|
+
given = setting("clean")
|
|
90
|
+
given if given.is_a?(Array) && given.all? { |path| inside?(path) }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def inside?(path) = path.is_a?(String) && !path.start_with?("/") && !path.split("/").include?("..")
|
|
94
|
+
|
|
95
|
+
def clean_error
|
|
96
|
+
given = setting("clean")
|
|
97
|
+
return nil if given.nil? || cleaned
|
|
98
|
+
|
|
99
|
+
".fun-ci/config: clean must be a list of paths inside the project, such as [public/assets], " \
|
|
100
|
+
"not #{given.inspect}"
|
|
101
|
+
end
|
|
102
|
+
|
|
53
103
|
def setting(key)
|
|
54
104
|
raw.is_a?(Hash) ? raw[key] : nil
|
|
55
|
-
rescue Psych::
|
|
105
|
+
rescue Psych::Exception
|
|
56
106
|
nil
|
|
57
107
|
end
|
|
58
108
|
|
|
@@ -61,8 +111,7 @@ module FunCi
|
|
|
61
111
|
match && (match[1].to_i * UNITS.fetch(match[2]))
|
|
62
112
|
end
|
|
63
113
|
|
|
64
|
-
def raw = (@text && YAML.safe_load(@text)) || {}
|
|
65
|
-
def values = DEFAULTS.merge(raw)
|
|
114
|
+
def raw = (@text && YAML.safe_load(@text, aliases: true)) || {}
|
|
66
115
|
end
|
|
67
116
|
end
|
|
68
117
|
end
|
|
@@ -2,13 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "project_config"
|
|
4
4
|
require_relative "legacy_hooks"
|
|
5
|
+
require_relative "installed_hooks"
|
|
5
6
|
require_relative "trunk_report"
|
|
7
|
+
require_relative "placeholders"
|
|
6
8
|
|
|
7
9
|
module FunCi
|
|
8
10
|
module Setup
|
|
9
11
|
class SetupChecker
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
# What it warns of: fun-ci 1.x's hooks, any in `hooks_dir` that doesn't
|
|
13
|
+
# run fun-ci, and stage scripts still init's placeholders.
|
|
14
|
+
Hooks = Data.define(:sources) do
|
|
15
|
+
def warnings = sources.flat_map(&:warnings)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# hooks_dir: where git runs the project's hooks from; nil outside a git repository.
|
|
19
|
+
def self.run(project_root:, stdout: $stdout, hooks_dir: nil)
|
|
20
|
+
sources = [LegacyHooks.new(project_root), InstalledHooks.new(hooks_dir), Placeholders.new(project_root)]
|
|
21
|
+
hooks = Hooks.new(sources)
|
|
22
|
+
new(config: ProjectConfig.new(project_root), hooks: hooks, stdout: stdout,
|
|
12
23
|
trunk: TrunkReport.new(project_root)).run
|
|
13
24
|
end
|
|
14
25
|
|
|
@@ -22,7 +33,7 @@ module FunCi
|
|
|
22
33
|
|
|
23
34
|
# Warnings are printed but don't fail the check.
|
|
24
35
|
def run
|
|
25
|
-
errors =
|
|
36
|
+
errors = problems
|
|
26
37
|
@stdout.puts(errors.empty? ? "All OK. The project is configured." : errors)
|
|
27
38
|
@stdout.puts(@trunk.lines)
|
|
28
39
|
list_presets
|
|
@@ -33,6 +44,8 @@ module FunCi
|
|
|
33
44
|
|
|
34
45
|
private
|
|
35
46
|
|
|
47
|
+
def problems = @config.validate + @config.settings_errors + @config.evidence_errors + @config.job_errors
|
|
48
|
+
|
|
36
49
|
def list_presets
|
|
37
50
|
lists = { "this project" => @config.presets, "any project's output" => @config.any_project_presets }
|
|
38
51
|
lists.each { |what, names| @stdout.puts "Evidence presets for #{what}: #{names.join(", ")}" if names.any? }
|
|
@@ -5,13 +5,23 @@ require_relative "shell"
|
|
|
5
5
|
module FunCi
|
|
6
6
|
module Setup
|
|
7
7
|
# JavaScript and TypeScript: Deno, Bun, and Node with its package managers.
|
|
8
|
+
# Lint runs beside the build, which installs the packages, so it checks
|
|
9
|
+
# the source alone: a linter that loads them, ESLint, goes in the build
|
|
10
|
+
# after the install (EslintDetection). Deno's lint needs no packages.
|
|
8
11
|
module StageTemplates
|
|
12
|
+
SOURCES = "find . -name node_modules -prune -o -name .git -prune -o -type f"
|
|
13
|
+
NODE_CHECK = "#{SOURCES} \\( -name '*.js' -o -name '*.mjs' -o -name '*.cjs' \\) -print | " \
|
|
14
|
+
'while read -r file; do node --check "$file" || exit 1; done'.freeze
|
|
15
|
+
BUN_CHECK = "#{SOURCES} \\( -name '*.js' -o -name '*.mjs' -o -name '*.cjs' -o -name '*.ts' " \
|
|
16
|
+
"-o -name '*.tsx' \\) -print | " \
|
|
17
|
+
'while read -r file; do bun build --no-bundle "$file" > /dev/null || exit 1; done'.freeze
|
|
18
|
+
|
|
9
19
|
JAVASCRIPT = {
|
|
10
20
|
deno: stages("deno lint", "deno check", "deno test", "deno task test:slow"),
|
|
11
|
-
bun: stages(
|
|
12
|
-
node_pnpm: stages(
|
|
13
|
-
node_yarn: stages(
|
|
14
|
-
node_npm: stages(
|
|
21
|
+
bun: stages(BUN_CHECK, "bun install", "bun test", "bun run test:slow"),
|
|
22
|
+
node_pnpm: stages(NODE_CHECK, "pnpm install", "pnpm test", "pnpm run test:slow"),
|
|
23
|
+
node_yarn: stages(NODE_CHECK, "yarn install", "yarn test", "yarn run test:slow"),
|
|
24
|
+
node_npm: stages(NODE_CHECK, "npm install", "npm test", "npm run test:slow")
|
|
15
25
|
}.freeze
|
|
16
26
|
end
|
|
17
27
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "shell"
|
|
4
|
+
|
|
5
|
+
module FunCi
|
|
6
|
+
module Setup
|
|
7
|
+
# The scripts `fun-ci init` writes where it knows no stack: each passes,
|
|
8
|
+
# says it tests nothing, and carries MARKER, by which `fun-ci check` warns
|
|
9
|
+
# of it until the project's own command takes its place.
|
|
10
|
+
module StageTemplates
|
|
11
|
+
MARKER = "# fun-ci placeholder"
|
|
12
|
+
|
|
13
|
+
def self.placeholder(stage)
|
|
14
|
+
"#{MARKER}: init knew no stack here, so put the project's #{stage} in place of this.\n" \
|
|
15
|
+
"echo \"fun-ci: .fun-ci/#{stage}.sh is init's placeholder and tests nothing\""
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
PLACEHOLDERS = stages(*%w[lint build fast slow].map { |stage| placeholder(stage) }).freeze
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -4,6 +4,7 @@ require_relative "stage_templates/managed"
|
|
|
4
4
|
require_relative "stage_templates/native"
|
|
5
5
|
require_relative "stage_templates/scripting"
|
|
6
6
|
require_relative "stage_templates/javascript"
|
|
7
|
+
require_relative "stage_templates/placeholders"
|
|
7
8
|
|
|
8
9
|
module FunCi
|
|
9
10
|
module Setup
|
|
@@ -15,10 +16,11 @@ module FunCi
|
|
|
15
16
|
module StageTemplates
|
|
16
17
|
TEMPLATES = [MANAGED, NATIVE, SCRIPTING, JAVASCRIPT].reduce(:merge).freeze
|
|
17
18
|
|
|
18
|
-
# { "lint.sh" => script, ... }; +lint_override+
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
# { "lint.sh" => script, ... }; +lint_override+ and +build_override+
|
|
20
|
+
# replace the lint and build commands.
|
|
21
|
+
def self.scripts(template_id, lint_override: nil, build_override: nil)
|
|
22
|
+
overrides = { "lint.sh" => lint_override, "build.sh" => build_override }.compact
|
|
23
|
+
TEMPLATES.fetch(template_id).merge(overrides.to_h { |name, command| [name, script(name, command)] })
|
|
22
24
|
end
|
|
23
25
|
end
|
|
24
26
|
end
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "stage_templates"
|
|
4
|
-
|
|
5
3
|
module FunCi
|
|
6
4
|
module Setup
|
|
7
|
-
# Writes a project's .fun-ci/ stage scripts, executable.
|
|
5
|
+
# Writes a project's .fun-ci/ stage scripts, executable: scripts is { "lint.sh" => script, ... }.
|
|
8
6
|
class TemplateWriter
|
|
9
|
-
def initialize(
|
|
10
|
-
@scripts =
|
|
7
|
+
def initialize(scripts, target_dir)
|
|
8
|
+
@scripts = scripts
|
|
11
9
|
@target_dir = target_dir
|
|
12
10
|
end
|
|
13
11
|
|
data/lib/fun_ci/trunk/shown.rb
CHANGED
|
@@ -8,8 +8,9 @@ module FunCi
|
|
|
8
8
|
# rather than stored, how many seconds ago its trunk tip was seen (nil
|
|
9
9
|
# when it had none), the project's last fetch (a LastFetch, nil if none), and
|
|
10
10
|
# the SHA the trunk has moved to since, if it has.
|
|
11
|
-
# A run whose check hasn't been recorded is checking
|
|
12
|
-
# merge are past their time, and then
|
|
11
|
+
# A run whose check hasn't been recorded is checking while its foreground
|
|
12
|
+
# stages run and until its fetch and merge are past their time, and then
|
|
13
|
+
# unknown.
|
|
13
14
|
Shown = Data.define(:state, :check, :age, :fetch, :moved_to)
|
|
14
15
|
|
|
15
16
|
class Shown
|
|
@@ -21,8 +22,10 @@ module FunCi
|
|
|
21
22
|
fetch: fetch, moved_to: moved_to)
|
|
22
23
|
end
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
# stages_running: whether the run's foreground stages, which the check
|
|
26
|
+
# is recorded after, are still going.
|
|
27
|
+
def self.unchecked(started:, now:, stages_running: false)
|
|
28
|
+
if stages_running || now - started <= FETCH_DEADLINE + CHECK_BUDGET
|
|
26
29
|
return new(state: "checking", check: nil, age: nil, fetch: nil, moved_to: nil)
|
|
27
30
|
end
|
|
28
31
|
|
data/lib/fun_ci/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fun_ci
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Erik Thyge Madsen
|
|
@@ -63,6 +63,7 @@ files:
|
|
|
63
63
|
- exe/fun-ci
|
|
64
64
|
- lib/fun_ci.rb
|
|
65
65
|
- lib/fun_ci/agent/age.rb
|
|
66
|
+
- lib/fun_ci/agent/cancel_command.rb
|
|
66
67
|
- lib/fun_ci/agent/command_support.rb
|
|
67
68
|
- lib/fun_ci/agent/commands.rb
|
|
68
69
|
- lib/fun_ci/agent/context.rb
|
|
@@ -94,6 +95,7 @@ files:
|
|
|
94
95
|
- lib/fun_ci/agent/snapshots.rb
|
|
95
96
|
- lib/fun_ci/agent/span.rb
|
|
96
97
|
- lib/fun_ci/agent/stage_summary.rb
|
|
98
|
+
- lib/fun_ci/agent/starts_in.rb
|
|
97
99
|
- lib/fun_ci/agent/status_command.rb
|
|
98
100
|
- lib/fun_ci/agent/status_json.rb
|
|
99
101
|
- lib/fun_ci/agent/status_text.rb
|
|
@@ -111,6 +113,7 @@ files:
|
|
|
111
113
|
- lib/fun_ci/agent/why_json.rb
|
|
112
114
|
- lib/fun_ci/agent/why_text.rb
|
|
113
115
|
- lib/fun_ci/cli.rb
|
|
116
|
+
- lib/fun_ci/cli_command_help.rb
|
|
114
117
|
- lib/fun_ci/cli_help.rb
|
|
115
118
|
- lib/fun_ci/console/board_data.rb
|
|
116
119
|
- lib/fun_ci/console/board_heartbeat.rb
|
|
@@ -226,12 +229,15 @@ files:
|
|
|
226
229
|
- lib/fun_ci/jobs/job_fork.rb
|
|
227
230
|
- lib/fun_ci/jobs/job_run.rb
|
|
228
231
|
- lib/fun_ci/jobs/locks.rb
|
|
232
|
+
- lib/fun_ci/jobs/schedule.rb
|
|
229
233
|
- lib/fun_ci/jobs/site.rb
|
|
230
234
|
- lib/fun_ci/jobs/standings.rb
|
|
231
235
|
- lib/fun_ci/jobs/state.rb
|
|
236
|
+
- lib/fun_ci/jobs/wall_clock_wait.rb
|
|
232
237
|
- lib/fun_ci/persistence/active_jobs.rb
|
|
233
238
|
- lib/fun_ci/persistence/active_runs.rb
|
|
234
239
|
- lib/fun_ci/persistence/database.rb
|
|
240
|
+
- lib/fun_ci/persistence/died.rb
|
|
235
241
|
- lib/fun_ci/persistence/job_recorder.rb
|
|
236
242
|
- lib/fun_ci/persistence/job_runs.rb
|
|
237
243
|
- lib/fun_ci/persistence/null_recorder.rb
|
|
@@ -249,14 +255,18 @@ files:
|
|
|
249
255
|
- lib/fun_ci/persistence/trunk_recording.rb
|
|
250
256
|
- lib/fun_ci/persistence/write_trouble.rb
|
|
251
257
|
- lib/fun_ci/pipeline/background_fork.rb
|
|
258
|
+
- lib/fun_ci/pipeline/budgets.rb
|
|
252
259
|
- lib/fun_ci/pipeline/command_executor.rb
|
|
253
260
|
- lib/fun_ci/pipeline/gate.rb
|
|
254
261
|
- lib/fun_ci/pipeline/git_environment.rb
|
|
262
|
+
- lib/fun_ci/pipeline/group_kill.rb
|
|
255
263
|
- lib/fun_ci/pipeline/head_part.rb
|
|
256
264
|
- lib/fun_ci/pipeline/in_place.rb
|
|
257
265
|
- lib/fun_ci/pipeline/output_window.rb
|
|
258
266
|
- lib/fun_ci/pipeline/pipeline_forker.rb
|
|
267
|
+
- lib/fun_ci/pipeline/priorities.rb
|
|
259
268
|
- lib/fun_ci/pipeline/process_runner.rb
|
|
269
|
+
- lib/fun_ci/pipeline/process_tree.rb
|
|
260
270
|
- lib/fun_ci/pipeline/progress_reporter.rb
|
|
261
271
|
- lib/fun_ci/pipeline/run_canceller.rb
|
|
262
272
|
- lib/fun_ci/pipeline/slot.rb
|
|
@@ -279,14 +289,19 @@ files:
|
|
|
279
289
|
- lib/fun_ci/pipeline/worktrees.rb
|
|
280
290
|
- lib/fun_ci/setup/agent_instructions.rb
|
|
281
291
|
- lib/fun_ci/setup/commands.rb
|
|
292
|
+
- lib/fun_ci/setup/eslint_detection.rb
|
|
282
293
|
- lib/fun_ci/setup/git_hooks.rb
|
|
283
294
|
- lib/fun_ci/setup/gradle_linter_detector.rb
|
|
284
295
|
- lib/fun_ci/setup/hook_script.rb
|
|
285
296
|
- lib/fun_ci/setup/hook_writer.rb
|
|
297
|
+
- lib/fun_ci/setup/init_options.rb
|
|
298
|
+
- lib/fun_ci/setup/installed_hooks.rb
|
|
286
299
|
- lib/fun_ci/setup/installer.rb
|
|
287
300
|
- lib/fun_ci/setup/legacy_hooks.rb
|
|
301
|
+
- lib/fun_ci/setup/lfs_hook.rb
|
|
288
302
|
- lib/fun_ci/setup/lint_detection.rb
|
|
289
303
|
- lib/fun_ci/setup/maven_linter_detector.rb
|
|
304
|
+
- lib/fun_ci/setup/placeholders.rb
|
|
290
305
|
- lib/fun_ci/setup/project_config.rb
|
|
291
306
|
- lib/fun_ci/setup/project_detector.rb
|
|
292
307
|
- lib/fun_ci/setup/settings.rb
|
|
@@ -295,6 +310,7 @@ files:
|
|
|
295
310
|
- lib/fun_ci/setup/stage_templates/javascript.rb
|
|
296
311
|
- lib/fun_ci/setup/stage_templates/managed.rb
|
|
297
312
|
- lib/fun_ci/setup/stage_templates/native.rb
|
|
313
|
+
- lib/fun_ci/setup/stage_templates/placeholders.rb
|
|
298
314
|
- lib/fun_ci/setup/stage_templates/scripting.rb
|
|
299
315
|
- lib/fun_ci/setup/stage_templates/shell.rb
|
|
300
316
|
- lib/fun_ci/setup/template_writer.rb
|