defmodule Mix.Tasks.Firebird.Target.Verify do @moduledoc """ Verify compiled WASM modules by loading and optionally running smoke tests. ## Usage # Verify all WASM files in output directory mix firebird.target.verify # Verify specific directory mix firebird.target.verify --dir _build/wasm # Verify specific file mix firebird.target.verify --file _build/wasm/MyMath.wasm # Run with smoke tests (auto-detected from source annotations) mix firebird.target.verify --smoke ## Output Reports for each WASM file: - Whether it loads successfully - Number and names of exports - Smoke test results (if enabled) """ use Mix.Task @shortdoc "Verify compiled WASM modules" @switches [ dir: :string, file: :string, smoke: :boolean, json: :boolean, verbose: :boolean ] @aliases [ d: :dir, f: :file, v: :verbose ] @impl Mix.Task def run(args) do {opts, _rest, _invalid} = OptionParser.parse(args, switches: @switches, aliases: @aliases) verbose = Keyword.get(opts, :verbose, false) json_mode = Keyword.get(opts, :json, false) cond do file = Keyword.get(opts, :file) -> verify_file(file, verbose, json_mode) dir = Keyword.get(opts, :dir) -> verify_directory(dir, verbose, json_mode) true -> # Default: verify _build/wasm config = Firebird.Target.Config.from_mix_project() verify_directory(config.output_dir, verbose, json_mode) end end defp verify_file(path, _verbose, json_mode) do Mix.shell().info("🔍 Verifying #{path}...") case File.read(path) do {:ok, binary} -> {:ok, report} = Firebird.Target.Verify.verify_binary(binary) if json_mode do Mix.shell().info(Jason.encode!(report, pretty: true)) else Mix.shell().info(Firebird.Target.Verify.format_report(report)) end {:error, reason} -> Mix.shell().error(" ❌ Cannot read #{path}: #{reason}") end end defp verify_directory(dir, verbose, json_mode) do unless File.dir?(dir) do Mix.shell().info( "Directory #{dir} does not exist. Run `mix firebird.target.compile` first." ) :ok else Mix.shell().info("🔍 Verifying WASM modules in #{dir}/") Mix.shell().info("") case Firebird.Target.Verify.verify_dir(dir) do {:ok, reports} -> if Enum.empty?(reports) do Mix.shell().info(" No WASM files found in #{dir}/") else if json_mode do data = Enum.map(reports, fn r -> %{file: r.file, valid: r.result.valid, exports: r.result.export_count} end) Mix.shell().info(Jason.encode!(data, pretty: true)) else Enum.each(reports, fn r -> basename = Path.basename(r.file) status = if r.result.valid, do: "✅", else: "❌" Mix.shell().info(" #{status} #{basename}") if verbose do Mix.shell().info(Firebird.Target.Verify.format_report(r.result)) else Mix.shell().info(" Exports: #{r.result.export_count}") end Mix.shell().info("") end) valid = Enum.count(reports, & &1.result.valid) total = length(reports) Mix.shell().info("📊 #{valid}/#{total} modules verified successfully") end end {:error, reason} -> Mix.shell().error(" Error: #{inspect(reason)}") end end end end