captive-stack-detector 0.10.0 → 0.12.5
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/docs/stacks.md +2 -2
- data/lib/captive_stack_detector/github_api_client.rb +11 -5
- data/lib/captive_stack_detector/github_response.rb +28 -0
- data/lib/captive_stack_detector/js_stack_detector.rb +2 -0
- data/lib/captive_stack_detector/rails_stack_detector.rb +1 -1
- data/lib/captive_stack_detector/stack_router.rb +32 -0
- data/lib/captive_stack_detector/types.rb +13 -0
- data/lib/captive_stack_detector/version.rb +1 -1
- data/lib/captive_stack_detector.rb +2 -19
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e5c9cf6e0559903b9b472715ed1d425903be96cdf7ca09c4689a66f63df7b19f
|
|
4
|
+
data.tar.gz: 7146a542cf6d2887063172c47a0d8f85251d3e7e02062969c8ee04882c321398
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d2162dfa8b2aa58308fb3523677fba79ffba3cd7abb7fa980bd2466408bc8347519845ec93bb83b8d751b308e504488c4f2edc56a2147132634ef288de5e78b1
|
|
7
|
+
data.tar.gz: 65b1daea44aa2172b74e215d3d6fd55b87e94ae6dc5115657605dee8ee880dbc5e4f0b2f04db139348cab9c34883a33d07a131e32fe5377db6256368531de625
|
data/docs/stacks.md
CHANGED
|
@@ -112,7 +112,7 @@ Ces variables sont lues **au build** : après en avoir changé une, redéployez.
|
|
|
112
112
|
|
|
113
113
|
### Site statique sans build
|
|
114
114
|
|
|
115
|
-
Condition :
|
|
115
|
+
Condition : pas de `Gemfile` à la racine, mais un `public/index.html` ou un `index.html` à la racine. Un `package.json` est accepté s'il ne sert qu'à l'outillage (Supabase CLI, prettier…) : sans dépendance `expo` ni script `start` ou `build`. Rien n'est installé ni buildé : vos fichiers sont servis tels quels.
|
|
116
116
|
|
|
117
117
|
- Si `public/` existe, seul ce dossier est servi.
|
|
118
118
|
- Sinon la racine est servie, limitée aux fichiers web (HTML, JavaScript, CSS, JSON, images, polices, manifeste, texte). Les dossiers cachés (`.git/`, `.github/`), `supabase/`, vos scripts SQL ou votre documentation ne sont jamais publiés.
|
|
@@ -150,6 +150,6 @@ Condition : un `package.json` à la racine avec la dépendance `expo` (dans `dep
|
|
|
150
150
|
| --- | --- | --- |
|
|
151
151
|
| Aucun Gemfile ni package.json à la racine du dépôt. | Pas de fichier de dépendances ni d'`index.html` à la racine. | Vérifiez que l'application est bien à la racine du dépôt, pas dans un sous-dossier (`app/`, `frontend/`…). Les monorepos ne sont pas pris en charge. |
|
|
152
152
|
| Gemfile trouvé, mais sans la gem rails. | Application Ruby non Rails (Sinatra, Hanami…), ou `Gemfile` d'outillage dans un projet JavaScript. | Seul Rails est pris en charge côté Ruby. Si le `Gemfile` ne sert qu'à de l'outillage, retirez-le du dépôt. |
|
|
153
|
-
| package.json trouvé, mais sans dépendance expo ni script start ou build. | Captive ne sait pas comment construire l'application. | Ajoutez un script `build` (application web) ou `start` (serveur), comme dans les exemples ci-dessus. |
|
|
153
|
+
| package.json trouvé, mais sans dépendance expo ni script start ou build. | Captive ne sait pas comment construire l'application, et aucun `index.html` ne permet de la servir telle quelle. | Ajoutez un script `build` (application web) ou `start` (serveur), comme dans les exemples ci-dessus, ou un `index.html` pour un site statique. |
|
|
154
154
|
|
|
155
155
|
Si votre stack n'est pas listée ici, contactez l'équipe Captive.
|
|
@@ -5,9 +5,14 @@ require "uri"
|
|
|
5
5
|
require "base64"
|
|
6
6
|
require "json"
|
|
7
7
|
require_relative "github_tree"
|
|
8
|
+
require_relative "types"
|
|
9
|
+
require_relative "github_response"
|
|
8
10
|
|
|
9
11
|
module CaptiveStackDetector
|
|
10
12
|
class GithubApiClient
|
|
13
|
+
# Un corps illisible (JSON invalide ou sans le champ attendu) vaut une absence.
|
|
14
|
+
UNREADABLE = [ JSON::ParserError, NoMethodError, TypeError ].freeze
|
|
15
|
+
|
|
11
16
|
def initialize(token, repo)
|
|
12
17
|
@token = token
|
|
13
18
|
@repo = repo
|
|
@@ -18,7 +23,7 @@ module CaptiveStackDetector
|
|
|
18
23
|
return nil unless body
|
|
19
24
|
|
|
20
25
|
Base64.decode64(JSON.parse(body)["content"]).force_encoding("utf-8")
|
|
21
|
-
rescue
|
|
26
|
+
rescue *UNREADABLE
|
|
22
27
|
nil
|
|
23
28
|
end
|
|
24
29
|
|
|
@@ -27,7 +32,7 @@ module CaptiveStackDetector
|
|
|
27
32
|
return [] unless body
|
|
28
33
|
|
|
29
34
|
GithubTree.new(body).files_in(dir)
|
|
30
|
-
rescue
|
|
35
|
+
rescue *UNREADABLE
|
|
31
36
|
[]
|
|
32
37
|
end
|
|
33
38
|
|
|
@@ -35,9 +40,10 @@ module CaptiveStackDetector
|
|
|
35
40
|
|
|
36
41
|
def get(path)
|
|
37
42
|
uri = URI("https://api.github.com/repos/#{@repo}/#{path}")
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
GithubResponse.body(Net::HTTP.get_response(uri, "Authorization" => "Bearer #{@token}",
|
|
44
|
+
"Accept" => "application/vnd.github+json"))
|
|
45
|
+
rescue SystemCallError, IOError, SocketError, Timeout::Error, OpenSSL::SSL::SSLError => e
|
|
46
|
+
raise SourceUnavailable.new(:network, detail: e.message)
|
|
41
47
|
end
|
|
42
48
|
end
|
|
43
49
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require_relative "types"
|
|
5
|
+
|
|
6
|
+
module CaptiveStackDetector
|
|
7
|
+
# Ce que veut dire une réponse de l'API GitHub : un corps, une absence, ou un refus nommé.
|
|
8
|
+
# Change avec la façon dont GitHub signale ses erreurs, pas avec ce que l'on y lit.
|
|
9
|
+
module GithubResponse
|
|
10
|
+
# 404 : fichier absent ; 409 : dépôt vide. Tout autre échec vient de GitHub, pas du dépôt.
|
|
11
|
+
ABSENT = [ "404", "409" ].freeze
|
|
12
|
+
REFUSALS = { "401" => :unauthorized, "429" => :rate_limited }.freeze
|
|
13
|
+
|
|
14
|
+
def self.body(res)
|
|
15
|
+
return res.body if res.is_a?(Net::HTTPSuccess)
|
|
16
|
+
return nil if ABSENT.include?(res.code)
|
|
17
|
+
|
|
18
|
+
raise SourceUnavailable.new(refusal_kind(res), status: res.code.to_i)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# GitHub signale un quota épuisé par un 403 ou un 429, avec x-ratelimit-remaining à 0.
|
|
22
|
+
def self.refusal_kind(res)
|
|
23
|
+
return :rate_limited if res["x-ratelimit-remaining"] == "0"
|
|
24
|
+
|
|
25
|
+
REFUSALS.fetch(res.code) { res.is_a?(Net::HTTPServerError) ? :server_error : :forbidden }
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -20,7 +20,7 @@ module CaptiveStackDetector
|
|
|
20
20
|
subtype: @analyzer.subtype,
|
|
21
21
|
services: Services.new(database: @analyzer.database, queue: @analyzer.queue, backend: nil),
|
|
22
22
|
worker: build_worker,
|
|
23
|
-
runtime: Runtime.new(ruby: @reader.ruby_version, node:
|
|
23
|
+
runtime: Runtime.new(ruby: @reader.ruby_version, node: @reader.node_version),
|
|
24
24
|
env_vars: @reader.env_vars,
|
|
25
25
|
system_packages: system_packages,
|
|
26
26
|
)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "types"
|
|
4
|
+
require_relative "gemfile_analyzer"
|
|
5
|
+
require_relative "rails_stack_detector"
|
|
6
|
+
require_relative "js_stack_detector"
|
|
7
|
+
require_relative "static_site_detector"
|
|
8
|
+
|
|
9
|
+
module CaptiveStackDetector
|
|
10
|
+
# Aiguille un dépôt vers le détecteur de sa stack, d'après ses manifestes à la racine.
|
|
11
|
+
class StackRouter
|
|
12
|
+
def initialize(reader)
|
|
13
|
+
@reader = reader
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def detect
|
|
17
|
+
gemfile = @reader.read("Gemfile")
|
|
18
|
+
package_json = @reader.read("package.json")
|
|
19
|
+
|
|
20
|
+
return RailsStackDetector.new(@reader, GemfileAnalyzer.new(gemfile)).detect if gemfile
|
|
21
|
+
|
|
22
|
+
static_site = StaticSiteDetector.new(@reader)
|
|
23
|
+
# Un package.json sans point d'entrée (Supabase CLI, prettier…) n'est que de l'outillage :
|
|
24
|
+
# un index.html présent reste servi tel quel au lieu d'un refus no_node_entry.
|
|
25
|
+
js = package_json && JsStackDetector.new(@reader, package_json)
|
|
26
|
+
return js.detect if js && (js.entry? || !static_site.site?)
|
|
27
|
+
raise UnsupportedStack, :no_manifest unless static_site.site?
|
|
28
|
+
|
|
29
|
+
static_site.detect
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -26,4 +26,17 @@ module CaptiveStackDetector
|
|
|
26
26
|
super(reason&.to_s || "unsupported stack")
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
|
+
|
|
30
|
+
# GitHub n'a pas laissé lire le dépôt, ce qui ne dit rien de sa stack. `kind` dit pourquoi,
|
|
31
|
+
# pour que l'appelant l'explique : :unauthorized (jeton refusé), :rate_limited (quota épuisé),
|
|
32
|
+
# :forbidden (accès refusé), :server_error (panne GitHub), :network (pas de réponse).
|
|
33
|
+
class SourceUnavailable < StandardError
|
|
34
|
+
attr_reader :kind, :status
|
|
35
|
+
|
|
36
|
+
def initialize(kind = :network, status: nil, detail: nil)
|
|
37
|
+
@kind = kind
|
|
38
|
+
@status = status
|
|
39
|
+
super(detail || "GitHub illisible : #{kind}#{" (#{status})" if status}")
|
|
40
|
+
end
|
|
41
|
+
end
|
|
29
42
|
end
|
|
@@ -3,10 +3,7 @@
|
|
|
3
3
|
require "json"
|
|
4
4
|
require_relative "captive_stack_detector/types"
|
|
5
5
|
require_relative "captive_stack_detector/file_reader"
|
|
6
|
-
require_relative "captive_stack_detector/
|
|
7
|
-
require_relative "captive_stack_detector/rails_stack_detector"
|
|
8
|
-
require_relative "captive_stack_detector/js_stack_detector"
|
|
9
|
-
require_relative "captive_stack_detector/static_site_detector"
|
|
6
|
+
require_relative "captive_stack_detector/stack_router"
|
|
10
7
|
require_relative "captive_stack_detector/addon_recommender"
|
|
11
8
|
|
|
12
9
|
# Déduit d'un dépôt sa stack, ses services et les options d'add-on à recommander.
|
|
@@ -20,21 +17,7 @@ module CaptiveStackDetector
|
|
|
20
17
|
end
|
|
21
18
|
|
|
22
19
|
def self.detect_from(reader)
|
|
23
|
-
AddonRecommender.new(reader).apply(
|
|
20
|
+
AddonRecommender.new(reader).apply(StackRouter.new(reader).detect)
|
|
24
21
|
end
|
|
25
22
|
private_class_method :detect_from
|
|
26
|
-
|
|
27
|
-
def self.detect_stack(reader)
|
|
28
|
-
gemfile = reader.read("Gemfile")
|
|
29
|
-
package_json = reader.read("package.json")
|
|
30
|
-
|
|
31
|
-
return RailsStackDetector.new(reader, GemfileAnalyzer.new(gemfile)).detect if gemfile
|
|
32
|
-
return JsStackDetector.new(reader, package_json).detect if package_json
|
|
33
|
-
|
|
34
|
-
static_site = StaticSiteDetector.new(reader)
|
|
35
|
-
raise UnsupportedStack, :no_manifest unless static_site.site?
|
|
36
|
-
|
|
37
|
-
static_site.detect
|
|
38
|
-
end
|
|
39
|
-
private_class_method :detect_stack
|
|
40
23
|
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.
|
|
4
|
+
version: 0.12.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Captive Studio
|
|
@@ -29,6 +29,7 @@ files:
|
|
|
29
29
|
- lib/captive_stack_detector/gem_package_map.rb
|
|
30
30
|
- lib/captive_stack_detector/gemfile_analyzer.rb
|
|
31
31
|
- lib/captive_stack_detector/github_api_client.rb
|
|
32
|
+
- lib/captive_stack_detector/github_response.rb
|
|
32
33
|
- lib/captive_stack_detector/github_tree.rb
|
|
33
34
|
- lib/captive_stack_detector/js_env_detector.rb
|
|
34
35
|
- lib/captive_stack_detector/js_env_files.rb
|
|
@@ -46,6 +47,7 @@ files:
|
|
|
46
47
|
- lib/captive_stack_detector/solid_queue_usage.rb
|
|
47
48
|
- lib/captive_stack_detector/solid_queue_worker.rb
|
|
48
49
|
- lib/captive_stack_detector/ssr_framework_detector.rb
|
|
50
|
+
- lib/captive_stack_detector/stack_router.rb
|
|
49
51
|
- lib/captive_stack_detector/static_site_detector.rb
|
|
50
52
|
- lib/captive_stack_detector/system_package_detector.rb
|
|
51
53
|
- lib/captive_stack_detector/types.rb
|