good_pipeline 0.1.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 (117) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +16 -0
  3. data/CODE_OF_CONDUCT.md +132 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +217 -0
  6. data/Rakefile +20 -0
  7. data/app/controllers/good_pipeline/application_controller.rb +9 -0
  8. data/app/controllers/good_pipeline/frontends_controller.rb +31 -0
  9. data/app/controllers/good_pipeline/pipelines_controller.rb +57 -0
  10. data/app/frontend/good_pipeline/style.css +518 -0
  11. data/app/helpers/good_pipeline/pipelines_helper.rb +119 -0
  12. data/app/jobs/good_pipeline/pipeline_callback_job.rb +52 -0
  13. data/app/jobs/good_pipeline/pipeline_reconciliation_job.rb +10 -0
  14. data/app/jobs/good_pipeline/step_finished_job.rb +10 -0
  15. data/app/models/good_pipeline/chain_record.rb +18 -0
  16. data/app/models/good_pipeline/dependency_record.rb +23 -0
  17. data/app/models/good_pipeline/pipeline_record.rb +73 -0
  18. data/app/models/good_pipeline/step_record.rb +74 -0
  19. data/app/views/good_pipeline/pipelines/_chain_links.html.erb +30 -0
  20. data/app/views/good_pipeline/pipelines/_pagination.html.erb +24 -0
  21. data/app/views/good_pipeline/pipelines/_pipeline_row.html.erb +7 -0
  22. data/app/views/good_pipeline/pipelines/_steps_table.html.erb +33 -0
  23. data/app/views/good_pipeline/pipelines/definitions.html.erb +49 -0
  24. data/app/views/good_pipeline/pipelines/index.html.erb +43 -0
  25. data/app/views/good_pipeline/pipelines/show.html.erb +40 -0
  26. data/app/views/layouts/good_pipeline/application.html.erb +40 -0
  27. data/config/routes.rb +13 -0
  28. data/demo/Rakefile +5 -0
  29. data/demo/app/jobs/always_failing_job.rb +12 -0
  30. data/demo/app/jobs/application_job.rb +4 -0
  31. data/demo/app/jobs/cleanup_job.rb +5 -0
  32. data/demo/app/jobs/download_job.rb +5 -0
  33. data/demo/app/jobs/failing_job.rb +12 -0
  34. data/demo/app/jobs/publish_job.rb +5 -0
  35. data/demo/app/jobs/retryable_job.rb +19 -0
  36. data/demo/app/jobs/thumbnail_job.rb +5 -0
  37. data/demo/app/jobs/transcode_job.rb +5 -0
  38. data/demo/app/pipelines/analytics_pipeline.rb +7 -0
  39. data/demo/app/pipelines/archive_pipeline.rb +7 -0
  40. data/demo/app/pipelines/continue_test_pipeline.rb +11 -0
  41. data/demo/app/pipelines/halt_test_pipeline.rb +10 -0
  42. data/demo/app/pipelines/notification_pipeline.rb +7 -0
  43. data/demo/app/pipelines/test_pipeline.rb +5 -0
  44. data/demo/app/pipelines/video_processing_pipeline.rb +14 -0
  45. data/demo/bin/rails +6 -0
  46. data/demo/config/application.rb +18 -0
  47. data/demo/config/boot.rb +5 -0
  48. data/demo/config/database.yml +15 -0
  49. data/demo/config/environment.rb +5 -0
  50. data/demo/config/environments/development.rb +9 -0
  51. data/demo/config/environments/test.rb +10 -0
  52. data/demo/config/routes.rb +6 -0
  53. data/demo/config.ru +5 -0
  54. data/demo/db/migrate/20260319205325_create_good_jobs.rb +112 -0
  55. data/demo/db/migrate/20260319205326_create_good_pipeline_tables.rb +53 -0
  56. data/demo/db/seeds.rb +153 -0
  57. data/demo/test/good_pipeline/test_chain_record.rb +29 -0
  58. data/demo/test/good_pipeline/test_cleanup.rb +93 -0
  59. data/demo/test/good_pipeline/test_coordinator.rb +286 -0
  60. data/demo/test/good_pipeline/test_dependency_record.rb +46 -0
  61. data/demo/test/good_pipeline/test_failure_metadata.rb +77 -0
  62. data/demo/test/good_pipeline/test_introspection.rb +46 -0
  63. data/demo/test/good_pipeline/test_pipeline_callback_job.rb +132 -0
  64. data/demo/test/good_pipeline/test_pipeline_reconciliation_job.rb +33 -0
  65. data/demo/test/good_pipeline/test_pipeline_record.rb +183 -0
  66. data/demo/test/good_pipeline/test_runner.rb +86 -0
  67. data/demo/test/good_pipeline/test_step_finished_job.rb +37 -0
  68. data/demo/test/good_pipeline/test_step_record.rb +208 -0
  69. data/demo/test/integration/test_concurrent_fan_in.rb +109 -0
  70. data/demo/test/integration/test_end_to_end.rb +89 -0
  71. data/demo/test/integration/test_enqueue_atomicity.rb +59 -0
  72. data/demo/test/integration/test_pipeline_chaining.rb +183 -0
  73. data/demo/test/integration/test_retry_scenarios.rb +90 -0
  74. data/demo/test/integration/test_step_finished_idempotency.rb +38 -0
  75. data/demo/test/test_helper.rb +71 -0
  76. data/dev-docker-compose.yml +16 -0
  77. data/docs/.vitepress/config.mts +66 -0
  78. data/docs/.vitepress/theme/custom.css +21 -0
  79. data/docs/.vitepress/theme/index.ts +4 -0
  80. data/docs/architecture.md +184 -0
  81. data/docs/callbacks.md +66 -0
  82. data/docs/cleanup.md +45 -0
  83. data/docs/dag-validation.md +88 -0
  84. data/docs/dashboard.md +66 -0
  85. data/docs/defining-pipelines.md +167 -0
  86. data/docs/failure-strategies.md +138 -0
  87. data/docs/getting-started.md +77 -0
  88. data/docs/index.md +23 -0
  89. data/docs/introduction.md +42 -0
  90. data/docs/monitoring.md +103 -0
  91. data/docs/package-lock.json +2510 -0
  92. data/docs/package.json +11 -0
  93. data/docs/pipeline-chaining.md +104 -0
  94. data/docs/public/screenshots/definitions.png +0 -0
  95. data/docs/public/screenshots/index.png +0 -0
  96. data/docs/public/screenshots/show.png +0 -0
  97. data/docs/screenshots/definitions.png +0 -0
  98. data/docs/screenshots/index.png +0 -0
  99. data/docs/screenshots/show.png +0 -0
  100. data/lib/generators/good_pipeline/install/install_generator.rb +20 -0
  101. data/lib/generators/good_pipeline/install/templates/create_good_pipeline_tables.rb.erb +51 -0
  102. data/lib/good_pipeline/chain.rb +54 -0
  103. data/lib/good_pipeline/chain_coordinator.rb +53 -0
  104. data/lib/good_pipeline/coordinator.rb +176 -0
  105. data/lib/good_pipeline/cycle_detector.rb +36 -0
  106. data/lib/good_pipeline/engine.rb +23 -0
  107. data/lib/good_pipeline/errors.rb +11 -0
  108. data/lib/good_pipeline/failure_metadata.rb +29 -0
  109. data/lib/good_pipeline/graph_validator.rb +71 -0
  110. data/lib/good_pipeline/pipeline.rb +122 -0
  111. data/lib/good_pipeline/runner.rb +63 -0
  112. data/lib/good_pipeline/step_definition.rb +18 -0
  113. data/lib/good_pipeline/version.rb +5 -0
  114. data/lib/good_pipeline.rb +45 -0
  115. data/mise.toml +10 -0
  116. data/sig/good_pipeline.rbs +4 -0
  117. metadata +204 -0
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GoodPipeline
4
+ class Pipeline
5
+ VALID_FAILURE_STRATEGIES = %i[halt continue ignore].freeze
6
+ DSL_ATTRIBUTES = %i[description failure_strategy on_complete on_success on_failure].freeze
7
+
8
+ # --- Class-level DSL ---
9
+
10
+ class << self
11
+ def inherited(subclass)
12
+ super
13
+ DSL_ATTRIBUTES.each do |attribute|
14
+ subclass.instance_variable_set(:"@#{attribute}", instance_variable_get(:"@#{attribute}"))
15
+ end
16
+ end
17
+
18
+ def description(text = :__unset__)
19
+ return @description if text == :__unset__
20
+
21
+ @description = text
22
+ end
23
+
24
+ def failure_strategy(strategy = :__unset__)
25
+ return @failure_strategy || :halt if strategy == :__unset__
26
+
27
+ unless VALID_FAILURE_STRATEGIES.include?(strategy)
28
+ valid = VALID_FAILURE_STRATEGIES.map { |valid_strategy| ":#{valid_strategy}" }.join(", ")
29
+ raise ConfigurationError, "invalid failure strategy :#{strategy}, must be one of #{valid}"
30
+ end
31
+
32
+ @failure_strategy = strategy
33
+ end
34
+
35
+ def on_complete(method_name = :__unset__)
36
+ return @on_complete if method_name == :__unset__
37
+
38
+ @on_complete = method_name
39
+ end
40
+
41
+ def on_success(method_name = :__unset__)
42
+ return @on_success if method_name == :__unset__
43
+
44
+ @on_success = method_name
45
+ end
46
+
47
+ def on_failure(method_name = :__unset__)
48
+ return @on_failure if method_name == :__unset__
49
+
50
+ @on_failure = method_name
51
+ end
52
+
53
+ alias build new
54
+
55
+ def run(**)
56
+ instance = new(**)
57
+ pipeline_record = Runner.call(instance)
58
+ Chain.new(pipeline_record)
59
+ end
60
+
61
+ # Internal: reconstructs a minimal Pipeline for callback dispatch.
62
+ def for_callback(pipeline_record)
63
+ instance = allocate
64
+ instance.instance_variable_set(:@pipeline_record, pipeline_record)
65
+ instance.instance_variable_set(:@params, pipeline_record.params.symbolize_keys.freeze)
66
+ instance.instance_variable_set(:@step_definitions, [].freeze)
67
+ instance.instance_variable_set(:@steps_by_key, {}.freeze)
68
+ instance.instance_variable_set(:@root_steps, [].freeze)
69
+ instance
70
+ end
71
+ end
72
+
73
+ # --- Instance API ---
74
+
75
+ attr_reader :step_definitions, :steps_by_key, :root_steps, :params, :pipeline_record
76
+
77
+ def description
78
+ self.class.description
79
+ end
80
+
81
+ def failure_strategy
82
+ self.class.failure_strategy
83
+ end
84
+
85
+ def on_complete_callback = self.class.on_complete
86
+ def on_success_callback = self.class.on_success
87
+ def on_failure_callback = self.class.on_failure
88
+
89
+ def initialize(**kwargs)
90
+ @params = kwargs.freeze
91
+ @step_definitions = []
92
+ @building = true
93
+ configure(**kwargs)
94
+ GraphValidator.validate!(@step_definitions)
95
+ @step_definitions.freeze
96
+ @building = false
97
+ @steps_by_key = @step_definitions.to_h { |step| [step.key, step] }.freeze
98
+ @root_steps = @step_definitions.select { |step| step.dependencies.empty? }.freeze
99
+ freeze
100
+ end
101
+
102
+ private
103
+
104
+ def configure(**_kwargs)
105
+ raise NotImplementedError, "#{self.class} must implement #configure"
106
+ end
107
+
108
+ def run(key, job_class, with: {}, after: [], failure_strategy: nil, queue: nil, priority: nil)
109
+ raise ConfigurationError, "run can only be called inside configure" unless @building
110
+
111
+ @step_definitions << StepDefinition.new(
112
+ key: key,
113
+ job_class: job_class,
114
+ params: with,
115
+ dependencies: after,
116
+ failure_strategy: failure_strategy,
117
+ queue: queue,
118
+ priority: priority
119
+ )
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GoodPipeline
4
+ class Runner
5
+ def self.call(pipeline_instance, start: true)
6
+ new(pipeline_instance).call(start: start)
7
+ end
8
+
9
+ def initialize(pipeline_instance)
10
+ @pipeline = pipeline_instance
11
+ end
12
+
13
+ def call(start: true) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
14
+ pipeline_record = nil
15
+ step_records = {}
16
+
17
+ PipelineRecord.transaction do # rubocop:disable Metrics/BlockLength
18
+ pipeline_record = PipelineRecord.create!(
19
+ type: @pipeline.class.name,
20
+ params: @pipeline.params,
21
+ status: :pending,
22
+ on_failure_strategy: @pipeline.failure_strategy.to_s
23
+ )
24
+
25
+ @pipeline.step_definitions.each do |step_definition|
26
+ step_records[step_definition.key] = StepRecord.create!(
27
+ pipeline: pipeline_record,
28
+ key: step_definition.key.to_s,
29
+ job_class: step_definition.job_class.name,
30
+ params: step_definition.params,
31
+ on_failure_strategy: step_definition.failure_strategy&.to_s,
32
+ queue: step_definition.queue,
33
+ priority: step_definition.priority
34
+ )
35
+
36
+ step_definition.dependencies.each do |dependency_key|
37
+ DependencyRecord.create!(
38
+ pipeline: pipeline_record,
39
+ step: step_records[step_definition.key],
40
+ depends_on_step: step_records[dependency_key]
41
+ )
42
+ end
43
+ end
44
+
45
+ pipeline_batch = GoodJob::Batch.new
46
+ pipeline_batch.on_finish = "GoodPipeline::PipelineReconciliationJob"
47
+ pipeline_batch.properties = { pipeline_id: pipeline_record.id }
48
+ pipeline_batch.save
49
+ pipeline_record.update_column(:good_job_batch_id, pipeline_batch.id)
50
+
51
+ pipeline_record.transition_to!(:running) if start
52
+ end
53
+
54
+ if start
55
+ @pipeline.root_steps.each do |step_definition|
56
+ Coordinator.try_enqueue_step(step_records[step_definition.key].id)
57
+ end
58
+ end
59
+
60
+ pipeline_record
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GoodPipeline
4
+ class StepDefinition
5
+ attr_reader :key, :job_class, :params, :dependencies, :failure_strategy, :queue, :priority
6
+
7
+ def initialize(key:, job_class:, params: {}, dependencies: [], failure_strategy: nil, queue: nil, priority: nil)
8
+ @key = key
9
+ @job_class = job_class
10
+ @params = params.freeze
11
+ @dependencies = Array(dependencies).freeze
12
+ @failure_strategy = failure_strategy
13
+ @queue = queue
14
+ @priority = priority
15
+ freeze
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GoodPipeline
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "good_pipeline/version"
4
+ require_relative "good_pipeline/errors"
5
+ require_relative "good_pipeline/step_definition"
6
+ require_relative "good_pipeline/cycle_detector"
7
+ require_relative "good_pipeline/graph_validator"
8
+ require_relative "good_pipeline/pipeline"
9
+ require_relative "good_pipeline/failure_metadata"
10
+ require_relative "good_pipeline/coordinator"
11
+ require_relative "good_pipeline/chain_coordinator"
12
+ require_relative "good_pipeline/runner"
13
+ require_relative "good_pipeline/chain"
14
+ require_relative "good_pipeline/engine" if defined?(Rails::Engine)
15
+
16
+ module GoodPipeline
17
+ def self.run(*pipeline_configs)
18
+ pipeline_records = pipeline_configs.map do |config|
19
+ pipeline_class, pipeline_params = extract_pipeline_config(config)
20
+ instance = pipeline_class.build(**pipeline_params)
21
+ Runner.call(instance)
22
+ end
23
+
24
+ Chain.new(pipeline_records)
25
+ end
26
+
27
+ # Internal: parses [PipelineClass, { with: { ... } }] config format.
28
+ def self.extract_pipeline_config(config)
29
+ [config[0], config.fetch(1, {}).fetch(:with, {})]
30
+ end
31
+
32
+ def self.cleanup_preserved_pipelines(older_than:)
33
+ pipeline_ids = PipelineRecord.where(status: PipelineRecord::TERMINAL_STATUSES)
34
+ .where("updated_at < ?", older_than)
35
+ .pluck(:id)
36
+ return if pipeline_ids.empty?
37
+
38
+ DependencyRecord.where(pipeline_id: pipeline_ids).delete_all
39
+ StepRecord.where(pipeline_id: pipeline_ids).delete_all
40
+ ChainRecord.where(upstream_pipeline_id: pipeline_ids)
41
+ .or(ChainRecord.where(downstream_pipeline_id: pipeline_ids))
42
+ .delete_all
43
+ PipelineRecord.where(id: pipeline_ids).delete_all
44
+ end
45
+ end
data/mise.toml ADDED
@@ -0,0 +1,10 @@
1
+ [tools]
2
+ ruby = "3.4.7"
3
+
4
+ [tasks."docker:start"]
5
+ description = "Start Docker services using docker-compose"
6
+ run = "docker compose -f dev-docker-compose.yml up -d"
7
+
8
+ [tasks."docker:stop"]
9
+ description = "Stop Docker services using docker-compose"
10
+ run = "docker compose -f dev-docker-compose.yml down"
@@ -0,0 +1,4 @@
1
+ module GoodPipeline
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,204 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: good_pipeline
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ali Hamdi Ali Fadel
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activerecord
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '7.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: good_job
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '3.10'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '3.10'
40
+ - !ruby/object:Gem::Dependency
41
+ name: railties
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '7.1'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '7.1'
54
+ description: Define multi-step workflows as directed acyclic graphs where each step
55
+ is a GoodJob job. Handles dependency resolution, parallel execution, failure strategies,
56
+ and lifecycle callbacks.
57
+ email:
58
+ - aliosm1997@gmail.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - CHANGELOG.md
64
+ - CODE_OF_CONDUCT.md
65
+ - LICENSE.txt
66
+ - README.md
67
+ - Rakefile
68
+ - app/controllers/good_pipeline/application_controller.rb
69
+ - app/controllers/good_pipeline/frontends_controller.rb
70
+ - app/controllers/good_pipeline/pipelines_controller.rb
71
+ - app/frontend/good_pipeline/style.css
72
+ - app/helpers/good_pipeline/pipelines_helper.rb
73
+ - app/jobs/good_pipeline/pipeline_callback_job.rb
74
+ - app/jobs/good_pipeline/pipeline_reconciliation_job.rb
75
+ - app/jobs/good_pipeline/step_finished_job.rb
76
+ - app/models/good_pipeline/chain_record.rb
77
+ - app/models/good_pipeline/dependency_record.rb
78
+ - app/models/good_pipeline/pipeline_record.rb
79
+ - app/models/good_pipeline/step_record.rb
80
+ - app/views/good_pipeline/pipelines/_chain_links.html.erb
81
+ - app/views/good_pipeline/pipelines/_pagination.html.erb
82
+ - app/views/good_pipeline/pipelines/_pipeline_row.html.erb
83
+ - app/views/good_pipeline/pipelines/_steps_table.html.erb
84
+ - app/views/good_pipeline/pipelines/definitions.html.erb
85
+ - app/views/good_pipeline/pipelines/index.html.erb
86
+ - app/views/good_pipeline/pipelines/show.html.erb
87
+ - app/views/layouts/good_pipeline/application.html.erb
88
+ - config/routes.rb
89
+ - demo/Rakefile
90
+ - demo/app/jobs/always_failing_job.rb
91
+ - demo/app/jobs/application_job.rb
92
+ - demo/app/jobs/cleanup_job.rb
93
+ - demo/app/jobs/download_job.rb
94
+ - demo/app/jobs/failing_job.rb
95
+ - demo/app/jobs/publish_job.rb
96
+ - demo/app/jobs/retryable_job.rb
97
+ - demo/app/jobs/thumbnail_job.rb
98
+ - demo/app/jobs/transcode_job.rb
99
+ - demo/app/pipelines/analytics_pipeline.rb
100
+ - demo/app/pipelines/archive_pipeline.rb
101
+ - demo/app/pipelines/continue_test_pipeline.rb
102
+ - demo/app/pipelines/halt_test_pipeline.rb
103
+ - demo/app/pipelines/notification_pipeline.rb
104
+ - demo/app/pipelines/test_pipeline.rb
105
+ - demo/app/pipelines/video_processing_pipeline.rb
106
+ - demo/bin/rails
107
+ - demo/config.ru
108
+ - demo/config/application.rb
109
+ - demo/config/boot.rb
110
+ - demo/config/database.yml
111
+ - demo/config/environment.rb
112
+ - demo/config/environments/development.rb
113
+ - demo/config/environments/test.rb
114
+ - demo/config/routes.rb
115
+ - demo/db/migrate/20260319205325_create_good_jobs.rb
116
+ - demo/db/migrate/20260319205326_create_good_pipeline_tables.rb
117
+ - demo/db/seeds.rb
118
+ - demo/test/good_pipeline/test_chain_record.rb
119
+ - demo/test/good_pipeline/test_cleanup.rb
120
+ - demo/test/good_pipeline/test_coordinator.rb
121
+ - demo/test/good_pipeline/test_dependency_record.rb
122
+ - demo/test/good_pipeline/test_failure_metadata.rb
123
+ - demo/test/good_pipeline/test_introspection.rb
124
+ - demo/test/good_pipeline/test_pipeline_callback_job.rb
125
+ - demo/test/good_pipeline/test_pipeline_reconciliation_job.rb
126
+ - demo/test/good_pipeline/test_pipeline_record.rb
127
+ - demo/test/good_pipeline/test_runner.rb
128
+ - demo/test/good_pipeline/test_step_finished_job.rb
129
+ - demo/test/good_pipeline/test_step_record.rb
130
+ - demo/test/integration/test_concurrent_fan_in.rb
131
+ - demo/test/integration/test_end_to_end.rb
132
+ - demo/test/integration/test_enqueue_atomicity.rb
133
+ - demo/test/integration/test_pipeline_chaining.rb
134
+ - demo/test/integration/test_retry_scenarios.rb
135
+ - demo/test/integration/test_step_finished_idempotency.rb
136
+ - demo/test/test_helper.rb
137
+ - dev-docker-compose.yml
138
+ - docs/.vitepress/config.mts
139
+ - docs/.vitepress/theme/custom.css
140
+ - docs/.vitepress/theme/index.ts
141
+ - docs/architecture.md
142
+ - docs/callbacks.md
143
+ - docs/cleanup.md
144
+ - docs/dag-validation.md
145
+ - docs/dashboard.md
146
+ - docs/defining-pipelines.md
147
+ - docs/failure-strategies.md
148
+ - docs/getting-started.md
149
+ - docs/index.md
150
+ - docs/introduction.md
151
+ - docs/monitoring.md
152
+ - docs/package-lock.json
153
+ - docs/package.json
154
+ - docs/pipeline-chaining.md
155
+ - docs/public/screenshots/definitions.png
156
+ - docs/public/screenshots/index.png
157
+ - docs/public/screenshots/show.png
158
+ - docs/screenshots/definitions.png
159
+ - docs/screenshots/index.png
160
+ - docs/screenshots/show.png
161
+ - lib/generators/good_pipeline/install/install_generator.rb
162
+ - lib/generators/good_pipeline/install/templates/create_good_pipeline_tables.rb.erb
163
+ - lib/good_pipeline.rb
164
+ - lib/good_pipeline/chain.rb
165
+ - lib/good_pipeline/chain_coordinator.rb
166
+ - lib/good_pipeline/coordinator.rb
167
+ - lib/good_pipeline/cycle_detector.rb
168
+ - lib/good_pipeline/engine.rb
169
+ - lib/good_pipeline/errors.rb
170
+ - lib/good_pipeline/failure_metadata.rb
171
+ - lib/good_pipeline/graph_validator.rb
172
+ - lib/good_pipeline/pipeline.rb
173
+ - lib/good_pipeline/runner.rb
174
+ - lib/good_pipeline/step_definition.rb
175
+ - lib/good_pipeline/version.rb
176
+ - mise.toml
177
+ - sig/good_pipeline.rbs
178
+ homepage: https://github.com/milkstrawai/good_pipeline
179
+ licenses:
180
+ - MIT
181
+ metadata:
182
+ allowed_push_host: https://rubygems.org
183
+ homepage_uri: https://github.com/milkstrawai/good_pipeline
184
+ source_code_uri: https://github.com/milkstrawai/good_pipeline
185
+ changelog_uri: https://github.com/milkstrawai/good_pipeline/blob/main/CHANGELOG.md
186
+ rubygems_mfa_required: 'true'
187
+ rdoc_options: []
188
+ require_paths:
189
+ - lib
190
+ required_ruby_version: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: 3.2.0
195
+ required_rubygems_version: !ruby/object:Gem::Requirement
196
+ requirements:
197
+ - - ">="
198
+ - !ruby/object:Gem::Version
199
+ version: '0'
200
+ requirements: []
201
+ rubygems_version: 3.6.9
202
+ specification_version: 4
203
+ summary: DAG-based job pipeline orchestration for Rails, built on GoodJob
204
+ test_files: []