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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/exe/fun-ci-trigger +6 -6
  4. data/exe/fun-ci-tui +6 -6
  5. data/lib/fun_ci/cli.rb +17 -16
  6. data/lib/fun_ci/persistence/database.rb +52 -0
  7. data/lib/fun_ci/persistence/pipeline_recorder.rb +74 -0
  8. data/lib/fun_ci/persistence/pipeline_run.rb +59 -0
  9. data/lib/fun_ci/persistence/stage_job.rb +46 -0
  10. data/lib/fun_ci/{state_machine.rb → persistence/state_machine.rb} +3 -12
  11. data/lib/fun_ci/pipeline/background_wrapper.rb +27 -0
  12. data/lib/fun_ci/pipeline/pipeline_forker.rb +38 -0
  13. data/lib/fun_ci/pipeline/process_runner.rb +28 -0
  14. data/lib/fun_ci/pipeline/progress_reporter.rb +31 -0
  15. data/lib/fun_ci/pipeline/stage_runner.rb +85 -0
  16. data/lib/fun_ci/pipeline/stale_pipeline_canceller.rb +53 -0
  17. data/lib/fun_ci/pipeline/trigger.rb +153 -0
  18. data/lib/fun_ci/setup/hook_writer.rb +75 -0
  19. data/lib/fun_ci/setup/installer.rb +55 -0
  20. data/lib/fun_ci/setup/maven_linter_detector.rb +26 -0
  21. data/lib/fun_ci/setup/project_config.rb +42 -0
  22. data/lib/fun_ci/setup/project_detector.rb +22 -0
  23. data/lib/fun_ci/setup/setup_checker.rb +30 -0
  24. data/lib/fun_ci/setup/template_writer.rb +53 -0
  25. data/lib/fun_ci/tui/admin_tui.rb +90 -0
  26. data/lib/fun_ci/tui/animation.rb +49 -0
  27. data/lib/fun_ci/tui/animation_compositor.rb +107 -0
  28. data/lib/fun_ci/tui/animation_frames.rb +112 -0
  29. data/lib/fun_ci/tui/animation_library.rb +46 -0
  30. data/lib/fun_ci/tui/animation_renderer.rb +144 -0
  31. data/lib/fun_ci/tui/ansi.rb +34 -0
  32. data/lib/fun_ci/tui/board_data.rb +53 -0
  33. data/lib/fun_ci/tui/board_renderer.rb +105 -0
  34. data/lib/fun_ci/tui/duration_formatter.rb +24 -0
  35. data/lib/fun_ci/tui/header_animation_manager.rb +71 -0
  36. data/lib/fun_ci/tui/header_animation_player.rb +45 -0
  37. data/lib/fun_ci/tui/key_handler.rb +86 -0
  38. data/lib/fun_ci/tui/looping_animation_player.rb +45 -0
  39. data/lib/fun_ci/tui/relative_time.rb +22 -0
  40. data/lib/fun_ci/tui/row_formatter.rb +108 -0
  41. data/lib/fun_ci/tui/screen.rb +103 -0
  42. data/lib/fun_ci/tui/spinner.rb +24 -0
  43. data/lib/fun_ci/tui/stage_change_detector.rb +58 -0
  44. data/lib/fun_ci/tui/streak_counter.rb +29 -0
  45. data/lib/fun_ci/tui/terminal_input.rb +69 -0
  46. data/lib/fun_ci.rb +6 -6
  47. metadata +41 -37
  48. data/lib/fun_ci/admin_tui.rb +0 -238
  49. data/lib/fun_ci/animation.rb +0 -47
  50. data/lib/fun_ci/animation_compositor.rb +0 -105
  51. data/lib/fun_ci/animation_frames.rb +0 -111
  52. data/lib/fun_ci/animation_library.rb +0 -44
  53. data/lib/fun_ci/animation_renderer.rb +0 -142
  54. data/lib/fun_ci/ansi.rb +0 -32
  55. data/lib/fun_ci/background_wrapper.rb +0 -27
  56. data/lib/fun_ci/board_data.rb +0 -51
  57. data/lib/fun_ci/database.rb +0 -50
  58. data/lib/fun_ci/duration_formatter.rb +0 -23
  59. data/lib/fun_ci/header_animation_manager.rb +0 -69
  60. data/lib/fun_ci/header_animation_player.rb +0 -43
  61. data/lib/fun_ci/hook_writer.rb +0 -73
  62. data/lib/fun_ci/installer.rb +0 -53
  63. data/lib/fun_ci/looping_animation_player.rb +0 -43
  64. data/lib/fun_ci/maven_linter_detector.rb +0 -24
  65. data/lib/fun_ci/pipeline_forker.rb +0 -36
  66. data/lib/fun_ci/pipeline_recorder.rb +0 -72
  67. data/lib/fun_ci/pipeline_run.rb +0 -57
  68. data/lib/fun_ci/progress_reporter.rb +0 -29
  69. data/lib/fun_ci/project_config.rb +0 -40
  70. data/lib/fun_ci/project_detector.rb +0 -18
  71. data/lib/fun_ci/relative_time.rb +0 -20
  72. data/lib/fun_ci/row_formatter.rb +0 -106
  73. data/lib/fun_ci/screen.rb +0 -95
  74. data/lib/fun_ci/setup_checker.rb +0 -28
  75. data/lib/fun_ci/spinner.rb +0 -22
  76. data/lib/fun_ci/stage_change_detector.rb +0 -56
  77. data/lib/fun_ci/stage_job.rb +0 -44
  78. data/lib/fun_ci/stage_runner.rb +0 -108
  79. data/lib/fun_ci/stale_pipeline_canceller.rb +0 -51
  80. data/lib/fun_ci/streak_counter.rb +0 -30
  81. data/lib/fun_ci/template_writer.rb +0 -51
  82. 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
@@ -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
@@ -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
@@ -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
@@ -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