1 import Base 2 import ../effs/io.bend as X 3 import ../plan/type.bend as P 4 import ../../environment/external/type.bend as Ex 5 import ./type.bend as Ct 6 import ./ops.bend as O 7 8 # Each built container's stack against the repository at `root`: its dependencies (the nearest 9 # package.json at or above its source folder, the root go.mod, its .NET projects when it declares C#, 10 # and shadcn/ui when components.json sits beside that package.json) and the extensions of its source files. 11 12 def under(+root: String, +dir: String, +file: String) -> String: 13 Bool.pick(String, String.is_empty(dir), root ++ "/" ++ file, root ++ "/" ++ dir ++ "/" ++ file) 14 15 # The first of these folders with a package.json, or "" (the root). 16 def package_dir(dirs: List<&2, String>, +root: String) -> IO(String): 17 match dirs: 18 case []: 19 IO.pure(String, "") 20 case +d <> rest: 21 do IO: 22 text : String <- X.read(under(root, d, "package.json")) 23 later : String <- package_dir(rest, root) 24 return Bool.pick(String, String.is_empty(String.trim(text)), later, d) 25 26 def project_file_if(is_project: Bool, +path: String, +root: String) -> IO(List<&2, String>): 27 match is_project: 28 case False{}: 29 IO.pure(List<&2, String>, []) 30 case True{}: 31 do IO>: 32 text : String <- X.read(root ++ "/" ++ path) 33 return O.csproj_deps(String.lines(text)) 34 35 def project_file(+path: String, +root: String) -> IO(List<&2, String>): 36 project_file_if(String.ends_with(path, ".csproj"), path, root) 37 38 def project_dependencies(paths: List<&2, String>, +root: String) -> IO(List<&2, String>): 39 match paths: 40 case []: 41 IO.pure(List<&2, String>, []) 42 case +path <> rest: 43 do IO>: 44 one : List<&2, String> <- project_file(path, root) 45 more : List<&2, String> <- project_dependencies(rest, root) 46 return List.append(&2, String, one, more) 47 48 def dotnet_dependencies(+root: String, +source: String, enabled: Bool) -> IO(List<&2, String>): 49 match enabled: 50 case False{}: 51 IO.pure(List<&2, String>, []) 52 case True{}: 53 do IO>: 54 out : String <- X.run("git", ["-C", root, "ls-files", "-co", "--exclude-standard", "--", source]) 55 project_dependencies(X.nonempty(String.lines(out)), root) 56 57 def dependencies(+root: String, +source: String, +go: List<&2, String>, +dotnet: List<&2, String>) -> IO(List<&2, String>): 58 do IO>: 59 +dir : String <- package_dir(O.folders(source), root) 60 package : String <- X.read(under(root, dir, "package.json")) 61 components : String <- X.read(under(root, dir, "components.json")) 62 return List.append(&2, String, O.package_deps(package), 63 List.append(&2, String, go, List.append(&2, String, dotnet, 64 Bool.pick(List<&2, String>, String.is_empty(String.trim(components)), [], ["shadcn/ui"])))) 65 66 def extensions(+root: String, +source: String) -> IO(List<&2, String>): 67 do IO>: 68 out : String <- X.run("git", ["-C", root, "ls-files", "-co", "--exclude-standard", "--", source]) 69 return O.source_extensions(X.nonempty(String.lines(out))) 70 71 def blocked(+what: String, xs: List<&2, String>, +why: String) -> List<&2, P.Step>: 72 match xs: 73 case []: 74 [] 75 case x <> rest: 76 P.Blocked{what, x ++ why} <> blocked(what, rest, why) 77 78 def or_current(+what: String, bs: List<&2, P.Step>) -> List<&2, P.Step>: 79 match bs: 80 case []: 81 [P.Current{what}] 82 case b <> rest: 83 b <> rest 84 85 def check(+c: Ct.Container, +root: String, +xs: List<&2, Ex.External>, +go: List<&2, String>) -> IO(List<&2, P.Step>): 86 do IO>: 87 dotnet : List<&2, String> <- dotnet_dependencies(root, O.source_of(c), O.uses_language(c, "C#")) 88 +deps : List<&2, String> <- dependencies(root, O.source_of(c), go, dotnet) 89 exts : List<&2, String> <- extensions(root, O.source_of(c)) 90 +what : String = "stack " ++ O.key_of(c) 91 return or_current(what, List.append(&2, P.Step, blocked(what, O.stack_missing(c, deps), " is declared but not a dependency"), 92 List.append(&2, P.Step, blocked(what, O.languages_missing(c, exts), " files have no declared language"), 93 blocked(what, O.sdk_missing(c, xs, deps), " is the SDK of a model API it calls but not a dependency")))) 94 95 def checks_with(cs: List<&2, Ct.Container>, +root: String, +xs: List<&2, Ex.External>, +go: List<&2, String>) -> IO(List<&2, P.Step>): 96 match cs: 97 case []: 98 IO.pure(List<&2, P.Step>, []) 99 case c <> rest: 100 do IO>: 101 one : List<&2, P.Step> <- check(c, root, xs, go) 102 more : List<&2, P.Step> <- checks_with(rest, root, xs, go) 103 return List.append(&2, P.Step, one, more) 104 105 # The stack of every container built from the repository at `root`. 106 def checks(+root: String, cs: List<&2, Ct.Container>, +xs: List<&2, Ex.External>) -> IO(List<&2, P.Step>): 107 do IO>: 108 mod : String <- X.read(root ++ "/go.mod") 109 checks_with(O.built(cs), root, xs, O.go_deps(String.lines(mod)))