captive-stack-detector 0.8.3 → 0.8.4
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 +25 -0
- data/lib/captive_stack_detector/env_template_scanner.rb +10 -0
- data/lib/captive_stack_detector/file_reader.rb +1 -1
- data/lib/captive_stack_detector/github_tree.rb +1 -1
- data/lib/captive_stack_detector/js_env_detector.rb +21 -0
- data/lib/captive_stack_detector/js_env_files.rb +23 -0
- data/lib/captive_stack_detector/js_env_lexer.rb +63 -0
- data/lib/captive_stack_detector/js_env_scanner.rb +22 -0
- data/lib/captive_stack_detector/js_env_syntax.rb +18 -0
- data/lib/captive_stack_detector/js_stack_detector.rb +2 -1
- data/lib/captive_stack_detector/package_json_analyzer.rb +1 -1
- data/lib/captive_stack_detector/version.rb +1 -1
- metadata +7 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9b3af49e63482068e44d5c1b1064695ac49eadd05c1a63447e29cfe57423492b
|
|
4
|
+
data.tar.gz: ef69968ebaec94b711a248afe56dad7bfcbc0b8fca3f349683810375d3929ff9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 88acfedc7245af2d7526bc4416159d137f4b0fdd3649f883a7cc8d8f41af9bef4ef32f8c26c67921314d86b8d7b47437cd1bb01b1459a0cfa52e341b47e4840c
|
|
7
|
+
data.tar.gz: f9d7277815801c5e0e0b769dc68d0b884d911d88044318700b4af975957f31bbd4e43eda31c7089f55ebdbf4f3ef948e877a96bf4e5b8cbc0a48c75550abba10
|
data/docs/stacks.md
CHANGED
|
@@ -69,6 +69,30 @@ Pour Astro, utilisez l'adaptateur `@astrojs/node` en mode `standalone`, avec `ou
|
|
|
69
69
|
- Une ligne `worker: <commande>` dans un `Procfile` lance un worker.
|
|
70
70
|
|
|
71
71
|
|
|
72
|
+
### Variables à renseigner
|
|
73
|
+
|
|
74
|
+
La détection Node recherche les lectures à clé littérale `process.env.MA_CLE`,
|
|
75
|
+
`process.env["MA_CLE"]`, `import.meta.env.MA_CLE` et `import.meta.env["MA_CLE"]`
|
|
76
|
+
dans les sources JavaScript, TypeScript, JSX, TSX, Astro, Vue et Svelte, y compris les
|
|
77
|
+
fichiers de configuration à la racine. Les imports nommés de `astro:env/client` et
|
|
78
|
+
`astro:env/server` sont également reconnus.
|
|
79
|
+
|
|
80
|
+
Les modèles `.env.example`, `.env.sample` et `.env.template` à la racine déclarent aussi
|
|
81
|
+
des noms de variables à renseigner. **Leurs valeurs ne sont jamais reprises** : chaque nom
|
|
82
|
+
est signalé avec la valeur `placeholder`. Les fichiers `.env`, `.env.local` et les autres
|
|
83
|
+
fichiers d'environnement réels ne sont pas lus.
|
|
84
|
+
|
|
85
|
+
Les commentaires, chaînes d'exemple, dépendances, sorties de build, scripts d'outillage
|
|
86
|
+
(`scripts/`, `bin/`), documentation, exemples, tests, stories et
|
|
87
|
+
déclarations TypeScript sont ignorés. Les constantes intégrées de Vite et les variables
|
|
88
|
+
`NODE_ENV`, `NODE_OPTIONS`, `HOST` et `PORT` ne sont pas demandées. Une lecture avec une
|
|
89
|
+
valeur de repli littérale (`?? "local"`, `|| "local"`, nombre ou booléen) est facultative ;
|
|
90
|
+
une lecture sans repli ailleurs, ou sa déclaration dans un modèle, demande toujours la clé.
|
|
91
|
+
|
|
92
|
+
L'analyse est statique : les clés calculées, la déstructuration et les appels indirects
|
|
93
|
+
(comme `getSecret(nom)`) ne sont pas déduits. Déclarez ces clés dans un modèle si nécessaire.
|
|
94
|
+
La même analyse est utilisée sur les dépôts locaux et GitHub.
|
|
95
|
+
|
|
72
96
|
### Variables exposées au navigateur
|
|
73
97
|
|
|
74
98
|
Une variable n'arrive dans le code du navigateur que si elle porte le préfixe de votre bundler. Captive le déduit de vos dépendances :
|
|
@@ -77,6 +101,7 @@ Une variable n'arrive dans le code du navigateur que si elle porte le préfixe d
|
|
|
77
101
|
| --- | --- |
|
|
78
102
|
| `expo` | `EXPO_PUBLIC_` |
|
|
79
103
|
| `next` | `NEXT_PUBLIC_` |
|
|
104
|
+
| `astro` | `PUBLIC_` |
|
|
80
105
|
| `vite` | `VITE_` |
|
|
81
106
|
|
|
82
107
|
Si plusieurs sont présentes, c'est le premier de la liste qui l'emporte (`vite` est souvent là pour les tests seulement).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CaptiveStackDetector
|
|
4
|
+
# Les modèles déclarent des noms ; leurs valeurs d'exemple ne sont jamais importées.
|
|
5
|
+
module EnvTemplateScanner
|
|
6
|
+
def self.scan(content)
|
|
7
|
+
content.scan(/^\s*(?:export\s+)?([A-Z_][A-Z0-9_]*)\s*=/).flatten.to_h { |key| [ key, "placeholder" ] }
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -30,7 +30,7 @@ module CaptiveStackDetector
|
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def list(dir)
|
|
33
|
-
Dir.glob("#{dir}/**/*", base: @path).select { |entry| File.file?(File.join(@path, entry)) }.sort
|
|
33
|
+
Dir.glob("#{dir}/**/*", File::FNM_DOTMATCH, base: @path).select { |entry| File.file?(File.join(@path, entry)) }.sort
|
|
34
34
|
end
|
|
35
35
|
end
|
|
36
36
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "js_env_files"
|
|
4
|
+
|
|
5
|
+
module CaptiveStackDetector
|
|
6
|
+
# Ne lit que les sources et les modèles d'environnement, jamais les secrets .env.
|
|
7
|
+
class JsEnvDetector
|
|
8
|
+
def initialize(reader)
|
|
9
|
+
@reader = reader
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def detect
|
|
13
|
+
vars = @reader.list(".").sort.each_with_object({}) do |entry, found|
|
|
14
|
+
path = entry.delete_prefix("./")
|
|
15
|
+
parser = JsEnvFiles.parser(path)
|
|
16
|
+
found.merge!(parser.scan(@reader.read(path).to_s)) if parser
|
|
17
|
+
end
|
|
18
|
+
vars.reject { |key, _| JsEnvScanner::RUNTIME_VARS.include?(key) }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "js_env_scanner"
|
|
4
|
+
require_relative "env_template_scanner"
|
|
5
|
+
|
|
6
|
+
module CaptiveStackDetector
|
|
7
|
+
# Choisit les fichiers lisibles avant tout téléchargement de contenu.
|
|
8
|
+
module JsEnvFiles
|
|
9
|
+
TEMPLATES = %w[.env.example .env.sample .env.template].freeze
|
|
10
|
+
EXCLUDED = %r{
|
|
11
|
+
(?:\A|/)(?:node_modules|vendor|\.git|dist|build|\.output|\.next|\.nuxt|\.astro|coverage|
|
|
12
|
+
test|tests|spec|__tests__|__mocks__|\.storybook|\.github|scripts|bin|docs|examples|fixtures|e2e)(?:/|\z)
|
|
13
|
+
}x
|
|
14
|
+
SOURCE = /(?<!\.test)(?<!\.spec)(?<!\.stories)(?<!\.d)\.(?:[cm]?[jt]sx?|astro|vue|svelte)\z/
|
|
15
|
+
|
|
16
|
+
def self.parser(path)
|
|
17
|
+
return nil if EXCLUDED.match?(path)
|
|
18
|
+
return EnvTemplateScanner if TEMPLATES.include?(path)
|
|
19
|
+
|
|
20
|
+
JsEnvScanner if SOURCE.match?(path)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "strscan"
|
|
4
|
+
require_relative "js_env_syntax"
|
|
5
|
+
|
|
6
|
+
module CaptiveStackDetector
|
|
7
|
+
# Énumère les accès littéraux en ignorant commentaires et texte non exécuté.
|
|
8
|
+
class JsEnvLexer
|
|
9
|
+
include JsEnvSyntax
|
|
10
|
+
|
|
11
|
+
def initialize(content)
|
|
12
|
+
@scanner = StringScanner.new(content, fixed_anchor: true)
|
|
13
|
+
@references = []
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def references
|
|
17
|
+
code
|
|
18
|
+
@references
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def code(terminator = nil)
|
|
24
|
+
until @scanner.eos?
|
|
25
|
+
next if @scanner.skip(IGNORED)
|
|
26
|
+
|
|
27
|
+
break if consume == terminator
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def consume
|
|
32
|
+
if @scanner.scan(ASTRO_IMPORT)
|
|
33
|
+
astro_import(@scanner[1])
|
|
34
|
+
elsif @scanner.scan(REFERENCE)
|
|
35
|
+
reference
|
|
36
|
+
else
|
|
37
|
+
punctuation(@scanner.getch)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def punctuation(char)
|
|
42
|
+
code("}") if char == "{"
|
|
43
|
+
template if char == "`"
|
|
44
|
+
char
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def astro_import(names)
|
|
48
|
+
JsEnvSyntax.astro_keys(names).each { |key| @references << [ key, "astro", "" ] }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def reference
|
|
52
|
+
scope = @scanner[1].start_with?("import") ? "vite" : "node"
|
|
53
|
+
@references << [ @scanner[2] || @scanner[3], scope, @scanner.rest ]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def template
|
|
57
|
+
until @scanner.eos? || @scanner.skip(/`/)
|
|
58
|
+
code("}") if @scanner.skip(/\$\{/)
|
|
59
|
+
@scanner.skip(/(?:\\.|[^`$]|\$(?!\{))+/m)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "js_env_lexer"
|
|
4
|
+
|
|
5
|
+
module CaptiveStackDetector
|
|
6
|
+
# Ne demande que les lectures sans valeur de repli et hors variables intégrées.
|
|
7
|
+
module JsEnvScanner
|
|
8
|
+
DEFAULT = /\A\s*(?:\?\?|\|\|)\s*(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|true\b|false\b|\d+(?:\.\d+)?\b)/
|
|
9
|
+
ASSIGNMENT = /\A\s*(?:=(?!=|>)|\?\?=|\|\|=|&&=)/
|
|
10
|
+
RUNTIME_VARS = %w[NODE_ENV NODE_OPTIONS PORT HOST].freeze
|
|
11
|
+
VITE_VARS = %w[BASE_URL MODE DEV PROD SSR].freeze
|
|
12
|
+
|
|
13
|
+
def self.scan(content)
|
|
14
|
+
JsEnvLexer.new(content).references.each_with_object({}) do |(key, scope, rest), found|
|
|
15
|
+
next if RUNTIME_VARS.include?(key) || (scope == "vite" && VITE_VARS.include?(key))
|
|
16
|
+
next if DEFAULT.match?(rest) || ASSIGNMENT.match?(rest)
|
|
17
|
+
|
|
18
|
+
found[key] = "placeholder"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CaptiveStackDetector
|
|
4
|
+
# Tokens reconnus sans interpréter le programme JavaScript.
|
|
5
|
+
module JsEnvSyntax
|
|
6
|
+
KEY = /[A-Z_][A-Z0-9_]*/
|
|
7
|
+
REFERENCE = /
|
|
8
|
+
(?<![\w$.])(process\s*\.\s*env|import\s*\.\s*meta\s*\.\s*env)
|
|
9
|
+
(?:\s*\.\s*(#{KEY})(?![\w$])|\s*\[\s*['"](#{KEY})['"]\s*\])
|
|
10
|
+
/x
|
|
11
|
+
ASTRO_IMPORT = /import\s*\{([^}]+)\}\s*from\s*['"]astro:env\/(?:client|server)['"]/m
|
|
12
|
+
COMMENT = %r{//[^\n]*|/\*.*?\*/|<!--.*?-->}m
|
|
13
|
+
IGNORED = Regexp.union(COMMENT, /"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/m)
|
|
14
|
+
def self.astro_keys(names)
|
|
15
|
+
names.gsub(COMMENT, "").split(",").filter_map { |name| name.strip[/\A#{KEY}\b/] }
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../captive_stack_detector"
|
|
4
4
|
require_relative "package_json_analyzer"
|
|
5
|
+
require_relative "js_env_detector"
|
|
5
6
|
|
|
6
7
|
module CaptiveStackDetector
|
|
7
8
|
class JsStackDetector
|
|
@@ -20,7 +21,7 @@ module CaptiveStackDetector
|
|
|
20
21
|
services: Services.new(database: @analyzer.database, queue: @analyzer.queue, backend: @analyzer.backend),
|
|
21
22
|
worker: build_worker,
|
|
22
23
|
runtime: Runtime.new(ruby: nil, node: @reader.node_version),
|
|
23
|
-
env_vars:
|
|
24
|
+
env_vars: JsEnvDetector.new(@reader).detect,
|
|
24
25
|
system_packages: [],
|
|
25
26
|
public_env_prefix: @analyzer.public_env_prefix,
|
|
26
27
|
)
|
|
@@ -5,7 +5,7 @@ require_relative "ssr_framework_detector"
|
|
|
5
5
|
|
|
6
6
|
module CaptiveStackDetector
|
|
7
7
|
class PackageJsonAnalyzer
|
|
8
|
-
PUBLIC_ENV_PREFIXES = { "expo" => "EXPO_PUBLIC_", "next" => "NEXT_PUBLIC_", "vite" => "VITE_" }.freeze
|
|
8
|
+
PUBLIC_ENV_PREFIXES = { "expo" => "EXPO_PUBLIC_", "next" => "NEXT_PUBLIC_", "astro" => "PUBLIC_", "vite" => "VITE_" }.freeze
|
|
9
9
|
# Des clients, pas des ORM : Prisma ou Drizzle parlent aussi MySQL et SQLite, leur
|
|
10
10
|
# présence ne dit pas que l'app attend une base Postgres.
|
|
11
11
|
POSTGRES_CLIENTS = %w[pg postgres].freeze
|
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.8.
|
|
4
|
+
version: 0.8.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Captive Studio
|
|
@@ -21,6 +21,7 @@ files:
|
|
|
21
21
|
- lib/captive/stack/detector.rb
|
|
22
22
|
- lib/captive_stack_detector.rb
|
|
23
23
|
- lib/captive_stack_detector/aptfile_parser.rb
|
|
24
|
+
- lib/captive_stack_detector/env_template_scanner.rb
|
|
24
25
|
- lib/captive_stack_detector/env_vars_scanner.rb
|
|
25
26
|
- lib/captive_stack_detector/file_content_parser.rb
|
|
26
27
|
- lib/captive_stack_detector/file_reader.rb
|
|
@@ -28,6 +29,11 @@ files:
|
|
|
28
29
|
- lib/captive_stack_detector/gemfile_analyzer.rb
|
|
29
30
|
- lib/captive_stack_detector/github_api_client.rb
|
|
30
31
|
- lib/captive_stack_detector/github_tree.rb
|
|
32
|
+
- lib/captive_stack_detector/js_env_detector.rb
|
|
33
|
+
- lib/captive_stack_detector/js_env_files.rb
|
|
34
|
+
- lib/captive_stack_detector/js_env_lexer.rb
|
|
35
|
+
- lib/captive_stack_detector/js_env_scanner.rb
|
|
36
|
+
- lib/captive_stack_detector/js_env_syntax.rb
|
|
31
37
|
- lib/captive_stack_detector/js_stack_detector.rb
|
|
32
38
|
- lib/captive_stack_detector/node_version_detector.rb
|
|
33
39
|
- lib/captive_stack_detector/package_json_analyzer.rb
|