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