vetra 0.1.0.pre.alpha.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +196 -0
- data/bin/vetra +6 -0
- data/docs/ir/v0.1.schema.json +941 -0
- data/docs/ir/v0.2.schema.json +951 -0
- data/docs/ir/v0.3.schema.json +961 -0
- data/lib/vetra/cli.rb +64 -0
- data/lib/vetra/graph/analysis.rb +94 -0
- data/lib/vetra/graph/canonical.rb +52 -0
- data/lib/vetra/graph/capabilities.rb +22 -0
- data/lib/vetra/graph/compiler.rb +119 -0
- data/lib/vetra/graph/currency.rb +28 -0
- data/lib/vetra/graph/data.rb +48 -0
- data/lib/vetra/graph/declaration_loader.rb +62 -0
- data/lib/vetra/graph/diagnostic.rb +23 -0
- data/lib/vetra/graph/diff.rb +412 -0
- data/lib/vetra/graph/diff_source.rb +51 -0
- data/lib/vetra/graph/dsl.rb +444 -0
- data/lib/vetra/graph/linked_graph.rb +26 -0
- data/lib/vetra/graph/linker.rb +95 -0
- data/lib/vetra/graph/live_dependencies.rb +48 -0
- data/lib/vetra/graph/manifest.rb +78 -0
- data/lib/vetra/graph/normalizer.rb +689 -0
- data/lib/vetra/graph/registry.rb +50 -0
- data/lib/vetra/graph/schema.rb +78 -0
- data/lib/vetra/graph/validator.rb +600 -0
- data/lib/vetra/version.rb +3 -0
- data/lib/vetra.rb +29 -0
- metadata +141 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
require "find"
|
|
2
|
+
require "pathname"
|
|
3
|
+
|
|
4
|
+
module Vetra
|
|
5
|
+
module Graph
|
|
6
|
+
class Manifest
|
|
7
|
+
REQUIRED = %w[config/app.rb Gemfile Gemfile.lock .ruby-version].freeze
|
|
8
|
+
attr_reader :root, :entries
|
|
9
|
+
|
|
10
|
+
def initialize(root, extra_paths: [])
|
|
11
|
+
@root = Pathname.new(root).realpath
|
|
12
|
+
@bytes = {}
|
|
13
|
+
paths = REQUIRED + extra_paths
|
|
14
|
+
%w[app lib config].each do |directory|
|
|
15
|
+
base = @root.join(directory)
|
|
16
|
+
next unless base.exist? || base.symlink?
|
|
17
|
+
Find.find(base.to_s) do |path|
|
|
18
|
+
relative = Pathname.new(path).relative_path_from(@root).to_s
|
|
19
|
+
if Pathname.new(path).basename.to_s.match?(/\A(?:secrets|credentials)(?:\.|\z)|\A\.env|\.(?:key|pem|enc)\z/)
|
|
20
|
+
Find.prune if File.directory?(path)
|
|
21
|
+
next
|
|
22
|
+
end
|
|
23
|
+
checked_path(relative)
|
|
24
|
+
if File.symlink?(path) && File.directory?(path)
|
|
25
|
+
raise Error.single("input_symlink", "Diretórios de entrada não podem ser links: #{relative}.")
|
|
26
|
+
end
|
|
27
|
+
paths << relative if File.file?(path)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
@entries = paths.uniq.sort.map do |path|
|
|
31
|
+
@bytes[path] = checked_path(path).binread
|
|
32
|
+
{ "path" => path, "digest" => "sha256:#{Digest::SHA256.hexdigest(@bytes.fetch(path))}" }
|
|
33
|
+
end
|
|
34
|
+
Data.freeze(@entries)
|
|
35
|
+
rescue Errno::ENOENT, Errno::ENOTDIR => e
|
|
36
|
+
raise Error.single("input_missing", "Entrada ausente; confira config/app.rb, Gemfile, Gemfile.lock e .ruby-version (#{e.class}).")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def read(path)
|
|
40
|
+
@bytes.fetch(path).dup.force_encoding(Encoding::UTF_8)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def digest_for(path)
|
|
44
|
+
@entries.find { |entry| entry['path'] == path }.fetch("digest")
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def source(path, line)
|
|
48
|
+
absolute = Pathname.new(path).expand_path
|
|
49
|
+
relative = absolute.relative_path_from(root).to_s
|
|
50
|
+
checked_path(relative)
|
|
51
|
+
{ "path" => relative, "line" => line }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def verify!(document)
|
|
55
|
+
unless entries == document.fetch("inputs")
|
|
56
|
+
raise Error.single("inputs_changed", "Fontes adicionados, removidos ou alterados; execute graph:build no build de release.", pointer: "/inputs")
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def checked_path(path)
|
|
63
|
+
pieces = path.split("/", -1)
|
|
64
|
+
if path.empty? || path.start_with?("/") || path.match?(/\A[A-Za-z]:|[\\\x00-\x1f]/) || pieces.any? { |part| %w[. ..].include?(part) || part.empty? }
|
|
65
|
+
raise Error.single("input_path", "Use caminho relativo sem travessia: #{path.inspect}.")
|
|
66
|
+
end
|
|
67
|
+
if pieces.any? { |part| part == ".vetra" || part.match?(/\A(?:secrets|credentials)(?:\.|\z)|\A\.env|\.(?:key|pem|enc)\z/) }
|
|
68
|
+
raise Error.single("input_path", "Cache e segredos não são entradas da IR.")
|
|
69
|
+
end
|
|
70
|
+
actual = root.join(path).realpath
|
|
71
|
+
unless actual.to_s.start_with?("#{root}/")
|
|
72
|
+
raise Error.single("input_path", "Entrada escapa da raiz do projeto: #{path}.")
|
|
73
|
+
end
|
|
74
|
+
actual
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|