captive-stack-detector 0.9.1 → 0.10.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/docs/stacks.md +14 -1
- data/lib/captive_stack_detector/addon_recommender.rb +33 -0
- data/lib/captive_stack_detector/recommendation_rule.rb +15 -0
- data/lib/captive_stack_detector/repo_sources.rb +23 -0
- data/lib/captive_stack_detector/static_site_detector.rb +5 -6
- data/lib/captive_stack_detector/types.rb +4 -2
- data/lib/captive_stack_detector/version.rb +1 -1
- data/lib/captive_stack_detector.rb +8 -1
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5275b5b8c6fc70aec72f1ab4de5c4301c93e8e269908d93fc8db6d7735f99540
|
|
4
|
+
data.tar.gz: e8ba22932d1a222f38f5b6d569c32d772af28930ccfc8c9967c916db9ac14152
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 18fd7d008bb7eba3e0f9b6a1efc3baead566d0cb3b676b49126776ec6112692a43334bdc199bb59c883d28aac67abf5e54515077bc79f4a9c265b7d36b57989f
|
|
7
|
+
data.tar.gz: be421c440ddee1fecdeeacec913e83cbdf2cdd23094ce1fbae24f9450bf02507a4d701746c389b912ed31b95ba7fc4483170e9eb4c586a0ce1db8b6167385500
|
data/docs/stacks.md
CHANGED
|
@@ -116,9 +116,22 @@ Condition : ni `Gemfile` ni `package.json` à la racine, mais un `public/index.h
|
|
|
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.
|
|
119
|
-
- Un dossier `supabase/` avec son `config.toml` provisionne un backend Supabase.
|
|
119
|
+
- Un dossier `supabase/` avec son `config.toml` provisionne un backend Supabase et sa base Postgres.
|
|
120
120
|
- Faute de build, aucune variable n'est injectée : votre code lit directement l'adresse de l'API, par exemple `location.origin`.
|
|
121
121
|
|
|
122
|
+
## Options Supabase recommandées
|
|
123
|
+
|
|
124
|
+
Avec un backend Supabase (application Node ou site statique), Captive lit votre code et vous recommande les options de l'add-on dont il a besoin, chacune avec sa raison (par exemple « votre code appelle signUp »). Une recommandation ne s'active pas seule : vous la retrouvez dans le dashboard au moment de la création.
|
|
125
|
+
|
|
126
|
+
| Option | Recommandée quand |
|
|
127
|
+
| --- | --- |
|
|
128
|
+
| `smtp` | votre JavaScript ou TypeScript appelle `signUp`, `resetPasswordForEmail`, `signInWithOtp`, `inviteUserByEmail` ou `updateUser({ email })` : Supabase doit envoyer un email. Une connexion par `signInWithPassword` ou la double authentification (`auth.mfa`) n'en envoie pas. |
|
|
129
|
+
| `realtime` | votre code s'abonne à un canal (`.channel(`) ou aux changements de la base (`postgres_changes`). |
|
|
130
|
+
| `pg_cron` | un fichier `.sql` du dépôt (dont `supabase/migrations/` et `db/`) appelle `cron.schedule`, ou `net.http_` (pg_net est livré avec pg_cron). |
|
|
131
|
+
| `vault` | un fichier `.sql` utilise le coffre Supabase (`vault.create_secret`, `vault.decrypted_secrets`…). |
|
|
132
|
+
|
|
133
|
+
Les dépendances (`node_modules/`), sorties de build, tests et fichiers vendored sont ignorés, comme pour les variables. L'analyse est statique : un appel construit dynamiquement n'est pas vu ; activez alors l'option vous-même.
|
|
134
|
+
|
|
122
135
|
## Expo
|
|
123
136
|
|
|
124
137
|
Condition : un `package.json` à la racine avec la dépendance `expo` (dans `dependencies` ou `devDependencies`). Aucun script n'est exigé.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "recommendation_rule"
|
|
4
|
+
require_relative "repo_sources"
|
|
5
|
+
|
|
6
|
+
module CaptiveStackDetector
|
|
7
|
+
# Propose les options de l'add-on Supabase que le code du dépôt utilise, chacune avec sa raison.
|
|
8
|
+
class AddonRecommender
|
|
9
|
+
# Appels d'auth qui envoient un email ; signInWithPassword et auth.mfa n'en envoient pas.
|
|
10
|
+
AUTH_EMAILS = /
|
|
11
|
+
\b(?<call>signUp|resetPasswordForEmail|signInWithOtp|inviteUserByEmail)\(
|
|
12
|
+
| \b(?<call>updateUser)\(\s*\{\s*email\b
|
|
13
|
+
/x
|
|
14
|
+
RULES = [
|
|
15
|
+
RecommendationRule.new("smtp", :js, AUTH_EMAILS),
|
|
16
|
+
RecommendationRule.new("realtime", :js, /\.(?<call>channel)\(|(?<call>postgres_changes)/),
|
|
17
|
+
RecommendationRule.new("pg_cron", :sql, /\b(?<call>cron\.schedule|net\.http_)/),
|
|
18
|
+
RecommendationRule.new("vault", :sql, /\b(?<call>vault)\./),
|
|
19
|
+
].freeze
|
|
20
|
+
|
|
21
|
+
def initialize(reader)
|
|
22
|
+
@reader = reader
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Seul Supabase a des options : les autres stacks gardent recommendations vide, sans lire les sources.
|
|
26
|
+
def apply(result)
|
|
27
|
+
return result unless result.services.backend == "supabase"
|
|
28
|
+
|
|
29
|
+
sources = RepoSources.new(@reader)
|
|
30
|
+
result.with(recommendations: RULES.filter_map { |rule| rule.recommend(sources) })
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "types"
|
|
4
|
+
|
|
5
|
+
module CaptiveStackDetector
|
|
6
|
+
# Une option d'add-on et les appels qui la justifient ; le groupe nommé call est l'appel cité dans la raison.
|
|
7
|
+
class RecommendationRule < Data.define(:option, :kind, :pattern)
|
|
8
|
+
LABELS = { js: "votre code", sql: "votre SQL" }.freeze
|
|
9
|
+
|
|
10
|
+
def recommend(sources)
|
|
11
|
+
call = pattern.match(sources.of(kind))&.[](:call)
|
|
12
|
+
call && Recommendation.new(addon: "supabase", option: option, reason: "#{LABELS.fetch(kind)} appelle #{call}")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "js_env_files"
|
|
4
|
+
|
|
5
|
+
module CaptiveStackDetector
|
|
6
|
+
# Contenu des sources JS et SQL du dépôt, hors dépendances, builds et tests (mêmes exclusions que JsEnvFiles).
|
|
7
|
+
class RepoSources
|
|
8
|
+
def self.kind(path)
|
|
9
|
+
return nil if JsEnvFiles::EXCLUDED.match?(path)
|
|
10
|
+
return :sql if path.end_with?(".sql")
|
|
11
|
+
|
|
12
|
+
:js if JsEnvFiles::SOURCE.match?(path)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(reader)
|
|
16
|
+
@reader = reader
|
|
17
|
+
@paths = reader.list(".").sort.map { |entry| entry.delete_prefix("./") }.group_by { |path| self.class.kind(path) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Tout le code d'un genre (:js ou :sql), fichier par fichier, en un seul texte.
|
|
21
|
+
def of(kind) = @paths.fetch(kind, []).map { |path| @reader.read(path).to_s }.join("\n")
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -4,10 +4,13 @@ require_relative "types"
|
|
|
4
4
|
|
|
5
5
|
module CaptiveStackDetector
|
|
6
6
|
# Un site statique sans manifeste ni build (index.html qui charge ses scripts) : stack node
|
|
7
|
-
# sans subtype, servie telle quelle. Supabase se reconnaît à son dossier, faute de package.json.
|
|
7
|
+
# sans subtype, servie telle quelle. Supabase (et sa base Postgres) se reconnaît à son dossier, faute de package.json.
|
|
8
8
|
class StaticSiteDetector
|
|
9
9
|
ENTRIES = %w[public/index.html index.html].freeze
|
|
10
10
|
NO_RUNTIME = Runtime.new(ruby: nil, node: nil)
|
|
11
|
+
NO_SERVICES = Services.new(database: nil, queue: nil, backend: nil)
|
|
12
|
+
# Supabase repose sur Postgres, comme le déduit PackageJsonAnalyzer pour une stack Node.
|
|
13
|
+
SUPABASE = Services.new(database: "postgres", queue: nil, backend: "supabase")
|
|
11
14
|
|
|
12
15
|
def initialize(reader)
|
|
13
16
|
@reader = reader
|
|
@@ -20,13 +23,9 @@ module CaptiveStackDetector
|
|
|
20
23
|
def detect
|
|
21
24
|
Result.new(
|
|
22
25
|
type: "node", subtype: nil, worker: nil, env_vars: {}, system_packages: [],
|
|
23
|
-
services:
|
|
26
|
+
services: @reader.read("supabase/config.toml") ? SUPABASE : NO_SERVICES,
|
|
24
27
|
runtime: NO_RUNTIME,
|
|
25
28
|
)
|
|
26
29
|
end
|
|
27
|
-
|
|
28
|
-
private
|
|
29
|
-
|
|
30
|
-
def backend = ("supabase" if @reader.read("supabase/config.toml"))
|
|
31
30
|
end
|
|
32
31
|
end
|
|
@@ -4,12 +4,14 @@ module CaptiveStackDetector
|
|
|
4
4
|
Services = Data.define(:database, :queue, :backend)
|
|
5
5
|
Worker = Data.define(:command)
|
|
6
6
|
Runtime = Data.define(:ruby, :node)
|
|
7
|
+
# Option d'add-on que la détection propose d'activer, avec sa raison lisible par le client.
|
|
8
|
+
Recommendation = Data.define(:addon, :option, :reason)
|
|
7
9
|
# public_env_prefix : préfixe des variables que le bundler expose au navigateur
|
|
8
10
|
# (VITE_, NEXT_PUBLIC_, EXPO_PUBLIC_), nil sans bundler front (PackageJsonAnalyzer).
|
|
9
11
|
Result = Data.define(:type, :subtype, :services, :worker, :runtime, :env_vars, :system_packages,
|
|
10
|
-
:public_env_prefix) do
|
|
12
|
+
:public_env_prefix, :recommendations) do
|
|
11
13
|
# env_vars : { "NOM" => valeur }, fusionné par captive-admin au provisionnement.
|
|
12
|
-
def initialize(env_vars:, public_env_prefix: nil, **)
|
|
14
|
+
def initialize(env_vars:, public_env_prefix: nil, recommendations: [], **)
|
|
13
15
|
Hash.try_convert(env_vars) or raise ArgumentError, "env_vars doit être un Hash"
|
|
14
16
|
super
|
|
15
17
|
end
|
|
@@ -7,7 +7,9 @@ require_relative "captive_stack_detector/gemfile_analyzer"
|
|
|
7
7
|
require_relative "captive_stack_detector/rails_stack_detector"
|
|
8
8
|
require_relative "captive_stack_detector/js_stack_detector"
|
|
9
9
|
require_relative "captive_stack_detector/static_site_detector"
|
|
10
|
+
require_relative "captive_stack_detector/addon_recommender"
|
|
10
11
|
|
|
12
|
+
# Déduit d'un dépôt sa stack, ses services et les options d'add-on à recommander.
|
|
11
13
|
module CaptiveStackDetector
|
|
12
14
|
# Doc publique des stacks supportées, livrée dans la gem et affichée par le dashboard (ADR 0003).
|
|
13
15
|
DOC_PATH = File.expand_path("../docs/stacks.md", __dir__)
|
|
@@ -18,6 +20,11 @@ module CaptiveStackDetector
|
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
def self.detect_from(reader)
|
|
23
|
+
AddonRecommender.new(reader).apply(detect_stack(reader))
|
|
24
|
+
end
|
|
25
|
+
private_class_method :detect_from
|
|
26
|
+
|
|
27
|
+
def self.detect_stack(reader)
|
|
21
28
|
gemfile = reader.read("Gemfile")
|
|
22
29
|
package_json = reader.read("package.json")
|
|
23
30
|
|
|
@@ -29,5 +36,5 @@ module CaptiveStackDetector
|
|
|
29
36
|
|
|
30
37
|
static_site.detect
|
|
31
38
|
end
|
|
32
|
-
private_class_method :
|
|
39
|
+
private_class_method :detect_stack
|
|
33
40
|
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.10.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Captive Studio
|
|
@@ -20,6 +20,7 @@ files:
|
|
|
20
20
|
- docs/stacks.md
|
|
21
21
|
- lib/captive/stack/detector.rb
|
|
22
22
|
- lib/captive_stack_detector.rb
|
|
23
|
+
- lib/captive_stack_detector/addon_recommender.rb
|
|
23
24
|
- lib/captive_stack_detector/aptfile_parser.rb
|
|
24
25
|
- lib/captive_stack_detector/env_template_scanner.rb
|
|
25
26
|
- lib/captive_stack_detector/env_vars_scanner.rb
|
|
@@ -38,7 +39,9 @@ files:
|
|
|
38
39
|
- lib/captive_stack_detector/node_version_detector.rb
|
|
39
40
|
- lib/captive_stack_detector/package_json_analyzer.rb
|
|
40
41
|
- lib/captive_stack_detector/rails_stack_detector.rb
|
|
42
|
+
- lib/captive_stack_detector/recommendation_rule.rb
|
|
41
43
|
- lib/captive_stack_detector/recurring_schedule.rb
|
|
44
|
+
- lib/captive_stack_detector/repo_sources.rb
|
|
42
45
|
- lib/captive_stack_detector/ruby_version_detector.rb
|
|
43
46
|
- lib/captive_stack_detector/solid_queue_usage.rb
|
|
44
47
|
- lib/captive_stack_detector/solid_queue_worker.rb
|