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, and shadcn/ui when components.json sits 10 # beside that package.json) and the extensions of its source files, less those in another container's 11 # folder inside its own. 12 13 def under(+root: String, +dir: String, +file: String) -> String: 14 Bool.pick(String, String.is_empty(dir), root ++ "/" ++ file, root ++ "/" ++ dir ++ "/" ++ file) 15 16 # The first of these folders with a package.json, or "" (the root). 17 def package_dir(dirs: List<&2, String>, +root: String) -> IO(String): 18 match dirs: 19 case []: 20 IO.pure(String, "") 21 case +d <> rest: 22 do IO: 23 text : String <- X.read(under(root, d, "package.json")) 24 later : String <- package_dir(rest, root) 25 return Bool.pick(String, String.is_empty(String.trim(text)), later, d) 26 27 def dependencies(+root: String, +source: String, +go: List<&2, String>) -> IO(List<&2, String>): 28 do IO>: 29 +dir : String <- package_dir(O.folders(source), root) 30 package : String <- X.read(under(root, dir, "package.json")) 31 components : String <- X.read(under(root, dir, "components.json")) 32 return List.append(&2, String, O.package_deps(package), 33 List.append(&2, String, go, Bool.pick(List<&2, String>, String.is_empty(String.trim(components)), [], ["shadcn/ui"]))) 34 35 def outside(fs: List<&2, String>, +others: List<&2, String>) -> List<&2, String>: 36 match fs: 37 case []: 38 [] 39 case +f <> rest: 40 +more = outside(rest, others) 41 Bool.pick(List<&2, String>, O.under_any(f, others), more, f <> more) 42 43 def extensions(+root: String, +source: String, +others: List<&2, String>) -> IO(List<&2, String>): 44 do IO>: 45 out : String <- X.run("git", ["-C", root, "ls-files", "-co", "--exclude-standard", "--", source]) 46 return O.source_extensions(outside(X.nonempty(String.lines(out)), others)) 47 48 def blocked(+what: String, xs: List<&2, String>, +why: String) -> List<&2, P.Step>: 49 match xs: 50 case []: 51 [] 52 case x <> rest: 53 P.Blocked{what, x ++ why} <> blocked(what, rest, why) 54 55 def or_current(+what: String, bs: List<&2, P.Step>) -> List<&2, P.Step>: 56 match bs: 57 case []: 58 [P.Current{what}] 59 case b <> rest: 60 b <> rest 61 62 def check(+c: Ct.Container, +root: String, +xs: List<&2, Ex.External>, +go: List<&2, String>, +others: List<&2, String>) -> IO(List<&2, P.Step>): 63 do IO>: 64 +deps : List<&2, String> <- dependencies(root, O.source_of(c), go) 65 exts : List<&2, String> <- extensions(root, O.source_of(c), O.other_sources(O.source_of(c), others)) 66 +what : String = "stack " ++ O.key_of(c) 67 return or_current(what, List.append(&2, P.Step, blocked(what, O.stack_missing(c, deps), " is declared but not a dependency"), 68 List.append(&2, P.Step, blocked(what, O.languages_missing(c, exts), " files have no declared language"), 69 blocked(what, O.sdk_missing(c, xs, deps), " is the SDK of a model API it calls but not a dependency")))) 70 71 def checks_with(cs: List<&2, Ct.Container>, +root: String, +xs: List<&2, Ex.External>, +go: List<&2, String>, +others: List<&2, String>) -> IO(List<&2, P.Step>): 72 match cs: 73 case []: 74 IO.pure(List<&2, P.Step>, []) 75 case c <> rest: 76 do IO>: 77 one : List<&2, P.Step> <- check(c, root, xs, go, others) 78 more : List<&2, P.Step> <- checks_with(rest, root, xs, go, others) 79 return List.append(&2, P.Step, one, more) 80 81 # The stack of every container built from the repository at `root`. 82 def checks(+root: String, +cs: List<&2, Ct.Container>, +xs: List<&2, Ex.External>) -> IO(List<&2, P.Step>): 83 do IO>: 84 mod : String <- X.read(root ++ "/go.mod") 85 checks_with(O.built(cs), root, xs, O.go_deps(String.lines(mod)), O.sources(O.built(cs)))