Monorepo management for opam overlays
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(monopam): replace import/export with add/remove/publish/test

- Rename import → add with opam URL syntax (URL#ref)
- Rename export → publish with --opam-repo flag
- Add remove command for subtree removal
- Add test command for running tests across the monorepo

+387 -124
+101
bin/cmd_add.ml
··· 1 + open Cmdliner 2 + 3 + let cmd = 4 + let doc = "Add a package from a git URL or lock file" in 5 + let man = 6 + [ 7 + `S Manpage.s_description; 8 + `P 9 + "Adds a package from a git repository into the current project. Uses \ 10 + git subtree to bring the package source into your project while \ 11 + preserving its commit history."; 12 + `S "SOURCE FORMATS"; 13 + `P "The SOURCE argument can be:"; 14 + `I ("Git URL", "A git repository URL (opam syntax: URL#ref)"); 15 + `I ("Lock file", "A mono.lock file path (adds all entries)"); 16 + `S "URL PARSING (opam syntax)"; 17 + `P "Git URLs support the opam URL syntax with optional ref:"; 18 + `I ("- ", "https://github.com/mirage/eio.git → ref=main"); 19 + `I ("- ", "https://github.com/ocaml/dune.git#v3.17.0 → ref=v3.17.0"); 20 + `I ("- ", "git+https://github.com/foo/bar#main → ref=main"); 21 + `S "LOCK FILE"; 22 + `P 23 + "When you add a package, monopam creates or updates a mono.lock file \ 24 + in the current directory. This file records:"; 25 + `I ("- ", "The source URL of each added package"); 26 + `I ("- ", "The exact commit SHA that was added"); 27 + `P 28 + "You can use a lock file as SOURCE to re-add all packages, ensuring \ 29 + reproducible builds."; 30 + `S Manpage.s_examples; 31 + `Pre "# From git URL (opam syntax: URL#ref)"; 32 + `Pre "monopam add https://github.com/mirage/eio.git"; 33 + `Pre "monopam add https://github.com/mirage/eio.git my-eio"; 34 + `Pre "monopam add https://github.com/ocaml/dune.git#v3.17.0"; 35 + `Pre "monopam add https://github.com/ocaml/dune.git#main"; 36 + `Pre ""; 37 + `Pre "# From lock file (adds all entries)"; 38 + `Pre "monopam add mono.lock"; 39 + `S Manpage.s_see_also; 40 + `P "$(b,monopam remove)(1), $(b,monopam publish)(1)"; 41 + ] 42 + in 43 + let info = Cmd.info "add" ~doc ~man in 44 + let source_arg = 45 + let doc = "Git URL (opam syntax: URL#ref) or path to mono.lock file." in 46 + Arg.(required & pos 0 (some string) None & info [] ~docv:"SOURCE" ~doc) 47 + in 48 + let dir_arg = 49 + let doc = "Directory name (default: inferred from URL)." in 50 + Arg.(value & pos 1 (some string) None & info [] ~docv:"DIR" ~doc) 51 + in 52 + let dry_run_arg = 53 + let doc = "Show what would be added without making changes." in 54 + Arg.(value & flag & info [ "dry-run"; "n" ] ~doc) 55 + in 56 + let run source dir dry_run () = 57 + Eio_main.run @@ fun env -> 58 + let fs = Eio.Stdenv.fs env in 59 + let proc = Eio.Stdenv.process_mgr env in 60 + let target = Fpath.v (Sys.getcwd ()) in 61 + (* Auto-detect if source is a lock file or git URL *) 62 + let source = 63 + if String.ends_with ~suffix:".lock" source then 64 + Monopam.Import.Lock_file (Fpath.v source) 65 + else 66 + (* Parse opam URL syntax: URL#ref *) 67 + let url, ref_ = 68 + match String.rindex_opt source '#' with 69 + | Some i -> 70 + let url = String.sub source 0 i in 71 + let ref_ = 72 + String.sub source (i + 1) (String.length source - i - 1) 73 + in 74 + (url, Some ref_) 75 + | None -> (source, None) 76 + in 77 + Monopam.Import.Git_url { url; branch = None; ref_ } 78 + in 79 + match 80 + Monopam.Import.run ~proc ~fs ~target ~source ~name:dir ~dry_run () 81 + with 82 + | Ok results -> 83 + if results = [] then Fmt.pr "Nothing added.@." 84 + else begin 85 + Fmt.pr "Added %d subtree%s:@." (List.length results) 86 + (if List.length results = 1 then "" else "s"); 87 + List.iter 88 + (fun r -> 89 + Fmt.pr " %s (%s)@." r.Monopam.Import.name 90 + (String.sub r.Monopam.Import.commit 0 91 + (min 7 (String.length r.Monopam.Import.commit)))) 92 + results 93 + end; 94 + `Ok () 95 + | Error e -> 96 + Fmt.epr "Error: %s@." e; 97 + `Error (false, "add failed") 98 + in 99 + Cmd.v info 100 + Term.( 101 + ret (const run $ source_arg $ dir_arg $ dry_run_arg $ Common.logging_term))
+28 -26
bin/cmd_export.ml bin/cmd_publish.ml
··· 1 1 open Cmdliner 2 2 3 3 let cmd = 4 - let doc = "Export packages to opam-repo and checkouts" in 4 + let doc = "Publish packages to opam-repo" in 5 5 let man = 6 6 [ 7 7 `S Manpage.s_description; 8 8 `P 9 - "Exports packages from the current directory to an opam-repo overlay \ 10 - and to local checkouts. Works from any OCaml project directory, not \ 11 - just the main monorepo."; 9 + "Publishes packages from the current directory to an opam-repo \ 10 + overlay. Works from any OCaml project directory, not just the main \ 11 + monorepo."; 12 12 `S "WHAT IT DOES"; 13 13 `I ("1.", "Scans the current directory for dune-project and opam files"); 14 14 `I ··· 19 19 `I ("5.", "Stages and commits changes in opam-repo (unless --no-commit)"); 20 20 `I 21 21 ("6.", "Exports subtrees to checkouts (when running from main monorepo)"); 22 - `S "WORKING FROM SUBSET PROJECTS"; 22 + `S "WORKING FROM ANY PROJECT"; 23 23 `P 24 - "The export command works from any directory containing OCaml \ 25 - packages. You can export from:"; 24 + "The publish command works from any directory containing OCaml \ 25 + packages. You can publish from:"; 26 26 `I ("- ", "The main monorepo (mono/) - also exports to checkouts"); 27 27 `I ("- ", "A standalone project checkout - opam files only"); 28 28 `I ("- ", "A subtree directory - opam files only"); 29 29 `P 30 - "By default, it exports to ../opam-repo relative to the current \ 31 - directory. Use --target to specify a different location."; 30 + "By default, it publishes to ../opam-repo relative to the current \ 31 + directory. Use --opam-repo to specify a different location."; 32 32 `S Manpage.s_examples; 33 - `Pre "monopam export # Export all packages"; 34 - `Pre "monopam export aos clcw # Export specific packages"; 35 - `Pre "monopam export --dry-run # Show what would be exported"; 33 + `Pre "monopam publish # Publish all packages"; 34 + `Pre "monopam publish eio fmt # Publish specific packages"; 35 + `Pre "monopam publish --dry-run # Show what would be published"; 36 36 `Pre 37 - "monopam export --target ~/opam-repo # Export to a specific opam-repo"; 38 - `Pre "monopam export --no-checkouts # Skip checkout export"; 37 + "monopam publish --opam-repo ~/opam-repo # Publish to a specific \ 38 + opam-repo"; 39 + `Pre "monopam publish --no-checkouts # Skip checkout export"; 39 40 `S Manpage.s_see_also; 40 - `P "$(b,monopam import)(1), $(b,monopam push)(1), $(b,monopam status)(1)"; 41 + `P "$(b,monopam add)(1), $(b,monopam push)(1), $(b,monopam status)(1)"; 41 42 ] 42 43 in 43 - let info = Cmd.info "export" ~doc ~man in 44 - let target_arg = 44 + let info = Cmd.info "publish" ~doc ~man in 45 + let opam_repo_arg = 45 46 let doc = 46 47 "Target opam-repo directory. Defaults to ../opam-repo or the value from \ 47 48 config." 48 49 in 49 - Arg.(value & opt (some string) None & info [ "target" ] ~docv:"PATH" ~doc) 50 + Arg.( 51 + value & opt (some string) None & info [ "opam-repo" ] ~docv:"PATH" ~doc) 50 52 in 51 53 let no_commit_arg = 52 54 let doc = "Skip automatic git commit in opam-repo." in 53 55 Arg.(value & flag & info [ "no-commit" ] ~doc) 54 56 in 55 57 let dry_run_arg = 56 - let doc = "Show what would be exported without making changes." in 58 + let doc = "Show what would be published without making changes." in 57 59 Arg.(value & flag & info [ "dry-run"; "n" ] ~doc) 58 60 in 59 61 let no_checkouts_arg = 60 - let doc = "Skip exporting to checkouts (only export opam files)." in 62 + let doc = "Skip exporting to checkouts (only publish opam files)." in 61 63 Arg.(value & flag & info [ "no-checkouts" ] ~doc) 62 64 in 63 - let run packages target no_commit dry_run no_checkouts () = 65 + let run packages opam_repo no_commit dry_run no_checkouts () = 64 66 Eio_main.run @@ fun env -> 65 67 let fs = Eio.Stdenv.fs env in 66 68 let proc = Eio.Stdenv.process_mgr env in ··· 70 72 let config_opt = Result.to_option (Common.load_config env) in 71 73 (* Determine target opam-repo *) 72 74 let target = 73 - match target with 75 + match opam_repo with 74 76 | Some path -> 75 77 if Filename.is_relative path then Fpath.(source / path) 76 78 else Fpath.v path ··· 80 82 | None -> Fpath.(parent source / "opam-repo")) 81 83 in 82 84 if dry_run then 83 - Fmt.pr "Dry run: exporting from %a to %a@." Fpath.pp source Fpath.pp 85 + Fmt.pr "Dry run: publishing from %a to %a@." Fpath.pp source Fpath.pp 84 86 target; 85 - (* Export opam files *) 87 + (* Publish opam files *) 86 88 match 87 89 Monopam.Opam_sync.run_from_cwd ~fs ~proc ~source ~target ~packages 88 90 ~no_commit ~dry_run () 89 91 with 90 92 | Error (`Config_error e) -> 91 93 Fmt.epr "Error: %s@." e; 92 - `Error (false, "export failed") 94 + `Error (false, "publish failed") 93 95 | Ok opam_result -> 94 96 Fmt.pr "%a@." Monopam.Opam_sync.pp opam_result; 95 97 (* Also export to checkouts if we have config and not disabled *) ··· 113 115 Cmd.v info 114 116 Term.( 115 117 ret 116 - (const run $ Common.packages_arg $ target_arg $ no_commit_arg 118 + (const run $ Common.packages_arg $ opam_repo_arg $ no_commit_arg 117 119 $ dry_run_arg $ no_checkouts_arg $ Common.logging_term))
-96
bin/cmd_import.ml
··· 1 - open Cmdliner 2 - 3 - let cmd = 4 - let doc = "Import a package from a git repository" in 5 - let man = 6 - [ 7 - `S Manpage.s_description; 8 - `P 9 - "Imports a package from a git repository into the current project. \ 10 - Uses git subtree to bring the package source into your monorepo while \ 11 - preserving its commit history."; 12 - `S "SOURCE FORMATS"; 13 - `P "The SOURCE argument can be:"; 14 - `I ("Git URL", "A git repository URL (https or git@ format)"); 15 - `I ("Lock file", "A mono.lock file path (with --from-lock)"); 16 - `S "LOCK FILE"; 17 - `P 18 - "When you import a package, monopam creates or updates a mono.lock \ 19 - file in the target directory. This file records:"; 20 - `I ("- ", "The source URL of each imported package"); 21 - `I ("- ", "The exact commit SHA that was imported"); 22 - `I ("- ", "The branch or ref that was specified"); 23 - `P 24 - "You can use --from-lock to re-import all packages from an existing \ 25 - lock file, ensuring reproducible builds."; 26 - `S Manpage.s_examples; 27 - `Pre "monopam import https://github.com/mirage/eio.git"; 28 - `Pre "monopam import https://github.com/mirage/eio.git --name ocaml-eio"; 29 - `Pre "monopam import https://github.com/ocaml/dune.git --ref v3.17.0"; 30 - `Pre "monopam import mono.lock --from-lock"; 31 - `S Manpage.s_see_also; 32 - `P "$(b,monopam export)(1), $(b,monopam sync)(1)"; 33 - ] 34 - in 35 - let info = Cmd.info "import" ~doc ~man in 36 - let source_arg = 37 - let doc = 38 - "Git URL to import, or path to mono.lock file (with --from-lock)." 39 - in 40 - Arg.(required & pos 0 (some string) None & info [] ~docv:"SOURCE" ~doc) 41 - in 42 - let name_arg = 43 - let doc = "Override the subtree directory name." in 44 - Arg.(value & opt (some string) None & info [ "name" ] ~docv:"NAME" ~doc) 45 - in 46 - let branch_arg = 47 - let doc = "Branch to import (default: main)." in 48 - Arg.( 49 - value 50 - & opt (some string) None 51 - & info [ "branch"; "b" ] ~docv:"BRANCH" ~doc) 52 - in 53 - let ref_arg = 54 - let doc = "Specific commit or tag to pin." in 55 - Arg.(value & opt (some string) None & info [ "ref"; "r" ] ~docv:"REF" ~doc) 56 - in 57 - let from_lock_arg = 58 - let doc = "Treat SOURCE as a mono.lock file path and import all entries." in 59 - Arg.(value & flag & info [ "from-lock" ] ~doc) 60 - in 61 - let dry_run_arg = 62 - let doc = "Show what would be imported without making changes." in 63 - Arg.(value & flag & info [ "dry-run"; "n" ] ~doc) 64 - in 65 - let run source name branch ref_ from_lock dry_run () = 66 - Eio_main.run @@ fun env -> 67 - let fs = Eio.Stdenv.fs env in 68 - let proc = Eio.Stdenv.process_mgr env in 69 - let target = Fpath.v (Sys.getcwd ()) in 70 - let source = 71 - if from_lock then Monopam.Import.Lock_file (Fpath.v source) 72 - else Monopam.Import.Git_url { url = source; branch; ref_ } 73 - in 74 - match Monopam.Import.run ~proc ~fs ~target ~source ~name ~dry_run () with 75 - | Ok results -> 76 - if results = [] then Fmt.pr "Nothing imported.@." 77 - else begin 78 - Fmt.pr "Imported %d subtree%s:@." (List.length results) 79 - (if List.length results = 1 then "" else "s"); 80 - List.iter 81 - (fun r -> 82 - Fmt.pr " %s (%s)@." r.Monopam.Import.name 83 - (String.sub r.Monopam.Import.commit 0 84 - (min 7 (String.length r.Monopam.Import.commit)))) 85 - results 86 - end; 87 - `Ok () 88 - | Error e -> 89 - Fmt.epr "Error: %s@." e; 90 - `Error (false, "import failed") 91 - in 92 - Cmd.v info 93 - Term.( 94 - ret 95 - (const run $ source_arg $ name_arg $ branch_arg $ ref_arg 96 - $ from_lock_arg $ dry_run_arg $ Common.logging_term))
+130
bin/cmd_remove.ml
··· 1 + open Cmdliner 2 + 3 + let cmd = 4 + let doc = "Remove a subtree from the current project" in 5 + let man = 6 + [ 7 + `S Manpage.s_description; 8 + `P 9 + "Removes a subtree directory from the current project and updates the \ 10 + mono.lock file."; 11 + `S "WHAT IT DOES"; 12 + `I ("1.", "Checks if the directory exists"); 13 + `I ("2.", "Checks for uncommitted changes (unless --force)"); 14 + `I ("3.", "Removes the directory"); 15 + `I ("4.", "Updates mono.lock to remove the entry"); 16 + `I ("5.", "Stages and commits the removal (unless --no-commit)"); 17 + `S Manpage.s_examples; 18 + `Pre "monopam remove eio"; 19 + `Pre "monopam remove my-eio --force"; 20 + `Pre "monopam remove eio --dry-run"; 21 + `S Manpage.s_see_also; 22 + `P "$(b,monopam add)(1)"; 23 + ] 24 + in 25 + let info = Cmd.info "remove" ~doc ~man in 26 + let dir_arg = 27 + let doc = "Directory to remove." in 28 + Arg.(required & pos 0 (some string) None & info [] ~docv:"DIR" ~doc) 29 + in 30 + let force_arg = 31 + let doc = "Remove even if there are uncommitted changes." in 32 + Arg.(value & flag & info [ "force"; "f" ] ~doc) 33 + in 34 + let no_commit_arg = 35 + let doc = "Skip automatic git commit." in 36 + Arg.(value & flag & info [ "no-commit" ] ~doc) 37 + in 38 + let dry_run_arg = 39 + let doc = "Show what would be removed without making changes." in 40 + Arg.(value & flag & info [ "dry-run"; "n" ] ~doc) 41 + in 42 + let run dir force no_commit dry_run () = 43 + Eio_main.run @@ fun env -> 44 + let fs = Eio.Stdenv.fs env in 45 + let proc = Eio.Stdenv.process_mgr env in 46 + let target = Fpath.v (Sys.getcwd ()) in 47 + let dir_path = Fpath.(target / dir) in 48 + let eio_path = Eio.Path.(fs / Fpath.to_string dir_path) in 49 + (* Check if directory exists *) 50 + (match Eio.Path.kind ~follow:true eio_path with 51 + | `Directory -> () 52 + | _ -> 53 + Fmt.epr "Error: %s is not a directory@." dir; 54 + exit 1 55 + | exception _ -> 56 + Fmt.epr "Error: directory %s does not exist@." dir; 57 + exit 1); 58 + (* Check for uncommitted changes unless --force *) 59 + if not force then begin 60 + let target_eio = Eio.Path.(fs / Fpath.to_string target) in 61 + let buf = Buffer.create 256 in 62 + Eio.Switch.run @@ fun sw -> 63 + let child = 64 + Eio.Process.spawn proc ~sw ~cwd:target_eio 65 + ~stdout:(Eio.Flow.buffer_sink buf) 66 + [ "git"; "status"; "--porcelain"; "--"; dir ] 67 + in 68 + match Eio.Process.await child with 69 + | `Exited 0 -> 70 + let output = Buffer.contents buf in 71 + if String.trim output <> "" then begin 72 + Fmt.epr 73 + "Error: %s has uncommitted changes. Use --force to remove \ 74 + anyway.@." 75 + dir; 76 + Fmt.epr "Changes:@.%s@." output; 77 + exit 1 78 + end 79 + | _ -> () 80 + end; 81 + if dry_run then begin 82 + Fmt.pr "Would remove: %s@." dir; 83 + `Ok () 84 + end 85 + else begin 86 + (* Remove the directory *) 87 + Fmt.pr "Removing %s...@." dir; 88 + let target_eio = Eio.Path.(fs / Fpath.to_string target) in 89 + Eio.Switch.run @@ fun sw -> 90 + let child = 91 + Eio.Process.spawn proc ~sw ~cwd:target_eio [ "rm"; "-rf"; dir ] 92 + in 93 + (match Eio.Process.await child with 94 + | `Exited 0 -> () 95 + | _ -> 96 + Fmt.epr "Error: failed to remove %s@." dir; 97 + exit 1); 98 + (* Update mono.lock *) 99 + (match Monopam.Mono_lock.load ~fs target with 100 + | Ok lock -> ( 101 + let lock' = Monopam.Mono_lock.remove lock ~name:dir in 102 + match Monopam.Mono_lock.save ~fs target lock' with 103 + | Ok () -> Fmt.pr "Updated mono.lock@." 104 + | Error e -> Fmt.epr "Warning: failed to update mono.lock: %s@." e) 105 + | Error _ -> ()); 106 + (* Commit unless --no-commit *) 107 + if not no_commit then begin 108 + Eio.Switch.run @@ fun sw -> 109 + let child = 110 + Eio.Process.spawn proc ~sw ~cwd:target_eio 111 + [ "git"; "add"; "-A"; dir; "mono.lock" ] 112 + in 113 + (match Eio.Process.await child with `Exited 0 -> () | _ -> ()); 114 + let child = 115 + Eio.Process.spawn proc ~sw ~cwd:target_eio 116 + [ "git"; "commit"; "-m"; Fmt.str "Remove subtree %s" dir ] 117 + in 118 + match Eio.Process.await child with 119 + | `Exited 0 -> Fmt.pr "Committed removal.@." 120 + | _ -> Fmt.pr "No changes to commit.@." 121 + end; 122 + Fmt.pr "Removed %s@." dir; 123 + `Ok () 124 + end 125 + in 126 + Cmd.v info 127 + Term.( 128 + ret 129 + (const run $ dir_arg $ force_arg $ no_commit_arg $ dry_run_arg 130 + $ Common.logging_term))
+120
bin/cmd_test.ml
··· 1 + open Cmdliner 2 + 3 + let src = Logs.Src.create "monopam.test" ~doc:"Test runner" 4 + 5 + module Log = (val Logs.src_log src) 6 + 7 + type result = { 8 + name : string; 9 + duration : float; 10 + status : [ `Ok | `Slow | `Fail | `Timeout ]; 11 + } 12 + 13 + let run_test ~timeout dir = 14 + let start = Unix.gettimeofday () in 15 + let cmd = 16 + Printf.sprintf "timeout %ds opam exec -- dune test %s 2>&1" timeout dir 17 + in 18 + let ic = Unix.open_process_in cmd in 19 + let output = In_channel.input_all ic in 20 + let exit_status = Unix.close_process_in ic in 21 + let duration = Unix.gettimeofday () -. start in 22 + let status = 23 + match exit_status with 24 + | Unix.WEXITED 0 -> if duration > 2.0 then `Slow else `Ok 25 + | Unix.WEXITED 124 -> `Timeout 26 + | _ -> `Fail 27 + in 28 + Log.debug (fun m -> m "%s: %s" dir output); 29 + { name = dir; duration; status } 30 + 31 + let find_test_dirs () = 32 + let cmd = 33 + "find . -name 'test' -type d -path '*/test' | grep -v _build | grep -v \ 34 + _opam | sort" 35 + in 36 + let ic = Unix.open_process_in cmd in 37 + let lines = In_channel.input_all ic |> String.split_on_char '\n' in 38 + let _ = Unix.close_process_in ic in 39 + lines 40 + |> List.filter (fun s -> s <> "") 41 + |> List.filter_map (fun path -> 42 + match String.split_on_char '/' path with 43 + | [ "."; dir; "test" ] -> Some dir 44 + | _ -> None) 45 + |> List.sort_uniq String.compare 46 + 47 + let status_to_string = function 48 + | `Ok -> "OK" 49 + | `Slow -> "SLOW" 50 + | `Fail -> "FAIL" 51 + | `Timeout -> "TIMEOUT" 52 + 53 + let status_style = function 54 + | `Ok -> Tty.Style.(fg (Tty.Color.ansi `Green)) 55 + | `Slow -> Tty.Style.(fg (Tty.Color.ansi `Yellow)) 56 + | `Fail -> Tty.Style.(fg (Tty.Color.ansi `Red)) 57 + | `Timeout -> Tty.Style.(fg (Tty.Color.ansi `Red)) 58 + 59 + let styled style s = Fmt.str "%a" (Tty.Style.styled style Fmt.string) s 60 + 61 + let run timeout filter () = 62 + let dirs = find_test_dirs () in 63 + let dirs = 64 + match filter with 65 + | [] -> dirs 66 + | fs -> List.filter (fun d -> List.mem d fs) dirs 67 + in 68 + Log.info (fun m -> m "Testing %d directories" (List.length dirs)); 69 + let results = List.map (run_test ~timeout) dirs in 70 + let pp_result r = 71 + let status_str = 72 + styled (status_style r.status) (status_to_string r.status) 73 + in 74 + Fmt.pr "%-30s %s (%.2fs)@." r.name status_str r.duration 75 + in 76 + List.iter pp_result results; 77 + let slow = List.filter (fun r -> r.status = `Slow) results in 78 + let fail = 79 + List.filter (fun r -> r.status = `Fail || r.status = `Timeout) results 80 + in 81 + Fmt.pr "@."; 82 + if fail <> [] then 83 + Fmt.pr "%s %d tests failed@." 84 + (styled (status_style `Fail) "✗") 85 + (List.length fail); 86 + if slow <> [] then 87 + Fmt.pr "%s %d tests slow (>2s)@." 88 + (styled (status_style `Slow) "⚠") 89 + (List.length slow); 90 + if fail = [] && slow = [] then 91 + Fmt.pr "%s All %d tests OK@." 92 + (styled (status_style `Ok) "✓") 93 + (List.length results); 94 + if fail <> [] then `Error (false, "some tests failed") else `Ok () 95 + 96 + let cmd = 97 + let doc = "Run tests across the monorepo" in 98 + let man = 99 + [ 100 + `S Manpage.s_description; 101 + `P 102 + "Runs $(b,dune test) on each directory in the monorepo and reports \ 103 + which pass, fail, or are slow (>2s)."; 104 + `S "EXAMPLES"; 105 + `Pre "monopam test"; 106 + `Pre "monopam test ocaml-qemu ocaml-bloom"; 107 + `Pre "monopam test --timeout 60"; 108 + ] 109 + in 110 + let info = Cmd.info "test" ~doc ~man in 111 + let timeout_arg = 112 + let doc = "Timeout in seconds per test directory." in 113 + Arg.(value & opt int 30 & info [ "timeout"; "t" ] ~docv:"SECONDS" ~doc) 114 + in 115 + let filter_arg = 116 + let doc = "Only test specific directories." in 117 + Arg.(value & pos_all string [] & info [] ~docv:"DIR" ~doc) 118 + in 119 + Cmd.v info 120 + Term.(ret (const run $ timeout_arg $ filter_arg $ Common.logging_term))
+1
bin/dune
··· 12 12 logs.fmt 13 13 logs.cli 14 14 vlog 15 + tty 15 16 memtrace 16 17 monopam-info))
+7 -2
bin/main.ml
··· 23 23 `Pre "monopam push"; 24 24 `S "CORE WORKFLOW"; 25 25 `P "Commands match the git mental model:"; 26 + `I ("$(b,monopam add)", "Add a package (subtree) from a git URL"); 27 + `I ("$(b,monopam remove)", "Remove a package from the project"); 26 28 `I ("$(b,monopam pull)", "Fetch and merge upstream changes into mono/"); 27 29 `I ("$(b,monopam push)", "Push your mono/ changes to upstream remotes"); 28 30 `I ("$(b,monopam fetch)", "Fetch upstream changes without merging"); 29 31 `I ("$(b,monopam status)", "Show what's out of sync"); 30 32 `I ("$(b,monopam diff)", "Show diff between mono/ and upstream"); 33 + `I ("$(b,monopam publish)", "Publish packages to opam-repo"); 31 34 `S "TYPICAL SESSION"; 32 35 `Pre 33 36 "# Start by pulling latest\n\ ··· 59 62 Cmd_diff.cmd; 60 63 Cmd_init.cmd; 61 64 Cmd_clean.cmd; 62 - Cmd_export.cmd; 63 - Cmd_import.cmd; 65 + Cmd_add.cmd; 66 + Cmd_remove.cmd; 67 + Cmd_publish.cmd; 64 68 Cmd_verse.cmd; 69 + Cmd_test.cmd; 65 70 ] 66 71 67 72 let () =