fun_ci 1.1.0 → 1.2.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 +19 -0
- data/exe/fun-ci-trigger +6 -6
- data/exe/fun-ci-tui +6 -6
- data/lib/fun_ci/cli.rb +17 -16
- data/lib/fun_ci/persistence/database.rb +52 -0
- data/lib/fun_ci/persistence/pipeline_recorder.rb +74 -0
- data/lib/fun_ci/persistence/pipeline_run.rb +59 -0
- data/lib/fun_ci/persistence/stage_job.rb +46 -0
- data/lib/fun_ci/{state_machine.rb → persistence/state_machine.rb} +3 -12
- data/lib/fun_ci/pipeline/background_wrapper.rb +27 -0
- data/lib/fun_ci/pipeline/pipeline_forker.rb +38 -0
- data/lib/fun_ci/pipeline/process_runner.rb +28 -0
- data/lib/fun_ci/pipeline/progress_reporter.rb +31 -0
- data/lib/fun_ci/pipeline/stage_runner.rb +85 -0
- data/lib/fun_ci/pipeline/stale_pipeline_canceller.rb +53 -0
- data/lib/fun_ci/pipeline/trigger.rb +153 -0
- data/lib/fun_ci/setup/hook_writer.rb +75 -0
- data/lib/fun_ci/setup/installer.rb +55 -0
- data/lib/fun_ci/setup/maven_linter_detector.rb +26 -0
- data/lib/fun_ci/setup/project_config.rb +42 -0
- data/lib/fun_ci/setup/project_detector.rb +22 -0
- data/lib/fun_ci/setup/setup_checker.rb +30 -0
- data/lib/fun_ci/setup/template_writer.rb +53 -0
- data/lib/fun_ci/tui/admin_tui.rb +90 -0
- data/lib/fun_ci/tui/animation.rb +49 -0
- data/lib/fun_ci/tui/animation_compositor.rb +107 -0
- data/lib/fun_ci/tui/animation_frames.rb +112 -0
- data/lib/fun_ci/tui/animation_library.rb +46 -0
- data/lib/fun_ci/tui/animation_renderer.rb +144 -0
- data/lib/fun_ci/tui/ansi.rb +34 -0
- data/lib/fun_ci/tui/board_data.rb +53 -0
- data/lib/fun_ci/tui/board_renderer.rb +105 -0
- data/lib/fun_ci/tui/duration_formatter.rb +24 -0
- data/lib/fun_ci/tui/header_animation_manager.rb +71 -0
- data/lib/fun_ci/tui/header_animation_player.rb +45 -0
- data/lib/fun_ci/tui/key_handler.rb +86 -0
- data/lib/fun_ci/tui/looping_animation_player.rb +45 -0
- data/lib/fun_ci/tui/relative_time.rb +22 -0
- data/lib/fun_ci/tui/row_formatter.rb +108 -0
- data/lib/fun_ci/tui/screen.rb +103 -0
- data/lib/fun_ci/tui/spinner.rb +24 -0
- data/lib/fun_ci/tui/stage_change_detector.rb +58 -0
- data/lib/fun_ci/tui/streak_counter.rb +29 -0
- data/lib/fun_ci/tui/terminal_input.rb +69 -0
- data/lib/fun_ci.rb +6 -6
- metadata +41 -37
- data/lib/fun_ci/admin_tui.rb +0 -238
- data/lib/fun_ci/animation.rb +0 -47
- data/lib/fun_ci/animation_compositor.rb +0 -105
- data/lib/fun_ci/animation_frames.rb +0 -111
- data/lib/fun_ci/animation_library.rb +0 -44
- data/lib/fun_ci/animation_renderer.rb +0 -142
- data/lib/fun_ci/ansi.rb +0 -32
- data/lib/fun_ci/background_wrapper.rb +0 -27
- data/lib/fun_ci/board_data.rb +0 -51
- data/lib/fun_ci/database.rb +0 -50
- data/lib/fun_ci/duration_formatter.rb +0 -23
- data/lib/fun_ci/header_animation_manager.rb +0 -69
- data/lib/fun_ci/header_animation_player.rb +0 -43
- data/lib/fun_ci/hook_writer.rb +0 -73
- data/lib/fun_ci/installer.rb +0 -53
- data/lib/fun_ci/looping_animation_player.rb +0 -43
- data/lib/fun_ci/maven_linter_detector.rb +0 -24
- data/lib/fun_ci/pipeline_forker.rb +0 -36
- data/lib/fun_ci/pipeline_recorder.rb +0 -72
- data/lib/fun_ci/pipeline_run.rb +0 -57
- data/lib/fun_ci/progress_reporter.rb +0 -29
- data/lib/fun_ci/project_config.rb +0 -40
- data/lib/fun_ci/project_detector.rb +0 -18
- data/lib/fun_ci/relative_time.rb +0 -20
- data/lib/fun_ci/row_formatter.rb +0 -106
- data/lib/fun_ci/screen.rb +0 -95
- data/lib/fun_ci/setup_checker.rb +0 -28
- data/lib/fun_ci/spinner.rb +0 -22
- data/lib/fun_ci/stage_change_detector.rb +0 -56
- data/lib/fun_ci/stage_job.rb +0 -44
- data/lib/fun_ci/stage_runner.rb +0 -108
- data/lib/fun_ci/stale_pipeline_canceller.rb +0 -51
- data/lib/fun_ci/streak_counter.rb +0 -30
- data/lib/fun_ci/template_writer.rb +0 -51
- data/lib/fun_ci/trigger.rb +0 -150
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "animation"
|
|
4
|
-
require_relative "animation_compositor"
|
|
5
|
-
require_relative "animation_library"
|
|
6
|
-
require_relative "header_animation_manager"
|
|
7
|
-
require_relative "stage_change_detector"
|
|
8
|
-
|
|
9
|
-
module FunCi
|
|
10
|
-
class AnimationRenderer
|
|
11
|
-
HEADER_HEIGHT = HeaderAnimationManager::HEADER_HEIGHT
|
|
12
|
-
|
|
13
|
-
def initialize(animation_library: AnimationLibrary)
|
|
14
|
-
@previous_runs = []
|
|
15
|
-
@animations = []
|
|
16
|
-
@header_manager = HeaderAnimationManager.new(animation_library: animation_library)
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def render(screen, runs)
|
|
20
|
-
detect_and_queue(runs)
|
|
21
|
-
|
|
22
|
-
screen.save_cursor
|
|
23
|
-
render_header_overlay(screen)
|
|
24
|
-
render_stage_overlays(screen, runs)
|
|
25
|
-
render_footer_overlay(screen, runs)
|
|
26
|
-
screen.restore_cursor
|
|
27
|
-
|
|
28
|
-
advance_all
|
|
29
|
-
expire_finished
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def any_active?
|
|
33
|
-
@animations.any? { |a| !a.finished? } || @header_manager.any_active_event?
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def active_count
|
|
37
|
-
@animations.count { |a| !a.finished? }
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
private
|
|
41
|
-
|
|
42
|
-
def detect_and_queue(runs)
|
|
43
|
-
changes = StageChangeDetector.detect(@previous_runs, runs)
|
|
44
|
-
@previous_runs = snapshot(runs)
|
|
45
|
-
changes.each { |change| queue_animation(change, runs) }
|
|
46
|
-
update_running_state(runs)
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def update_running_state(runs)
|
|
50
|
-
any_running = runs.any? { |r| r[:status] == "running" }
|
|
51
|
-
any_running ? @header_manager.start_running : @header_manager.stop_running
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def queue_animation(change, runs)
|
|
55
|
-
run_status = runs.find { |r| r[:id] == change.run_id }&.dig(:status)
|
|
56
|
-
|
|
57
|
-
case change.to
|
|
58
|
-
when "failed"
|
|
59
|
-
add_animation(:failure, change.run_id, change.stage)
|
|
60
|
-
@header_manager.trigger_failure
|
|
61
|
-
when "timed_out"
|
|
62
|
-
add_animation(:timeout, change.run_id, change.stage)
|
|
63
|
-
when "completed"
|
|
64
|
-
if run_status == "completed"
|
|
65
|
-
add_animation(:success, change.run_id, change.stage)
|
|
66
|
-
@header_manager.trigger_success
|
|
67
|
-
else
|
|
68
|
-
add_animation(:stage_pass, change.run_id, change.stage)
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def add_animation(type, run_id, stage)
|
|
74
|
-
@animations.reject! do |a|
|
|
75
|
-
a.type == type && a.run_id == run_id && a.stage == stage
|
|
76
|
-
end
|
|
77
|
-
@animations << Animation.new(type: type, run_id: run_id, stage: stage)
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def render_header_overlay(screen)
|
|
81
|
-
@header_manager.current_lines(screen.width).each_with_index do |line, i|
|
|
82
|
-
screen.write_at(1 + i, 1, line)
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def render_footer_overlay(screen, runs)
|
|
87
|
-
anim = highest_priority(:has_footer?)
|
|
88
|
-
return unless anim
|
|
89
|
-
|
|
90
|
-
footer_row = HEADER_HEIGHT + (runs.length * 2) + 1
|
|
91
|
-
overlay = AnimationCompositor.footer_overlay(anim, screen.width)
|
|
92
|
-
screen.write_at(footer_row, 1, "#{overlay}\e[K") if overlay
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def render_stage_overlays(screen, runs)
|
|
96
|
-
@animations.each do |anim|
|
|
97
|
-
next if anim.finished?
|
|
98
|
-
|
|
99
|
-
run_index = runs.index { |r| r[:id] == anim.run_id }
|
|
100
|
-
next unless run_index
|
|
101
|
-
|
|
102
|
-
row = HEADER_HEIGHT + 1 + (run_index * 2)
|
|
103
|
-
run = runs[run_index]
|
|
104
|
-
text = AnimationCompositor.stage_text_for(run, anim.stage)
|
|
105
|
-
next unless text
|
|
106
|
-
|
|
107
|
-
overlay = AnimationCompositor.stage_column_overlay(anim, text, 0)
|
|
108
|
-
next unless overlay
|
|
109
|
-
|
|
110
|
-
col = AnimationCompositor.stage_col_for(run, anim.stage)
|
|
111
|
-
screen.write_at(row, col, overlay) if col
|
|
112
|
-
end
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def highest_priority(predicate)
|
|
116
|
-
@animations
|
|
117
|
-
.select { |a| a.send(predicate) && !a.finished? }
|
|
118
|
-
.max_by(&:priority)
|
|
119
|
-
end
|
|
120
|
-
|
|
121
|
-
def advance_all
|
|
122
|
-
@animations.each { |a| a.advance! unless a.finished? }
|
|
123
|
-
@header_manager.advance!
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def expire_finished
|
|
127
|
-
@animations.reject!(&:finished?)
|
|
128
|
-
@header_manager.expire_if_finished!
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
def snapshot(runs)
|
|
132
|
-
runs.map do |r|
|
|
133
|
-
{
|
|
134
|
-
id: r[:id],
|
|
135
|
-
stages: (r[:stages] || []).map do |s|
|
|
136
|
-
{ stage: s[:stage], status: s[:status] }
|
|
137
|
-
end
|
|
138
|
-
}
|
|
139
|
-
end
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
end
|
data/lib/fun_ci/ansi.rb
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module FunCi
|
|
4
|
-
module Ansi
|
|
5
|
-
RESET = "\e[0m"
|
|
6
|
-
|
|
7
|
-
def self.green(text) = "\e[32m#{text}#{RESET}"
|
|
8
|
-
def self.bold_green(text) = "\e[1;32m#{text}#{RESET}"
|
|
9
|
-
def self.bold_red(text) = "\e[1;31m#{text}#{RESET}"
|
|
10
|
-
def self.bold_yellow(text) = "\e[1;33m#{text}#{RESET}"
|
|
11
|
-
def self.cyan(text) = "\e[36m#{text}#{RESET}"
|
|
12
|
-
def self.bold_cyan(text) = "\e[1;36m#{text}#{RESET}"
|
|
13
|
-
def self.dim(text) = "\e[2m#{text}#{RESET}"
|
|
14
|
-
def self.bold_white(text) = "\e[1;37m#{text}#{RESET}"
|
|
15
|
-
def self.white(text) = "\e[37m#{text}#{RESET}"
|
|
16
|
-
def self.yellow(text) = "\e[33m#{text}#{RESET}"
|
|
17
|
-
def self.dim_green(text) = "\e[2;32m#{text}#{RESET}"
|
|
18
|
-
def self.dim_red(text) = "\e[2;31m#{text}#{RESET}"
|
|
19
|
-
def self.orange(text) = "\e[38;5;208m#{text}#{RESET}"
|
|
20
|
-
def self.dark_red(text) = "\e[38;5;52m#{text}#{RESET}"
|
|
21
|
-
def self.bg_charcoal(text) = "\e[48;5;236m#{text}#{RESET}"
|
|
22
|
-
def self.bg_dark_red(text) = "\e[48;5;124m#{text}#{RESET}"
|
|
23
|
-
def self.bg_orange(text) = "\e[48;5;166m#{text}#{RESET}"
|
|
24
|
-
def self.bg_very_dark_red(text) = "\e[48;5;52m#{text}#{RESET}"
|
|
25
|
-
def self.bg_dark_green(text) = "\e[48;5;22m#{text}#{RESET}"
|
|
26
|
-
def self.bg_dark_yellow(text) = "\e[48;5;58m#{text}#{RESET}"
|
|
27
|
-
|
|
28
|
-
def self.strip(text)
|
|
29
|
-
text.gsub(/\e\[[0-9;]*[A-Za-z]/, "")
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "timeout"
|
|
4
|
-
|
|
5
|
-
module FunCi
|
|
6
|
-
class BackgroundWrapper
|
|
7
|
-
def initialize(recorder:, job_id:, executor:)
|
|
8
|
-
@recorder = recorder
|
|
9
|
-
@job_id = job_id
|
|
10
|
-
@executor = executor
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def run
|
|
14
|
-
_output, status = @executor.call
|
|
15
|
-
if status.success?
|
|
16
|
-
@recorder.end_stage(@job_id, "completed")
|
|
17
|
-
@recorder.complete_run
|
|
18
|
-
else
|
|
19
|
-
@recorder.end_stage(@job_id, "failed")
|
|
20
|
-
@recorder.fail_run
|
|
21
|
-
end
|
|
22
|
-
rescue Timeout::Error
|
|
23
|
-
@recorder.end_stage(@job_id, "timed_out")
|
|
24
|
-
@recorder.fail_run
|
|
25
|
-
end
|
|
26
|
-
end
|
|
27
|
-
end
|
data/lib/fun_ci/board_data.rb
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "time"
|
|
4
|
-
require_relative "pipeline_run"
|
|
5
|
-
require_relative "stage_job"
|
|
6
|
-
require_relative "streak_counter"
|
|
7
|
-
|
|
8
|
-
module FunCi
|
|
9
|
-
class BoardData
|
|
10
|
-
def initialize(db, limit: 15, page_size: nil)
|
|
11
|
-
@db = db
|
|
12
|
-
@page_size = page_size || limit
|
|
13
|
-
@limit = @page_size
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def load_more
|
|
17
|
-
@limit += @page_size
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def runs
|
|
21
|
-
pipeline_runs = PipelineRun.recent(@db, limit: @limit)
|
|
22
|
-
pipeline_runs.map { |run| enrich_with_stages(run) }
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def streak
|
|
26
|
-
pipeline_runs = PipelineRun.recent(@db, limit: @limit)
|
|
27
|
-
StreakCounter.count(pipeline_runs)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def cancel_run(run_id)
|
|
31
|
-
PipelineRun.update_status(@db, run_id, "cancelled")
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
private
|
|
35
|
-
|
|
36
|
-
def enrich_with_stages(run)
|
|
37
|
-
rows = @db.execute(
|
|
38
|
-
"SELECT id, pipeline_run_id, stage, status, started_at, completed_at FROM stage_jobs WHERE pipeline_run_id = ? ORDER BY id",
|
|
39
|
-
[run[:id]]
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
stages = rows.map do |row|
|
|
43
|
-
job = { id: row[0], pipeline_run_id: row[1], stage: row[2], status: row[3], started_at: row[4], completed_at: row[5] }
|
|
44
|
-
duration = StageJob.elapsed_duration(job)
|
|
45
|
-
{ stage: job[:stage], status: job[:status], duration: duration, started_at: job[:started_at] }
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
run.merge(stages: stages)
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|
data/lib/fun_ci/database.rb
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "sqlite3"
|
|
4
|
-
|
|
5
|
-
module FunCi
|
|
6
|
-
module Database
|
|
7
|
-
def self.connection(db_path)
|
|
8
|
-
db = SQLite3::Database.new(db_path)
|
|
9
|
-
db.busy_timeout = 5000
|
|
10
|
-
db.execute("PRAGMA journal_mode=WAL")
|
|
11
|
-
db.execute("PRAGMA foreign_keys=ON")
|
|
12
|
-
db
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def self.migrate!(db)
|
|
16
|
-
db.execute(<<~SQL)
|
|
17
|
-
CREATE TABLE IF NOT EXISTS pipeline_runs (
|
|
18
|
-
id INTEGER PRIMARY KEY,
|
|
19
|
-
commit_hash TEXT,
|
|
20
|
-
branch TEXT,
|
|
21
|
-
status TEXT DEFAULT 'scheduled',
|
|
22
|
-
pid INTEGER,
|
|
23
|
-
created_at TEXT,
|
|
24
|
-
updated_at TEXT
|
|
25
|
-
)
|
|
26
|
-
SQL
|
|
27
|
-
|
|
28
|
-
add_column_if_missing(db, "pipeline_runs", "pid", "INTEGER")
|
|
29
|
-
add_column_if_missing(db, "pipeline_runs", "project_path", "TEXT")
|
|
30
|
-
|
|
31
|
-
db.execute(<<~SQL)
|
|
32
|
-
CREATE TABLE IF NOT EXISTS stage_jobs (
|
|
33
|
-
id INTEGER PRIMARY KEY,
|
|
34
|
-
pipeline_run_id INTEGER REFERENCES pipeline_runs(id),
|
|
35
|
-
stage TEXT,
|
|
36
|
-
status TEXT DEFAULT 'scheduled',
|
|
37
|
-
started_at TEXT,
|
|
38
|
-
completed_at TEXT
|
|
39
|
-
)
|
|
40
|
-
SQL
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
def self.add_column_if_missing(db, table, column, type)
|
|
44
|
-
columns = db.execute("PRAGMA table_info(#{table})").map { |row| row[1] }
|
|
45
|
-
return if columns.include?(column)
|
|
46
|
-
db.execute("ALTER TABLE #{table} ADD COLUMN #{column} #{type}")
|
|
47
|
-
end
|
|
48
|
-
private_class_method :add_column_if_missing
|
|
49
|
-
end
|
|
50
|
-
end
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module FunCi
|
|
4
|
-
module DurationFormatter
|
|
5
|
-
def self.format(seconds)
|
|
6
|
-
if seconds >= 60
|
|
7
|
-
mins = (seconds / 60).to_i
|
|
8
|
-
secs = (seconds % 60).to_i
|
|
9
|
-
"#{mins}m#{secs.to_s.rjust(2, "0")}"
|
|
10
|
-
elsif seconds == seconds.to_i
|
|
11
|
-
"#{seconds.to_i}s"
|
|
12
|
-
else
|
|
13
|
-
"#{format_decimal(seconds)}s"
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def self.format_decimal(value)
|
|
18
|
-
# Show one decimal place, remove trailing zeros
|
|
19
|
-
sprintf("%.1f", value)
|
|
20
|
-
end
|
|
21
|
-
private_class_method :format_decimal
|
|
22
|
-
end
|
|
23
|
-
end
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "header_animation_player"
|
|
4
|
-
require_relative "looping_animation_player"
|
|
5
|
-
|
|
6
|
-
module FunCi
|
|
7
|
-
class HeaderAnimationManager
|
|
8
|
-
HEADER_HEIGHT = 14
|
|
9
|
-
|
|
10
|
-
def initialize(animation_library:)
|
|
11
|
-
@idle_player = LoopingAnimationPlayer.new(animation_library.idle)
|
|
12
|
-
@running_player = nil
|
|
13
|
-
@header_player = nil
|
|
14
|
-
@animation_library = animation_library
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def trigger_failure
|
|
18
|
-
@header_player = HeaderAnimationPlayer.new(@animation_library.random_failure)
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def trigger_success
|
|
22
|
-
@header_player = HeaderAnimationPlayer.new(@animation_library.random_success)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def start_running
|
|
26
|
-
@running_player ||= LoopingAnimationPlayer.new(@animation_library.running)
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def stop_running
|
|
30
|
-
@running_player = nil
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def current_lines(width)
|
|
34
|
-
lines = active_player.current_lines(width)
|
|
35
|
-
pad_to_height(lines, HEADER_HEIGHT)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def advance!
|
|
39
|
-
@idle_player.advance!
|
|
40
|
-
@running_player&.advance!
|
|
41
|
-
@header_player&.advance!
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def expire_if_finished!
|
|
45
|
-
@header_player = nil if @header_player&.finished?
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def any_active_event?
|
|
49
|
-
@header_player && !@header_player.finished?
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
private
|
|
53
|
-
|
|
54
|
-
def active_player
|
|
55
|
-
return @header_player if any_active_event?
|
|
56
|
-
return @running_player if @running_player
|
|
57
|
-
|
|
58
|
-
@idle_player
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def pad_to_height(lines, height)
|
|
62
|
-
return lines if lines.length >= height
|
|
63
|
-
|
|
64
|
-
top_pad = (height - lines.length) / 2
|
|
65
|
-
blank = "\e[K"
|
|
66
|
-
Array.new(top_pad, blank) + lines + Array.new(height - lines.length - top_pad, blank)
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
end
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "ansi"
|
|
4
|
-
|
|
5
|
-
module FunCi
|
|
6
|
-
class HeaderAnimationPlayer
|
|
7
|
-
attr_reader :frame
|
|
8
|
-
|
|
9
|
-
def initialize(data)
|
|
10
|
-
@data = data
|
|
11
|
-
@frame = 0
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def advance!
|
|
15
|
-
@frame += 1
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def finished?
|
|
19
|
-
@frame >= total_frames
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def total_frames
|
|
23
|
-
@data[:frames].length
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def current_lines(width)
|
|
27
|
-
return [] if finished?
|
|
28
|
-
|
|
29
|
-
frame_lines = @data[:frames][@frame]
|
|
30
|
-
return [] unless frame_lines
|
|
31
|
-
|
|
32
|
-
frame_lines.map { |line| center_line(line, width) }
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
def center_line(line, width)
|
|
38
|
-
visible_len = Ansi.strip(line).length
|
|
39
|
-
pad = [(width - visible_len) / 2, 0].max
|
|
40
|
-
"#{" " * pad}#{line}\e[K"
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
data/lib/fun_ci/hook_writer.rb
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "fileutils"
|
|
4
|
-
|
|
5
|
-
module FunCi
|
|
6
|
-
class HookWriter
|
|
7
|
-
ALLOWED_HOOKS = %w[pre-commit pre-push].freeze
|
|
8
|
-
MARKER = "# fun-ci-managed-hook"
|
|
9
|
-
|
|
10
|
-
HOOK_COMMANDS = {
|
|
11
|
-
"pre-commit" => "fun-ci trigger --no-validate",
|
|
12
|
-
"pre-push" => "fun-ci trigger"
|
|
13
|
-
}.freeze
|
|
14
|
-
|
|
15
|
-
HOOK_TEMPLATE = <<~SH
|
|
16
|
-
#!/bin/sh
|
|
17
|
-
#{MARKER}
|
|
18
|
-
COMMIT=$(git rev-parse HEAD 2>/dev/null || echo "0000000000000000000000000000000000000000")
|
|
19
|
-
BRANCH=$(git branch --show-current 2>/dev/null || echo "unknown")
|
|
20
|
-
%<command>s "$COMMIT" "$BRANCH"
|
|
21
|
-
SH
|
|
22
|
-
|
|
23
|
-
def self.run(project_root:, hook_type:, stdout: $stdout)
|
|
24
|
-
new(project_root: project_root, hook_type: hook_type, stdout: stdout).run
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def initialize(project_root:, hook_type:, stdout:)
|
|
28
|
-
@project_root = project_root
|
|
29
|
-
@hook_type = hook_type
|
|
30
|
-
@stdout = stdout
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
def run
|
|
34
|
-
return reject("Not a git repository — no .git/ found.") unless git_repo?
|
|
35
|
-
return reject("Unknown hook type: #{@hook_type}") unless ALLOWED_HOOKS.include?(@hook_type)
|
|
36
|
-
return skip("Hook #{@hook_type} already exists from another tool — skipping.") if foreign_hook?
|
|
37
|
-
|
|
38
|
-
write_hook
|
|
39
|
-
@stdout.puts "Installed #{@hook_type} hook."
|
|
40
|
-
0
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
private
|
|
44
|
-
|
|
45
|
-
def git_repo?
|
|
46
|
-
Dir.exist?(File.join(@project_root, ".git"))
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def hook_path
|
|
50
|
-
File.join(@project_root, ".git", "hooks", @hook_type)
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def foreign_hook?
|
|
54
|
-
File.exist?(hook_path) && !File.read(hook_path).include?(MARKER)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def write_hook
|
|
58
|
-
FileUtils.mkdir_p(File.join(@project_root, ".git", "hooks"))
|
|
59
|
-
File.write(hook_path, format(HOOK_TEMPLATE, command: HOOK_COMMANDS[@hook_type]))
|
|
60
|
-
File.chmod(0o755, hook_path)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def reject(message)
|
|
64
|
-
@stdout.puts message
|
|
65
|
-
1
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def skip(message)
|
|
69
|
-
@stdout.puts message
|
|
70
|
-
0
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
end
|
data/lib/fun_ci/installer.rb
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "project_detector"
|
|
4
|
-
require_relative "template_writer"
|
|
5
|
-
require_relative "maven_linter_detector"
|
|
6
|
-
|
|
7
|
-
module FunCi
|
|
8
|
-
class Installer
|
|
9
|
-
def self.run(project_root:, stdout: $stdout, pom_reader: nil)
|
|
10
|
-
new(project_root: project_root, stdout: stdout, pom_reader: pom_reader).run
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def initialize(project_root:, stdout:, pom_reader: nil)
|
|
14
|
-
@project_root = project_root
|
|
15
|
-
@stdout = stdout
|
|
16
|
-
@pom_reader = pom_reader || ->(path) { File.read(path) }
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def run
|
|
20
|
-
if Dir.exist?(File.join(@project_root, ".fun-ci"))
|
|
21
|
-
@stdout.puts ".fun-ci/ already exists — skipping init."
|
|
22
|
-
return 0
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
filenames = Dir.children(@project_root)
|
|
26
|
-
detected = ProjectDetector.new(filenames).detect
|
|
27
|
-
|
|
28
|
-
if detected == :unknown
|
|
29
|
-
@stdout.puts "Could not detect project type. Create .fun-ci/ manually."
|
|
30
|
-
return 1
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
@stdout.puts "Detected: #{detected.to_s.tr("_", " ")}"
|
|
34
|
-
|
|
35
|
-
lint_override = detect_maven_linter(detected)
|
|
36
|
-
TemplateWriter.new(detected, @project_root, lint_override: lint_override).write
|
|
37
|
-
|
|
38
|
-
@stdout.puts "Created .fun-ci/ with template scripts."
|
|
39
|
-
0
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
private
|
|
43
|
-
|
|
44
|
-
def detect_maven_linter(detected)
|
|
45
|
-
return nil unless detected == :jvm_maven
|
|
46
|
-
|
|
47
|
-
pom_path = File.join(@project_root, "pom.xml")
|
|
48
|
-
pom_content = @pom_reader.call(pom_path)
|
|
49
|
-
command = MavenLinterDetector.new(pom_content).lint_command
|
|
50
|
-
command == MavenLinterDetector::DEFAULT_COMMAND ? nil : command
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
end
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "ansi"
|
|
4
|
-
|
|
5
|
-
module FunCi
|
|
6
|
-
class LoopingAnimationPlayer
|
|
7
|
-
def initialize(data)
|
|
8
|
-
@data = data
|
|
9
|
-
@frame = 0
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def advance!
|
|
13
|
-
@frame += 1
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def frame
|
|
17
|
-
@frame % total_frames
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def finished?
|
|
21
|
-
false
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
def total_frames
|
|
25
|
-
@data[:frames].length
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def current_lines(width)
|
|
29
|
-
frame_lines = @data[:frames][frame]
|
|
30
|
-
return [] unless frame_lines
|
|
31
|
-
|
|
32
|
-
frame_lines.map { |line| center_line(line, width) }
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
private
|
|
36
|
-
|
|
37
|
-
def center_line(line, width)
|
|
38
|
-
visible_len = Ansi.strip(line).length
|
|
39
|
-
pad = [(width - visible_len) / 2, 0].max
|
|
40
|
-
"#{" " * pad}#{line}\e[K"
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
end
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module FunCi
|
|
4
|
-
class MavenLinterDetector
|
|
5
|
-
LINTERS = [
|
|
6
|
-
{ artifact_id: "detekt-maven-plugin", command: "mvn detekt:check" },
|
|
7
|
-
{ artifact_id: "ktlint-maven-plugin", command: "mvn ktlint:check" },
|
|
8
|
-
{ artifact_id: "maven-checkstyle-plugin", command: "mvn checkstyle:check" },
|
|
9
|
-
{ artifact_id: "spotbugs-maven-plugin", command: "mvn spotbugs:check" },
|
|
10
|
-
{ artifact_id: "maven-pmd-plugin", command: "mvn pmd:check" }
|
|
11
|
-
].freeze
|
|
12
|
-
|
|
13
|
-
DEFAULT_COMMAND = "mvn verify -DskipTests"
|
|
14
|
-
|
|
15
|
-
def initialize(pom_content)
|
|
16
|
-
@pom_content = pom_content
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def lint_command
|
|
20
|
-
match = LINTERS.find { |linter| @pom_content.include?(linter[:artifact_id]) }
|
|
21
|
-
match ? match[:command] : DEFAULT_COMMAND
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "database"
|
|
4
|
-
require_relative "pipeline_recorder"
|
|
5
|
-
require_relative "background_wrapper"
|
|
6
|
-
|
|
7
|
-
module FunCi
|
|
8
|
-
class PipelineForker
|
|
9
|
-
def self.fork_pipeline(commit_hash:, branch:, db_path:)
|
|
10
|
-
pid = fork do
|
|
11
|
-
run_in_child(commit_hash: commit_hash, branch: branch, db_path: db_path)
|
|
12
|
-
end
|
|
13
|
-
Process.detach(pid)
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def self.run_in_child(commit_hash:, branch:, db_path:)
|
|
17
|
-
db = Database.connection(db_path)
|
|
18
|
-
recorder = DbRecorder.new(db)
|
|
19
|
-
Trigger.new(
|
|
20
|
-
project_root: Dir.pwd,
|
|
21
|
-
commit_hash: commit_hash,
|
|
22
|
-
branch: branch,
|
|
23
|
-
stdout: File.open(File::NULL, "w"),
|
|
24
|
-
recorder: recorder,
|
|
25
|
-
background_launcher: method(:sync_launcher)
|
|
26
|
-
).run
|
|
27
|
-
recorder.close
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def self.sync_launcher(db_path:, pipeline_run_id:, job_id:, executor:)
|
|
31
|
-
recorder = DbRecorder.for_background(db_path, pipeline_run_id)
|
|
32
|
-
BackgroundWrapper.new(recorder: recorder, job_id: job_id, executor: executor).run
|
|
33
|
-
recorder.close
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|