1 import Base 2 import ../release/type.bend as Rl 3 import ../release/ops.bend as RlO 4 import ../release/plugin/ops.bend as PlO 5 import ./type.bend as P 6 7 def visibility(p: Bool) -> String: 8 match p: 9 case True{}: 10 "public" 11 case False{}: 12 "private" 13 14 # ---- the repository: no dead Bend and hub paths the hub takes come first, then GitHub. What else the 15 # repository holds is its containers' business, checked against their declared stacks ---- 16 def repo_steps(+fs: List<&2, String>, +reached: List<&2, String>) -> List<&2, P.Step>: 17 List.append(&2, P.Step, 18 Bool.pick(List<&2, P.Step>, String.is_empty(RlO.dead(fs, reached)), [], 19 [P.Blocked{"repository", RlO.dead(fs, reached) ++ " is dead code: no entry reaches it"}]), 20 Bool.pick(List<&2, P.Step>, String.is_empty(RlO.hub_refused(reached)), [], 21 [P.Blocked{"repository", RlO.hub_refused(reached) ++ ": the Bend hub refuses this path (segments of letters, digits and _)"}])) 22 23 def remote_step(+repo: Rl.Repository, rm: P.Remote) -> P.Step: 24 match repo rm: 25 case Rl.Repository{+name, +public} P.Remote{+origin, +vis, clean, pushed}: 26 Bool.pick(P.Step, String.contains(origin, name), 27 Bool.pick(P.Step, String.eq(vis, visibility(public)), 28 Bool.pick(P.Step, clean, 29 Bool.pick(P.Step, pushed, P.Current{"github " ++ name}, P.Push{name}), 30 P.Blocked{"github " ++ name, "uncommitted changes"}), 31 P.Blocked{"github " ++ name, "GitHub says " ++ Bool.pick(String, String.is_empty(vis), "nothing (unreachable)", vis) ++ ", declared " ++ visibility(public)}), 32 P.Blocked{"github " ++ name, "origin is " ++ origin}) 33 34 # ---- each package: already published as it is, new, or changed without a new version ---- 35 def package_step(p: Rl.Package, h: P.Hub) -> P.Step: 36 match p: 37 case Rl.Package{+n, +v, e, _}: 38 match h: 39 case P.Same{}: 40 P.Current{"package " ++ n ++ "@" ++ v} 41 case P.Unpublished{}: 42 P.Publish{n, v, e} 43 case P.Differs{f}: 44 P.Blocked{"package " ++ n ++ "@" ++ v, f ++ " changed since it was published; give it a new version"} 45 46 def package_steps(ps: List<&2, Rl.Package>, hs: List<&2, P.Hub>) -> List<&2, P.Step>: 47 match ps: 48 case []: 49 [] 50 case p <> prest: 51 match hs: 52 case []: 53 [] 54 case h <> hrest: 55 package_step(p, h) <> package_steps(prest, hrest) 56 57 # ---- each plugin: released as it builds, new, or changed without a new version ---- 58 def plugin_step(g: Rl.Plugin, t: P.Tag) -> P.Step: 59 match g t: 60 case Rl.Plugin{+n, ver, _, _, Rl.Repository{+r, _}} P.Tag{repo, tagged, +built, valid, differs}: 61 +v = PlO.version_show(ver) 62 +what = "plugin " ++ n ++ "@" ++ v 63 Bool.pick(P.Step, Bool.not(PlO.name_ok(n)), P.Blocked{what, "the name breaks Agent Plugins 1.0's rule: 1 to 64 of a-z, 0-9, . and -, a letter or digit at each end, no -- or .."}, 64 Bool.pick(P.Step, String.is_empty(built), P.Blocked{what, "does not build"}, 65 Bool.pick(P.Step, Bool.not(String.eq(built, v)), P.Blocked{what, "builds as version " ++ built}, 66 Bool.pick(P.Step, Bool.not(valid), P.Blocked{what, "claude plugin validate rejects the build"}, 67 Bool.pick(P.Step, Bool.not(repo), P.ReleasePlugin{n, v, r, True{}}, 68 Bool.pick(P.Step, Bool.not(tagged), P.ReleasePlugin{n, v, r, False{}}, 69 Bool.pick(P.Step, differs, P.Blocked{what, "changed since " ++ n ++ "-v" ++ v ++ " was released; give it a new version"}, 70 P.Current{what}))))))) 71 72 def plugin_steps(gs: List<&2, Rl.Plugin>, ts: List<&2, P.Tag>) -> List<&2, P.Step>: 73 match gs: 74 case []: 75 [] 76 case g <> grest: 77 match ts: 78 case []: 79 [] 80 case t <> trest: 81 plugin_step(g, t) <> plugin_steps(grest, trest) 82 83 # The plan: what a deploy would do, from the release and what was observed. 84 def plan(r: Rl.Release, o: P.Observed) -> List<&2, P.Step>: 85 match r o: 86 case Rl.Release{repo, ps, gs} P.Observed{+fs, +reached, hs, rm, ts}: 87 List.append(&2, P.Step, repo_steps(fs, reached), List.append(&2, P.Step, package_steps(ps, hs), 88 List.append(&2, P.Step, plugin_steps(gs, ts), [remote_step(repo, rm)]))) 89 90 def blocked(s: P.Step) -> Bool: 91 match s: 92 case P.Blocked{_, _}: 93 True{} 94 case _: 95 False{} 96 97 def any_blocked(ss: List<&2, P.Step>) -> Bool: 98 match ss: 99 case []: 100 False{} 101 case s <> rest: 102 blocked(s) || any_blocked(rest) 103 104 def show(s: P.Step) -> String: 105 match s: 106 case P.Current{w}: 107 "current " ++ w 108 case P.Publish{n, v, _}: 109 "publish " ++ n ++ "@" ++ v 110 case P.Push{r}: 111 "push github " ++ r 112 case P.ReleasePlugin{+n, +v, r, create}: 113 "release plugin " ++ n ++ "@" ++ v ++ " to marketplace github " ++ r ++ " as tag " ++ n ++ "-v" ++ v ++ Bool.pick(String, create, " (a new repository)", "") 114 case P.Launch{l, _, _, _}: 115 "launch " ++ l 116 case P.Install{_, t}: 117 "install " ++ t 118 case P.BuildNext{f, +tree, d}: 119 "build " ++ f ++ " @ " ++ String.take(tree, 7n) ++ " (Next.js) into " ++ d 120 case P.Rebuild{h, _, _}: 121 "rebuild nix-darwin " ++ h 122 case P.ServePort{p, t, _}: 123 "serve :" ++ U32.show(p) ++ " -> " ++ t 124 case P.Change{w, now, want}: 125 "change " ++ w ++ ": " ++ now ++ " -> " ++ want 126 case P.Blocked{w, why}: 127 "blocked " ++ w ++ ": " ++ why 128 129 def show_all(ss: List<&2, P.Step>) -> String: 130 match ss: 131 case []: 132 "" 133 case s <> rest: 134 show(s) ++ "\n" ++ show_all(rest)