domain/architecture/system/container/ops.bend

near-v-framework@0.3.0.0 · 228 lines · 0 definitions

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