captive-stack-detector 0.3.3 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f08d442c931ad8580e07e66dcc9eecf1874efc3bb0efc5a83ca12168316eaccc
4
- data.tar.gz: caebf39fbe99eb829c53d42c2d54c73358a382f80e69eba3c44e01794b014ae0
3
+ metadata.gz: db3c27a80a53adecc0eaacc45336dad897fa31334c9f6da5b36422fc012e8a94
4
+ data.tar.gz: a7407996c89d2facb523558b4091d07474b8f1c45caa028e33f3e5ffa35a540c
5
5
  SHA512:
6
- metadata.gz: 6bf999ee430b24a763c7f41e7123c0ff47867cbac8686e4767c009f9be1aae58d42222bafc279987cada27bc905ea42117a0a81a36bd1c1dff3b2df643e4ec67
7
- data.tar.gz: 5edc3341ac4071a03ad9d6e1d7e65fcc5f029a10da04f26b1d344833d07b971e5d2920ea60fd26e7f2b23cf5e15eb5aa3a8b5b1bb50d3da817233a2d71acb274
6
+ metadata.gz: bf3a1c1030044bbf76c1e905a07a33edf81ec4b540aec91d443da4012242ccde8d79769acc5d69b843401ddc52556c387a6afd8087de95f22361d440971df994
7
+ data.tar.gz: 33e5ea4a5de2c91e463cb2afcb7885e1518450f698f7caf315f2c38b97b74752e134e3211e9e7ce45e35001763fd464ea878c0dd173ddf7f24cc9833392150e6
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CaptiveStackDetector
4
+ # Extrait les paquets système déclarés dans un Aptfile : un paquet par ligne,
5
+ # commentaires (`#`) et lignes vides ignorés.
6
+ module AptfileParser
7
+ def self.parse(content)
8
+ content.to_s.lines
9
+ .map { |line| line.split("#", 2).first.to_s.strip }
10
+ .reject(&:empty?)
11
+ end
12
+ end
13
+ end
@@ -28,12 +28,17 @@ module CaptiveStackDetector
28
28
  full = File.join(@path, filename)
29
29
  File.read(full, encoding: "utf-8") if File.exist?(full)
30
30
  end
31
+
32
+ def list(dir)
33
+ Dir.glob("#{dir}/**/*", base: @path).select { |entry| File.file?(File.join(@path, entry)) }.sort
34
+ end
31
35
  end
32
36
 
33
37
  class GithubFileReader
34
38
  extend Forwardable
35
39
 
36
40
  def_delegators :@parser, :ruby_version, :node_version, :env_vars
41
+ def_delegators :@client, :list
37
42
 
38
43
  def initialize(token, repo)
39
44
  @client = GithubApiClient.new(token, repo)
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CaptiveStackDetector
4
+ # Connaissance « quel gem exige quels paquets système ». Rend les paires gem → paquets
5
+ # pour les gems effectivement déclarés dans le Gemfile passé.
6
+ module GemPackageMap
7
+ MAP = {
8
+ "ruby-vips" => %w[libvips42 libvips-dev],
9
+ "mini_magick" => %w[imagemagick],
10
+ "rmagick" => %w[imagemagick],
11
+ "wkhtmltopdf-binary" => %w[], # retiré de Debian Trixie, projet upstream abandonné
12
+ "sqlite3" => %w[libsqlite3-dev],
13
+ }.freeze
14
+
15
+ def self.matches(gemfile)
16
+ content = gemfile.to_s
17
+ MAP.select { |gem, _| content.match?(/gem ['"]#{Regexp.escape(gem)}['"]/) }
18
+ end
19
+ end
20
+ end
@@ -17,6 +17,7 @@ module CaptiveStackDetector
17
17
  def subtype = ASSET_GEMS.any? { |g| gem?(g) } ? "app" : "api"
18
18
  def database = gem?("pg") ? "postgres" : nil
19
19
  def queue = REDIS_GEMS.any? { |g| gem?(g) } ? "redis" : nil
20
+ def solid_queue? = gem?("solid_queue")
20
21
 
21
22
  def worker_command(procfile)
22
23
  if procfile
@@ -4,6 +4,7 @@ require "net/http"
4
4
  require "uri"
5
5
  require "base64"
6
6
  require "json"
7
+ require_relative "github_tree"
7
8
 
8
9
  module CaptiveStackDetector
9
10
  class GithubApiClient
@@ -13,17 +14,30 @@ module CaptiveStackDetector
13
14
  end
14
15
 
15
16
  def fetch(filename)
16
- uri = URI("https://api.github.com/repos/#{@repo}/contents/#{filename}")
17
- req = Net::HTTP::Get.new(uri)
18
- req["Authorization"] = "Bearer #{@token}"
19
- req["Accept"] = "application/vnd.github+json"
17
+ body = get("contents/#{filename}")
18
+ return nil unless body
20
19
 
21
- res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |h| h.request(req) }
22
- return nil unless res.is_a?(Net::HTTPSuccess)
23
-
24
- Base64.decode64(JSON.parse(res.body)["content"]).force_encoding("utf-8")
20
+ Base64.decode64(JSON.parse(body)["content"]).force_encoding("utf-8")
25
21
  rescue StandardError
26
22
  nil
27
23
  end
24
+
25
+ def list(dir)
26
+ body = get("git/trees/HEAD?recursive=1")
27
+ return [] unless body
28
+
29
+ GithubTree.new(body).files_in(dir)
30
+ rescue StandardError
31
+ []
32
+ end
33
+
34
+ private
35
+
36
+ def get(path)
37
+ uri = URI("https://api.github.com/repos/#{@repo}/#{path}")
38
+ req = Net::HTTP::Get.new(uri, "Authorization" => "Bearer #{@token}", "Accept" => "application/vnd.github+json")
39
+ res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
40
+ res.body if res.is_a?(Net::HTTPSuccess)
41
+ end
28
42
  end
29
43
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module CaptiveStackDetector
6
+ class GithubTree
7
+ def initialize(body)
8
+ @entries = JSON.parse(body)["tree"]
9
+ end
10
+
11
+ def files_in(dir)
12
+ prefix = "#{dir}/"
13
+ @entries.filter_map { |entry| entry["path"] if entry["type"] == "blob" && entry["path"].start_with?(prefix) }
14
+ end
15
+ end
16
+ end
@@ -17,7 +17,7 @@ module CaptiveStackDetector
17
17
  Result.new(
18
18
  type: type,
19
19
  subtype: @analyzer.subtype,
20
- services: Services.new(database: @analyzer.database, queue: @analyzer.queue),
20
+ services: Services.new(database: @analyzer.database, queue: @analyzer.queue, backend: @analyzer.backend),
21
21
  worker: build_worker,
22
22
  runtime: Runtime.new(ruby: nil, node: @reader.node_version),
23
23
  env_vars: {},
@@ -21,13 +21,19 @@ module CaptiveStackDetector
21
21
  end
22
22
 
23
23
  def database
24
- deps.key?("pg") ? "postgres" : nil
24
+ return "postgres" if deps.key?("pg") || backend
25
+
26
+ nil
25
27
  end
26
28
 
27
29
  def queue
28
30
  (deps.key?("redis") || deps.key?("ioredis")) ? "redis" : nil
29
31
  end
30
32
 
33
+ def backend
34
+ (deps.key?("@supabase/supabase-js") || deps.key?("@supabase/ssr")) ? "supabase" : nil
35
+ end
36
+
31
37
  private
32
38
 
33
39
  def deps
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "../captive_stack_detector"
4
4
  require_relative "system_package_detector"
5
+ require_relative "solid_queue_worker"
5
6
 
6
7
  module CaptiveStackDetector
7
8
  class RailsStackDetector
@@ -16,7 +17,7 @@ module CaptiveStackDetector
16
17
  Result.new(
17
18
  type: "rails",
18
19
  subtype: @analyzer.subtype,
19
- services: Services.new(database: @analyzer.database, queue: @analyzer.queue),
20
+ services: Services.new(database: @analyzer.database, queue: @analyzer.queue, backend: nil),
20
21
  worker: build_worker,
21
22
  runtime: Runtime.new(ruby: @reader.ruby_version, node: nil),
22
23
  env_vars: @reader.env_vars,
@@ -27,7 +28,7 @@ module CaptiveStackDetector
27
28
  private
28
29
 
29
30
  def build_worker
30
- command = @analyzer.worker_command(@reader.read("Procfile"))
31
+ command = @analyzer.worker_command(@reader.read("Procfile")) || SolidQueueWorker.new(@reader, @analyzer).command
31
32
  command ? Worker.new(command: command) : nil
32
33
  end
33
34
 
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module CaptiveStackDetector
6
+ class RecurringSchedule
7
+ def initialize(yml)
8
+ @yml = yml
9
+ end
10
+
11
+ def task_names
12
+ environments.values.grep(Hash).flat_map(&:keys)
13
+ end
14
+
15
+ private
16
+
17
+ def environments
18
+ parsed = YAML.safe_load(@yml.to_s)
19
+ parsed.is_a?(Hash) ? parsed : {}
20
+ rescue Psych::Exception
21
+ {}
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CaptiveStackDetector
4
+ class SolidQueueUsage
5
+ DEFAULT_RECURRING_TASK = "clear_solid_queue_finished_jobs"
6
+ DEFAULT_JOB_FILE = "application_job.rb"
7
+
8
+ def initialize(recurring_task_names:, job_files:)
9
+ @recurring_task_names = recurring_task_names
10
+ @job_files = job_files
11
+ end
12
+
13
+ def used? = recurring_task? || application_job?
14
+
15
+ private
16
+
17
+ def recurring_task? = @recurring_task_names.any? { |task| task != DEFAULT_RECURRING_TASK }
18
+
19
+ def application_job?
20
+ @job_files.any? { |file| file.end_with?(".rb") && File.basename(file) != DEFAULT_JOB_FILE }
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "solid_queue_usage"
4
+ require_relative "recurring_schedule"
5
+
6
+ module CaptiveStackDetector
7
+ class SolidQueueWorker
8
+ COMMAND = "bin/jobs"
9
+
10
+ def initialize(reader, analyzer)
11
+ @reader = reader
12
+ @analyzer = analyzer
13
+ end
14
+
15
+ def command
16
+ COMMAND if @analyzer.solid_queue? && usage.used?
17
+ end
18
+
19
+ private
20
+
21
+ def usage
22
+ SolidQueueUsage.new(
23
+ recurring_task_names: RecurringSchedule.new(@reader.read("config/recurring.yml")).task_names,
24
+ job_files: @reader.list("app/jobs"),
25
+ )
26
+ end
27
+ end
28
+ end
@@ -1,15 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "aptfile_parser"
4
+ require_relative "gem_package_map"
5
+
3
6
  module CaptiveStackDetector
4
7
  class SystemPackageDetector
5
- GEM_TO_PACKAGES = {
6
- "ruby-vips" => %w[libvips42 libvips-dev],
7
- "mini_magick" => %w[imagemagick],
8
- "rmagick" => %w[imagemagick],
9
- "wkhtmltopdf-binary" => %w[], # retiré de Debian Trixie, projet upstream abandonné
10
- "sqlite3" => %w[libsqlite3-dev],
11
- }.freeze
12
-
13
8
  def initialize(gemfile:, aptfile:)
14
9
  @gemfile = gemfile.to_s
15
10
  @aptfile = aptfile.to_s
@@ -24,18 +19,16 @@ module CaptiveStackDetector
24
19
  private
25
20
 
26
21
  def from_gemfile
27
- GEM_TO_PACKAGES.each_with_object([]) do |(gem, pkgs), result|
28
- next unless @gemfile.match?(/gem ['"]#{Regexp.escape(gem)}['"]/)
29
-
22
+ GemPackageMap.matches(@gemfile).each_with_object([]) do |(gem, pkgs), result|
30
23
  $stderr.puts "[captive-stack-detector] gem '#{gem}' détecté → #{pkgs.any? ? pkgs.join(", ") : "aucun paquet"}"
31
24
  result.concat(pkgs)
32
25
  end
33
26
  end
34
27
 
35
28
  def from_aptfile
36
- packages = @aptfile.lines.map { |line| line.split("#", 2).first.to_s.strip }.reject(&:empty?)
37
- packages.each { |pkg| $stderr.puts "[captive-stack-detector] Aptfile → paquet '#{pkg}'" }
38
- packages
29
+ AptfileParser.parse(@aptfile).each do |pkg|
30
+ $stderr.puts "[captive-stack-detector] Aptfile → paquet '#{pkg}'"
31
+ end
39
32
  end
40
33
  end
41
34
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CaptiveStackDetector
4
- Services = Data.define(:database, :queue)
4
+ Services = Data.define(:database, :queue, :backend)
5
5
  Worker = Data.define(:command)
6
6
  Runtime = Data.define(:ruby, :node)
7
7
  Result = Data.define(:type, :subtype, :services, :worker, :runtime, :env_vars, :system_packages)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CaptiveStackDetector
4
- VERSION = "0.3.3"
4
+ VERSION = "0.5.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: captive-stack-detector
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Captive Studio
@@ -19,16 +19,22 @@ extra_rdoc_files: []
19
19
  files:
20
20
  - lib/captive/stack/detector.rb
21
21
  - lib/captive_stack_detector.rb
22
+ - lib/captive_stack_detector/aptfile_parser.rb
22
23
  - lib/captive_stack_detector/env_vars_scanner.rb
23
24
  - lib/captive_stack_detector/file_content_parser.rb
24
25
  - lib/captive_stack_detector/file_reader.rb
26
+ - lib/captive_stack_detector/gem_package_map.rb
25
27
  - lib/captive_stack_detector/gemfile_analyzer.rb
26
28
  - lib/captive_stack_detector/github_api_client.rb
29
+ - lib/captive_stack_detector/github_tree.rb
27
30
  - lib/captive_stack_detector/js_stack_detector.rb
28
31
  - lib/captive_stack_detector/node_version_detector.rb
29
32
  - lib/captive_stack_detector/package_json_analyzer.rb
30
33
  - lib/captive_stack_detector/rails_stack_detector.rb
34
+ - lib/captive_stack_detector/recurring_schedule.rb
31
35
  - lib/captive_stack_detector/ruby_version_detector.rb
36
+ - lib/captive_stack_detector/solid_queue_usage.rb
37
+ - lib/captive_stack_detector/solid_queue_worker.rb
32
38
  - lib/captive_stack_detector/ssr_framework_detector.rb
33
39
  - lib/captive_stack_detector/system_package_detector.rb
34
40
  - lib/captive_stack_detector/types.rb