domain/architecture/system/container/ops.bend
near-v-framework@0.3.0.2 · 267 lines · 0 definitions
1import Base
2import bend-net-json@0.3.0.0/json.bend as Json
3import near-v-framework@0.3.0.2/domain/architecture/relationship/type.bend as Rel
4import near-v-framework@0.3.0.2/domain/architecture/category/type.bend as K
5import near-v-framework@0.3.0.2/domain/architecture/environment/external/type.bend as Ex
6import near-v-framework@0.3.0.2/domain/architecture/system/container/type.bend as Ct
7import near-v-framework@0.3.0.2/domain/architecture/system/container/component/type.bend as Co
8
9# A container's stack against its repository: the frameworks and libraries it declares are its
10# dependencies, its source files are in its declared languages, and it has the SDK of each model API
11# it calls. What it lists as `migrating` may be absent.
12
13def key_of(c: Ct.Container) -> String:
14 match c:
15 case Ct.Container{k, _, _, _, _, _, _, _, _, _, _, _, _, _, _}:
16 k
17
18def source_of(c: Ct.Container) -> String:
19 match c:
20 case Ct.Container{_, _, _, _, _, _, _, s, _, _, _, _, _, _, _}:
21 s
22
23def uses_language(c: Ct.Container, +name: String) -> Bool:
24 match c:
25 case Ct.Container{_, _, _, _, _, _, _, _, _, _, _, langs, _, _, _}:
26 List.contains(~String, ~String.eq, langs, name)
27
28def keep_built(+c: Ct.Container, +rest: List<&2, Ct.Container>) -> List<&2, Ct.Container>:
29 Bool.pick(List<&2, Ct.Container>, String.is_empty(source_of(c)), rest, c <> rest)
30
31# The containers built from this repository: those with a source folder.
32def built(cs: List<&2, Ct.Container>) -> List<&2, Ct.Container>:
33 match cs:
34 case []:
35 []
36 case c <> rest:
37 keep_built(c, built(rest))
38
39# ---- names ----
40# Declared names are readable names; dependencies are package names.
41def package_name(+name: String) -> String:
42 Bool.pick(String, String.eq(name, "Next.js"), "next",
43 Bool.pick(String, String.eq(name, "Tailwind CSS"), "tailwindcss",
44 Bool.pick(String, String.eq(name, "React"), "react",
45 Bool.pick(String, String.eq(name, "Convex"), "convex",
46 Bool.pick(String, String.eq(name, "OpenRouter SDK"), "@openrouter/sdk",
47 Bool.pick(String, String.eq(name, "Sentry SDK"), "@sentry/nextjs",
48 Bool.pick(String, String.eq(name, "whatsmeow"), "go.mau.fi/whatsmeow",
49 Bool.pick(String, String.eq(name, "ASP.NET Core"), "Microsoft.NET.Sdk.Web",
50 Bool.pick(String, String.eq(name, "Hot Chocolate"), "HotChocolate.AspNetCore",
51 Bool.pick(String, String.eq(name, "EF Core"), "Microsoft.EntityFrameworkCore",
52 Bool.pick(String, String.eq(name, ".NET Generic Host"), "Microsoft.Extensions.Hosting",
53 Bool.pick(String, String.eq(name, "Hangfire"), "Hangfire.NetCore", name))))))))))))
54
55# Whether a language is written in files with this extension.
56def language_extension(+name: String, +ext: String) -> Bool:
57 (String.eq(name, "TypeScript") && (String.eq(ext, ".ts") || String.eq(ext, ".tsx"))) ||
58 (String.eq(name, "Go") && String.eq(ext, ".go")) ||
59 (String.eq(name, "JavaScript") && (String.eq(ext, ".js") || String.eq(ext, ".mjs") || String.eq(ext, ".jsx"))) ||
60 (String.eq(name, "Python") && String.eq(ext, ".py")) ||
61 (String.eq(name, "Bend") && String.eq(ext, ".bend")) ||
62 (String.eq(name, "C#") && String.eq(ext, ".cs")) ||
63 (String.eq(name, "C") && (String.eq(ext, ".c") || String.eq(ext, ".h")))
64
65def any_language(+ext: String, xs: List<&2, String>) -> Bool:
66 match xs:
67 case []:
68 False{}
69 case name <> rest:
70 language_extension(name, ext) || any_language(ext, rest)
71
72# The extensions of every language V knows; files with other extensions are not source.
73def known(+ext: String) -> Bool:
74 any_language(ext, ["TypeScript", "Go", "JavaScript", "Python", "Bend", "C#", "C"])
75
76# ---- what is missing ----
77def has_prefix(xs: List<&2, String>, +prefix: String) -> Bool:
78 match xs:
79 case []:
80 False{}
81 case x <> rest:
82 String.starts_with(x, prefix) || has_prefix(rest, prefix)
83
84def has_dependency(+name: String, +deps: List<&2, String>) -> Bool:
85 List.contains(~String, ~String.eq, deps, package_name(name)) ||
86 (String.eq(name, "EF Core") && has_prefix(deps, "Microsoft.EntityFrameworkCore."))
87
88# The declared names that are neither dependencies nor migrating.
89def absent(xs: List<&2, String>, +deps: List<&2, String>, +migrating: List<&2, String>) -> List<&2, String>:
90 match xs:
91 case []:
92 []
93 case +x <> rest:
94 +more = absent(rest, deps, migrating)
95 Bool.pick(List<&2, String>, has_dependency(x, deps) || List.contains(~String, ~String.eq, migrating, x), more, x <> more)
96
97# The frameworks and libraries a built container declares but does not depend on.
98def stack_missing(c: Ct.Container, +deps: List<&2, String>) -> List<&2, String>:
99 match c:
100 case Ct.Container{_, _, _, _, _, _, _, source, _, fs, ls, _, +ms, _, _}:
101 Bool.pick(List<&2, String>, String.is_empty(source), [], absent(List.append(&2, String, fs, ls), deps, ms))
102
103# The extensions in a built container's source that no declared or migrating language is written in,
104# and that are not themselves migrating.
105def unwritten(xs: List<&2, String>, +languages: List<&2, String>, +migrating: List<&2, String>) -> List<&2, String>:
106 match xs:
107 case []:
108 []
109 case +x <> rest:
110 +more = unwritten(rest, languages, migrating)
111 Bool.pick(List<&2, String>, any_language(x, languages) || any_language(x, migrating) || List.contains(~String, ~String.eq, migrating, x), more, x <> more)
112
113def languages_missing(c: Ct.Container, +extensions: List<&2, String>) -> List<&2, String>:
114 match c:
115 case Ct.Container{_, _, _, _, _, _, _, source, _, _, _, langs, +ms, _, _}:
116 Bool.pick(List<&2, String>, String.is_empty(source), [], unwritten(extensions, langs, ms))
117
118# ---- the SDK of each model API a container calls ----
119def destinations(rs: List<&2, Rel.Relationship>) -> List<&2, String>:
120 match rs:
121 case []:
122 []
123 case r <> rest:
124 match r:
125 case Rel.Relationship{d, _, _, _}:
126 d <> destinations(rest)
127
128def component_destinations(cos: List<&2, Co.Component>) -> List<&2, String>:
129 match cos:
130 case []:
131 []
132 case co <> rest:
133 match co:
134 case Co.Component{_, _, _, _, us}:
135 List.append(&2, String, destinations(us), component_destinations(rest))
136
137def model_libraries(cat: K.Category, ls: List<&2, String>) -> List<&2, String>:
138 match cat:
139 case K.ModelApi{}:
140 ls
141 case _:
142 []
143
144# The SDK of the model API with this key, among the externals.
145def sdk_of(+dest: String, xs: List<&2, Ex.External>) -> List<&2, String>:
146 match xs:
147 case []:
148 []
149 case x <> rest:
150 match x:
151 case Ex.External{k, _, _, cat, _, _, ls, _}:
152 List.append(&2, String, Bool.pick(List<&2, String>, String.eq(k, dest), model_libraries(cat, ls), []), sdk_of(dest, rest))
153
154def sdks(ds: List<&2, String>, +xs: List<&2, Ex.External>) -> List<&2, String>:
155 match ds:
156 case []:
157 []
158 case d <> rest:
159 List.append(&2, String, sdk_of(d, xs), sdks(rest, xs))
160
161# The SDK libraries of the model APIs a built container (or one of its components) calls that it does
162# not depend on.
163def sdk_missing(c: Ct.Container, +xs: List<&2, Ex.External>, +deps: List<&2, String>) -> List<&2, String>:
164 match c:
165 case Ct.Container{_, _, _, _, _, _, _, source, _, _, _, _, +ms, cos, us}:
166 Bool.pick(List<&2, String>, String.is_empty(source), [],
167 absent(Set.to_list(Set.from_list(sdks(List.append(&2, String, destinations(us), component_destinations(cos)), xs))), deps, ms))
168
169# ---- reading the repository's evidence ----
170def filled(xs: List<&2, String>) -> List<&2, String>:
171 match xs:
172 case []:
173 []
174 case +x <> rest:
175 +more = filled(rest)
176 Bool.pick(List<&2, String>, String.is_empty(x) || String.eq(x, "."), more, x <> more)
177
178def nth(+xs: List<&2, String>, n: Nat) -> String:
179 Maybe.default(&2, String, List.get(&2, String, xs, n), "")
180
181def last(+xs: List<&2, String>) -> String:
182 Maybe.default(&2, String, List.head(&2, String, List.reverse(&2, String, xs)), "")
183
184def prefixes(segs: List<&2, String>, +acc: String) -> List<&2, String>:
185 match segs:
186 case []:
187 [acc]
188 case +s <> rest:
189 acc <> prefixes(rest, Bool.pick(String, String.is_empty(acc), s, acc ++ "/" ++ s))
190
191# A folder and each folder above it, nearest first, ending with the repository root ("").
192def folders(+dir: String) -> List<&2, String>:
193 List.reverse(&2, String, prefixes(filled(String.split(dir, '/')), ""))
194
195def json_or_null(m: Maybe<&2, Json.Val>) -> Json.Val:
196 match m:
197 case None{}:
198 Json.Null{}
199 case Some{v}:
200 v
201
202def keys(v: Maybe<&2, Json.Val>) -> List<&2, String>:
203 match v:
204 case Some{Json.Obj{m}}:
205 Map.keys(&2, Json.Val, m)
206 case _:
207 []
208
209# A package.json's dependencies and devDependencies.
210def package_deps(+text: String) -> List<&2, String>:
211 +v = json_or_null(Json.parse(text))
212 List.append(&2, String, keys(Json.get(v, "dependencies")), keys(Json.get(v, "devDependencies")))
213
214# A .NET project's SDK and direct package or framework references.
215def csproj_dependency(+line: String) -> String:
216 +s = String.trim(line)
217 Bool.pick(String, String.starts_with(s, "<Project Sdk=\"") ||
218 String.starts_with(s, "<PackageReference Include=\"") ||
219 String.starts_with(s, "<FrameworkReference Include=\""),
220 nth(String.split(s, '"'), 1n), "")
221
222def csproj_deps(ls: List<&2, String>) -> List<&2, String>:
223 match ls:
224 case []:
225 []
226 case +line <> rest:
227 +name = csproj_dependency(line)
228 +more = csproj_deps(rest)
229 Bool.pick(List<&2, String>, String.is_empty(name), more, name <> more)
230
231# The module a go.mod line requires, or "".
232def module_name(+line: String) -> String:
233 +words = filled(String.split(String.join(String.split(String.trim(line), '\t'), " "), ' '))
234 +first = nth(words, 0n)
235 +second = nth(words, 1n)
236 Bool.pick(String, String.eq(first, "require") && String.starts_with(nth(words, 2n), "v"), second,
237 Bool.pick(String, String.starts_with(second, "v") && Bool.not(String.eq(first, "module")), first, ""))
238
239def go_deps(ls: List<&2, String>) -> List<&2, String>:
240 match ls:
241 case []:
242 []
243 case +l <> rest:
244 +more = go_deps(rest)
245 Bool.pick(List<&2, String>, String.is_empty(module_name(l)), more, module_name(l) <> more)
246
247# The source extension of a repository path, or "" when it is not source: generated files, and files
248# under node_modules, _generated or .next.
249def source_ext(+path: String) -> String:
250 +parts = String.split(path, '/')
251 +name = last(parts)
252 +ext = "." ++ last(String.split(name, '.'))
253 Bool.pick(String, String.contains(name, ".") && Bool.not(String.contains(name, ".generated.")) && known(ext) &&
254 Bool.not(List.contains(~String, ~String.eq, parts, "node_modules") || List.contains(~String, ~String.eq, parts, "_generated") || List.contains(~String, ~String.eq, parts, ".next")),
255 ext, "")
256
257def source_exts(paths: List<&2, String>) -> List<&2, String>:
258 match paths:
259 case []:
260 []
261 case +p <> rest:
262 +more = source_exts(rest)
263 Bool.pick(List<&2, String>, String.is_empty(source_ext(p)), more, source_ext(p) <> more)
264
265# The distinct source extensions among repository paths.
266def source_extensions(paths: List<&2, String>) -> List<&2, String>:
267 Set.to_list(Set.from_list(source_exts(paths)))