defmodule Mix.Tasks.Spec.Hub.Sync do use Mix.Task @shortdoc "Sync a local request folder into a spec-hub under requests//" @moduledoc """ Usage: mix spec.hub.sync --id --project --hub ../lang-spec-hub Copies `work/spec_requests/` to `/requests//` (creating directories as needed). Does not copy inbound/outbound transport unless present; preserves structure. Re-renders thread.md before copy to ensure up-to-date. """ @impl true def run(argv) do {opts, _, _} = OptionParser.parse(argv, switches: [ id: :string, project: :string, hub: :string, no_export: :boolean, no_index: :boolean ] ) id = req!(opts, :id) project = req!(opts, :project) hub = req!(opts, :hub) # Ensure thread is current Mix.Task.run("spec.thread.render", ["--id", id]) src = Path.join([File.cwd!(), "work", "spec_requests", id]) File.dir?(src) || Mix.raise("Missing local request folder: #{src}") dest = Path.join([hub, "requests", project, id]) File.mkdir_p!(dest) copy_tree!(src, dest) unless Keyword.get(opts, :no_export, false) do Mix.Task.run("spec.export.jsonld", ["--id", id, "--project", project, "--hub", hub]) end unless Keyword.get(opts, :no_index, false) do Mix.Task.run("spec.index", ["--hub", hub]) end Mix.shell().info( "Synced #{id} -> #{dest}" <> ((Keyword.get(opts, :no_export, false) && "") || " (JSON-LD exported") <> ((Keyword.get(opts, :no_index, false) && ")") || ", index refreshed)") ) end defp copy_tree!(src, dst) do for path <- Path.wildcard(Path.join(src, "**/*"), match_dot: true) do rel = Path.relative_to(path, src) target = Path.join(dst, rel) cond do File.dir?(path) -> File.mkdir_p!(target) File.regular?(path) -> File.mkdir_p!(Path.dirname(target)) File.cp!(path, target) true -> :ok end end end defp req!(opts, key), do: Keyword.get(opts, key) || Mix.raise("Missing --#{key}") end