defmodule Mix.Tasks.Firebird.Target.Clean do @moduledoc """ Clean WASM build artifacts produced by the Firebird compiler. Removes compiled `.wat`, `.wasm`, source map, and manifest files from the output directory. Optionally removes the entire output directory. ## Usage # Clean default output directory (_build/wasm) mix firebird.target.clean # Clean a specific output directory mix firebird.target.clean --output priv/wasm # Remove the entire output directory mix firebird.target.clean --all # Dry run โ€” show what would be deleted mix firebird.target.clean --dry-run # Clean only specific artifact types mix firebird.target.clean --wat # only .wat files mix firebird.target.clean --wasm # only .wasm files mix firebird.target.clean --manifests # only manifests ## What Gets Cleaned - `.wat` files (WebAssembly Text Format) - `.wasm` files (WebAssembly binary) - `.source_map.json` files (source maps) - `firebird_manifest.json` (compilation manifest) - `firebird_target.manifest` (incremental build manifest) - `firebird_content_hashes.manifest` (content hash cache) """ use Mix.Task @shortdoc "Clean WASM compilation artifacts" @switches [ output: :string, all: :boolean, dry_run: :boolean, wat: :boolean, wasm: :boolean, manifests: :boolean, quiet: :boolean ] @aliases [ o: :output, q: :quiet ] @impl Mix.Task def run(args) do {opts, _rest, _invalid} = OptionParser.parse(args, switches: @switches, aliases: @aliases) output_dir = Keyword.get(opts, :output, default_output_dir()) remove_all = Keyword.get(opts, :all, false) dry_run = Keyword.get(opts, :dry_run, false) quiet = Keyword.get(opts, :quiet, false) # Determine which artifact types to clean wat_only = Keyword.get(opts, :wat, false) wasm_only = Keyword.get(opts, :wasm, false) manifests_only = Keyword.get(opts, :manifests, false) # If no specific type is selected, clean all clean_all_types = not wat_only and not wasm_only and not manifests_only unless quiet do if dry_run do Mix.shell().info("๐Ÿงน Firebird WASM Clean โ€” Dry Run") else Mix.shell().info("๐Ÿงน Firebird WASM Clean") end Mix.shell().info("") end if remove_all do clean_entire_directory(output_dir, dry_run, quiet) else files_removed = clean_artifacts( output_dir, %{ wat: clean_all_types or wat_only, wasm: clean_all_types or wasm_only, manifests: clean_all_types or manifests_only }, dry_run, quiet ) # Also clean the Mix compiler manifest if clean_all_types or manifests_only do clean_compiler_manifests(dry_run, quiet) end unless quiet do if dry_run do Mix.shell().info("") Mix.shell().info(" Would remove #{files_removed} file(s)") else Mix.shell().info("") Mix.shell().info(" Removed #{files_removed} file(s)") end end end :ok end @doc """ Clean artifacts from the output directory. Returns the number of files removed (or that would be removed in dry-run mode). """ @spec clean_artifacts(String.t(), map(), boolean(), boolean()) :: non_neg_integer() def clean_artifacts(output_dir, types, dry_run \\ false, quiet \\ false) do unless File.dir?(output_dir) do unless quiet do Mix.shell().info(" #{output_dir}/ does not exist โ€” nothing to clean") end 0 else patterns = build_patterns(output_dir, types) files = patterns |> Enum.flat_map(&Path.wildcard/1) |> Enum.uniq() |> Enum.sort() Enum.each(files, fn file -> unless quiet do action = if dry_run, do: "Would remove", else: "Removing" Mix.shell().info(" #{action}: #{Path.relative_to_cwd(file)}") end unless dry_run do File.rm(file) end end) length(files) end end @doc """ Remove the entire output directory. """ @spec clean_entire_directory(String.t(), boolean(), boolean()) :: :ok def clean_entire_directory(output_dir, dry_run \\ false, quiet \\ false) do if File.dir?(output_dir) do unless quiet do action = if dry_run, do: "Would remove", else: "Removing" Mix.shell().info(" #{action} entire directory: #{output_dir}/") end unless dry_run do File.rm_rf!(output_dir) end # Also clean compiler manifests clean_compiler_manifests(dry_run, quiet) else unless quiet do Mix.shell().info(" #{output_dir}/ does not exist โ€” nothing to clean") end end :ok end @doc """ Clean Mix compiler manifests related to Firebird WASM compilation. """ @spec clean_compiler_manifests(boolean(), boolean()) :: :ok def clean_compiler_manifests(dry_run \\ false, quiet \\ false) do manifest_files = [ manifest_path("firebird_wasm.manifest"), manifest_path("firebird_target.manifest"), manifest_path("firebird_content_hashes.manifest") ] Enum.each(manifest_files, fn path -> if path && File.exists?(path) do unless quiet do action = if dry_run, do: "Would remove", else: "Removing" Mix.shell().info(" #{action}: #{Path.relative_to_cwd(path)}") end unless dry_run do File.rm(path) end end end) :ok end # Private defp build_patterns(output_dir, types) do patterns = [] patterns = if types.wat do patterns ++ [Path.join(output_dir, "*.wat")] else patterns end patterns = if types.wasm do patterns ++ [Path.join(output_dir, "*.wasm")] else patterns end patterns = if types.manifests do patterns ++ [ Path.join(output_dir, "firebird_manifest.json"), Path.join(output_dir, "*.source_map.json") ] else patterns end patterns end defp default_output_dir do config = Firebird.Target.Config.from_mix_project() config.output_dir rescue _ -> "_build/wasm" end defp manifest_path(filename) do Path.join(Mix.Project.manifest_path(), filename) rescue _ -> nil end end