feat: build infrastructure
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
|||||||
/_build/
|
/_build/
|
||||||
/cover/
|
/cover/
|
||||||
/deps/
|
/deps/
|
||||||
|
/dist/
|
||||||
/doc/
|
/doc/
|
||||||
/.elixir_ls/
|
/.elixir_ls/
|
||||||
/erl_crash.dump
|
/erl_crash.dump
|
||||||
|
|||||||
40
README.md
40
README.md
@@ -108,6 +108,46 @@ The initial scaffold is intentionally small:
|
|||||||
|
|
||||||
This is not yet the desktop application. It is the base runtime that future work will extend with the domain model, persistence layer, content pipelines, and desktop-facing boundaries.
|
This is not yet the desktop application. It is the base runtime that future work will extend with the domain model, persistence layer, content pipelines, and desktop-facing boundaries.
|
||||||
|
|
||||||
|
## MCP Packaging
|
||||||
|
|
||||||
|
The MCP server is packaged as its own self-contained release artifact and must not depend on the repository checkout at runtime.
|
||||||
|
|
||||||
|
- Build the distributable MCP release with `MIX_ENV=prod mix release bds_mcp`.
|
||||||
|
- The packaged artifact is assembled at `_build/prod/rel/bds_mcp`.
|
||||||
|
- The distributable executable is exposed as `mcp/bin/bds-mcp` on Unix-like systems and `mcp/bin/bds-mcp.bat` on Windows inside the installed payload.
|
||||||
|
- Agent configuration should point to the packaged executable inside the installed application resources, not to `mix`, not to source files, and not to repository-local scripts.
|
||||||
|
|
||||||
|
This allows the later UI app to bundle the MCP payload as normal application resources on each supported operating system.
|
||||||
|
|
||||||
|
## Release Build Flow
|
||||||
|
|
||||||
|
The repository now has a thin platform build flow around Mix releases.
|
||||||
|
|
||||||
|
- Unix-like systems: `scripts/release/build_platform.sh [macos|linux]`
|
||||||
|
- Windows: `scripts/release/build_platform.bat [windows]`
|
||||||
|
|
||||||
|
The scripts do the standard sequence:
|
||||||
|
|
||||||
|
1. `mix deps.get`
|
||||||
|
2. `mix test` unless `BDS_SKIP_TESTS=1`
|
||||||
|
3. `MIX_ENV=prod mix release bds`
|
||||||
|
4. `MIX_ENV=prod mix release bds_mcp`
|
||||||
|
5. `MIX_ENV=prod mix bds.package <platform>`
|
||||||
|
|
||||||
|
The packaging task creates a clean redistributable payload under `dist/<platform>/` with this layout:
|
||||||
|
|
||||||
|
- `app/`: the full main `bds` release
|
||||||
|
- `resources/`: the full `bds_mcp` release root
|
||||||
|
- `resources/mcp/`: the MCP executable payload used by agent integrations
|
||||||
|
- `manifest.json`: packaging metadata for downstream bundling
|
||||||
|
|
||||||
|
The task also creates a platform archive alongside the payload:
|
||||||
|
|
||||||
|
- macOS and Linux: `.tar.gz`
|
||||||
|
- Windows: `.zip`
|
||||||
|
|
||||||
|
This is the intermediate redistributable artifact intended to be consumed by the later desktop app bundling layer.
|
||||||
|
|
||||||
## Spec Hygiene
|
## Spec Hygiene
|
||||||
|
|
||||||
When editing files in [specs/](/Users/gb/Projects/bDS2/specs):
|
When editing files in [specs/](/Users/gb/Projects/bDS2/specs):
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
cd "$(dirname "$0")/.."
|
|
||||||
exec mix bds.mcp "$@"
|
|
||||||
9
config/prod.exs
Normal file
9
config/prod.exs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import Config
|
||||||
|
|
||||||
|
config :bds, BDS.Repo,
|
||||||
|
database: Path.expand("../priv/data/bds_prod.db", __DIR__),
|
||||||
|
pool_size: 5,
|
||||||
|
stacktrace: false,
|
||||||
|
show_sensitive_data_on_connection_error: false
|
||||||
|
|
||||||
|
config :logger, level: :info
|
||||||
@@ -21,16 +21,24 @@ defmodule BDS.MCP.AgentConfig do
|
|||||||
def config_path(:claude_code, home_dir), do: Path.join(home_dir, ".claude.json")
|
def config_path(:claude_code, home_dir), do: Path.join(home_dir, ".claude.json")
|
||||||
def config_path(:github_copilot, home_dir), do: Path.join([home_dir, "Library", "Application Support", "Code", "User", "mcp.json"])
|
def config_path(:github_copilot, home_dir), do: Path.join([home_dir, "Library", "Application Support", "Code", "User", "mcp.json"])
|
||||||
|
|
||||||
|
def packaged_executable_path(install_root, platform) when is_binary(install_root) do
|
||||||
|
executable_name =
|
||||||
|
case normalize_platform(platform) do
|
||||||
|
:windows -> "bds-mcp.bat"
|
||||||
|
_other -> "bds-mcp"
|
||||||
|
end
|
||||||
|
|
||||||
|
Path.join([install_root, "mcp", "bin", executable_name])
|
||||||
|
end
|
||||||
|
|
||||||
defp default_command(opts) do
|
defp default_command(opts) do
|
||||||
Keyword.get(opts, :script_path, repo_script_path())
|
install_root = Keyword.fetch!(opts, :install_root)
|
||||||
|
platform = Keyword.get(opts, :platform, current_platform())
|
||||||
|
packaged_executable_path(install_root, platform)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp default_args(_opts), do: []
|
defp default_args(_opts), do: []
|
||||||
|
|
||||||
defp repo_script_path do
|
|
||||||
Path.expand("../../../bin/bds-mcp", __DIR__)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp read_config(path) do
|
defp read_config(path) do
|
||||||
if File.exists?(path) do
|
if File.exists?(path) do
|
||||||
path
|
path
|
||||||
@@ -51,4 +59,15 @@ defmodule BDS.MCP.AgentConfig do
|
|||||||
servers = Map.get(config, "mcpServers", %{})
|
servers = Map.get(config, "mcpServers", %{})
|
||||||
Map.put(config, "mcpServers", Map.put(servers, @server_name, %{"command" => command, "args" => args}))
|
Map.put(config, "mcpServers", Map.put(servers, @server_name, %{"command" => command, "args" => args}))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp current_platform do
|
||||||
|
case :os.type() do
|
||||||
|
{:win32, _type} -> :windows
|
||||||
|
{:unix, :darwin} -> :macos
|
||||||
|
{:unix, _type} -> :linux
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp normalize_platform(:darwin), do: :macos
|
||||||
|
defp normalize_platform(platform), do: platform
|
||||||
end
|
end
|
||||||
|
|||||||
132
lib/bds/release_packaging.ex
Normal file
132
lib/bds/release_packaging.ex
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
defmodule BDS.ReleasePackaging do
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
defmodule Metadata do
|
||||||
|
@enforce_keys [
|
||||||
|
:platform,
|
||||||
|
:version,
|
||||||
|
:output_dir,
|
||||||
|
:payload_name,
|
||||||
|
:payload_root,
|
||||||
|
:app_root,
|
||||||
|
:resources_root,
|
||||||
|
:mcp_root,
|
||||||
|
:archive_path
|
||||||
|
]
|
||||||
|
defstruct [
|
||||||
|
:platform,
|
||||||
|
:version,
|
||||||
|
:output_dir,
|
||||||
|
:payload_name,
|
||||||
|
:payload_root,
|
||||||
|
:app_root,
|
||||||
|
:resources_root,
|
||||||
|
:mcp_root,
|
||||||
|
:archive_path
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_metadata(platform, version, output_dir) when is_binary(version) and is_binary(output_dir) do
|
||||||
|
normalized_platform = normalize_platform(platform)
|
||||||
|
payload_name = "bds2-#{normalized_platform}-#{version}"
|
||||||
|
payload_root = Path.join(output_dir, payload_name)
|
||||||
|
|
||||||
|
%Metadata{
|
||||||
|
platform: normalized_platform,
|
||||||
|
version: version,
|
||||||
|
output_dir: output_dir,
|
||||||
|
payload_name: payload_name,
|
||||||
|
payload_root: payload_root,
|
||||||
|
app_root: Path.join(payload_root, "app"),
|
||||||
|
resources_root: Path.join(payload_root, "resources"),
|
||||||
|
mcp_root: Path.join(payload_root, "resources/mcp"),
|
||||||
|
archive_path: Path.join(output_dir, payload_name <> archive_extension(normalized_platform))
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def package(opts) when is_list(opts) do
|
||||||
|
metadata =
|
||||||
|
build_metadata(
|
||||||
|
Keyword.fetch!(opts, :platform),
|
||||||
|
Keyword.fetch!(opts, :version),
|
||||||
|
Keyword.fetch!(opts, :output_dir)
|
||||||
|
)
|
||||||
|
|
||||||
|
app_release_source = Keyword.fetch!(opts, :app_release_source)
|
||||||
|
mcp_release_source = Keyword.fetch!(opts, :mcp_release_source)
|
||||||
|
|
||||||
|
with :ok <- reset_output(metadata),
|
||||||
|
:ok <- copy_release(app_release_source, metadata.app_root),
|
||||||
|
:ok <- copy_release(mcp_release_source, metadata.resources_root),
|
||||||
|
:ok <- write_manifest(metadata),
|
||||||
|
:ok <- create_archive(metadata) do
|
||||||
|
{:ok, metadata}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp normalize_platform(platform) when platform in [:macos, :linux, :windows], do: platform
|
||||||
|
defp normalize_platform(:darwin), do: :macos
|
||||||
|
defp normalize_platform(platform) when is_binary(platform), do: platform |> String.downcase() |> String.to_atom()
|
||||||
|
|
||||||
|
defp archive_extension(:windows), do: ".zip"
|
||||||
|
defp archive_extension(_platform), do: ".tar.gz"
|
||||||
|
|
||||||
|
defp reset_output(metadata) do
|
||||||
|
File.rm_rf!(metadata.payload_root)
|
||||||
|
File.rm_rf!(metadata.archive_path)
|
||||||
|
File.mkdir_p!(metadata.output_dir)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
defp copy_release(source, destination) do
|
||||||
|
File.mkdir_p!(Path.dirname(destination))
|
||||||
|
|
||||||
|
case File.cp_r(source, destination) do
|
||||||
|
{:ok, _files} -> :ok
|
||||||
|
{:error, reason, _file} -> {:error, reason}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp write_manifest(metadata) do
|
||||||
|
manifest = %{
|
||||||
|
"platform" => Atom.to_string(metadata.platform),
|
||||||
|
"version" => metadata.version,
|
||||||
|
"layout" => %{
|
||||||
|
"app" => "app",
|
||||||
|
"resources" => "resources",
|
||||||
|
"mcp" => "resources/mcp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest_path = Path.join(metadata.payload_root, "manifest.json")
|
||||||
|
File.write!(manifest_path, Jason.encode!(manifest, pretty: true))
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create_archive(%Metadata{platform: :windows} = metadata) do
|
||||||
|
relative_entries = collect_entries(metadata.payload_root)
|
||||||
|
cwd = metadata.output_dir |> String.to_charlist()
|
||||||
|
archive = metadata.archive_path |> String.to_charlist()
|
||||||
|
entries = Enum.map(relative_entries, &String.to_charlist(Path.join(metadata.payload_name, &1)))
|
||||||
|
|
||||||
|
case :zip.create(archive, entries, cwd: cwd) do
|
||||||
|
{:ok, _archive_path} -> :ok
|
||||||
|
{:error, reason} -> {:error, reason}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create_archive(metadata) do
|
||||||
|
case System.cmd("tar", ["-czf", metadata.archive_path, "-C", metadata.output_dir, metadata.payload_name]) do
|
||||||
|
{_output, 0} -> :ok
|
||||||
|
{output, status} -> {:error, {:tar_failed, status, output}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp collect_entries(root) do
|
||||||
|
root
|
||||||
|
|> Path.join("**/*")
|
||||||
|
|> Path.wildcard(match_dot: true)
|
||||||
|
|> Enum.reject(&File.dir?/1)
|
||||||
|
|> Enum.map(&Path.relative_to(&1, root))
|
||||||
|
end
|
||||||
|
end
|
||||||
49
lib/mix/tasks/bds.package.ex
Normal file
49
lib/mix/tasks/bds.package.ex
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
defmodule Mix.Tasks.Bds.Package do
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
use Mix.Task
|
||||||
|
|
||||||
|
@shortdoc "Packages built releases into a redistributable platform payload"
|
||||||
|
|
||||||
|
@impl Mix.Task
|
||||||
|
def run(args) do
|
||||||
|
{opts, positional, _invalid} =
|
||||||
|
OptionParser.parse(args,
|
||||||
|
strict: [output: :string, version: :string, app_release: :string, mcp_release: :string]
|
||||||
|
)
|
||||||
|
|
||||||
|
platform = positional |> List.first() |> normalize_platform_arg()
|
||||||
|
version = opts[:version] || Mix.Project.config()[:version]
|
||||||
|
env_name = Atom.to_string(Mix.env())
|
||||||
|
|
||||||
|
package_opts = [
|
||||||
|
platform: platform,
|
||||||
|
version: version,
|
||||||
|
output_dir: opts[:output] || Path.expand("dist/#{platform}", File.cwd!()),
|
||||||
|
app_release_source: opts[:app_release] || Path.expand("_build/#{env_name}/rel/bds", File.cwd!()),
|
||||||
|
mcp_release_source: opts[:mcp_release] || Path.expand("_build/#{env_name}/rel/bds_mcp", File.cwd!())
|
||||||
|
]
|
||||||
|
|
||||||
|
case BDS.ReleasePackaging.package(package_opts) do
|
||||||
|
{:ok, metadata} ->
|
||||||
|
Mix.shell().info("Packaged #{metadata.payload_name} at #{metadata.archive_path}")
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
Mix.raise("Packaging failed: #{inspect(reason)}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp normalize_platform_arg(nil), do: current_platform()
|
||||||
|
defp normalize_platform_arg("macos"), do: :macos
|
||||||
|
defp normalize_platform_arg("linux"), do: :linux
|
||||||
|
defp normalize_platform_arg("windows"), do: :windows
|
||||||
|
defp normalize_platform_arg(other), do: Mix.raise("Unsupported platform #{inspect(other)}")
|
||||||
|
|
||||||
|
defp current_platform do
|
||||||
|
case :os.type() do
|
||||||
|
{:win32, _type} -> :windows
|
||||||
|
{:unix, :darwin} -> :macos
|
||||||
|
{:unix, _type} -> :linux
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
16
mix.exs
16
mix.exs
@@ -6,6 +6,8 @@ defmodule BDS.MixProject do
|
|||||||
app: :bds,
|
app: :bds,
|
||||||
version: "0.1.0",
|
version: "0.1.0",
|
||||||
elixir: "~> 1.17",
|
elixir: "~> 1.17",
|
||||||
|
default_release: :bds,
|
||||||
|
releases: releases(),
|
||||||
start_permanent: Mix.env() == :prod,
|
start_permanent: Mix.env() == :prod,
|
||||||
aliases: aliases(),
|
aliases: aliases(),
|
||||||
deps: deps()
|
deps: deps()
|
||||||
@@ -41,4 +43,18 @@ defmodule BDS.MixProject do
|
|||||||
test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"]
|
test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"]
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp releases do
|
||||||
|
[
|
||||||
|
bds: [
|
||||||
|
include_executables_for: [:unix, :windows],
|
||||||
|
applications: [bds: :permanent]
|
||||||
|
],
|
||||||
|
bds_mcp: [
|
||||||
|
path: "_build/#{Mix.env()}/rel/bds_mcp",
|
||||||
|
include_executables_for: [:unix, :windows],
|
||||||
|
applications: [bds: :permanent]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
7
rel/overlays/mcp/bin/bds-mcp
Executable file
7
rel/overlays/mcp/bin/bds-mcp
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
||||||
|
RELEASE_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/../.." && pwd)
|
||||||
|
|
||||||
|
exec "$RELEASE_ROOT/bin/bds_mcp" eval "Application.ensure_all_started(:bds); BDS.MCP.Stdio.main()"
|
||||||
5
rel/overlays/mcp/bin/bds-mcp.bat
Normal file
5
rel/overlays/mcp/bin/bds-mcp.bat
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
@echo off
|
||||||
|
set SCRIPT_DIR=%~dp0
|
||||||
|
set RELEASE_ROOT=%SCRIPT_DIR%..\..
|
||||||
|
|
||||||
|
call "%RELEASE_ROOT%\bin\bds_mcp.bat" eval "Application.ensure_all_started(:bds); BDS.MCP.Stdio.main()"
|
||||||
31
scripts/release/build_platform.bat
Normal file
31
scripts/release/build_platform.bat
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
@echo off
|
||||||
|
setlocal enabledelayedexpansion
|
||||||
|
|
||||||
|
set ROOT_DIR=%~dp0\..\..
|
||||||
|
pushd "%ROOT_DIR%"
|
||||||
|
|
||||||
|
set PLATFORM=%1
|
||||||
|
if "%PLATFORM%"=="" set PLATFORM=windows
|
||||||
|
|
||||||
|
call mix deps.get
|
||||||
|
if errorlevel 1 goto :error
|
||||||
|
|
||||||
|
if not "%BDS_SKIP_TESTS%"=="1" (
|
||||||
|
call mix test
|
||||||
|
if errorlevel 1 goto :error
|
||||||
|
)
|
||||||
|
|
||||||
|
call set MIX_ENV=prod
|
||||||
|
call mix release --overwrite bds
|
||||||
|
if errorlevel 1 goto :error
|
||||||
|
call mix release --overwrite bds_mcp
|
||||||
|
if errorlevel 1 goto :error
|
||||||
|
call mix bds.package %PLATFORM%
|
||||||
|
if errorlevel 1 goto :error
|
||||||
|
|
||||||
|
popd
|
||||||
|
exit /b 0
|
||||||
|
|
||||||
|
:error
|
||||||
|
popd
|
||||||
|
exit /b 1
|
||||||
28
scripts/release/build_platform.sh
Executable file
28
scripts/release/build_platform.sh
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/../.." && pwd)
|
||||||
|
PLATFORM=${1:-}
|
||||||
|
|
||||||
|
if [[ -z "$PLATFORM" ]]; then
|
||||||
|
case "$(uname -s)" in
|
||||||
|
Darwin) PLATFORM="macos" ;;
|
||||||
|
Linux) PLATFORM="linux" ;;
|
||||||
|
*)
|
||||||
|
echo "Unsupported platform. Pass one of: macos, linux, windows" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$ROOT_DIR"
|
||||||
|
|
||||||
|
mix deps.get
|
||||||
|
|
||||||
|
if [[ "${BDS_SKIP_TESTS:-0}" != "1" ]]; then
|
||||||
|
mix test
|
||||||
|
fi
|
||||||
|
|
||||||
|
MIX_ENV=prod mix release --overwrite bds
|
||||||
|
MIX_ENV=prod mix release --overwrite bds_mcp
|
||||||
|
MIX_ENV=prod mix bds.package "$PLATFORM"
|
||||||
74
test/bds/release_packaging_test.exs
Normal file
74
test/bds/release_packaging_test.exs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
defmodule BDS.ReleasePackagingTest do
|
||||||
|
use ExUnit.Case, async: false
|
||||||
|
|
||||||
|
alias BDS.ReleasePackaging
|
||||||
|
|
||||||
|
setup do
|
||||||
|
base_dir = Path.join(System.tmp_dir!(), "bds-release-packaging-#{System.unique_integer([:positive])}")
|
||||||
|
output_dir = Path.join(base_dir, "dist")
|
||||||
|
app_release = Path.join(base_dir, "rel/bds")
|
||||||
|
mcp_release = Path.join(base_dir, "rel/bds_mcp")
|
||||||
|
|
||||||
|
File.mkdir_p!(Path.join(app_release, "bin"))
|
||||||
|
File.mkdir_p!(Path.join(mcp_release, "mcp/bin"))
|
||||||
|
File.write!(Path.join(app_release, "bin/bds"), "app-release")
|
||||||
|
File.write!(Path.join(mcp_release, "mcp/bin/bds-mcp"), "mcp-release")
|
||||||
|
|
||||||
|
on_exit(fn -> File.rm_rf(base_dir) end)
|
||||||
|
|
||||||
|
%{base_dir: base_dir, output_dir: output_dir, app_release: app_release, mcp_release: mcp_release}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "build metadata uses a clean future-app payload layout" do
|
||||||
|
metadata = ReleasePackaging.build_metadata(:macos, "0.1.0", "/tmp/out")
|
||||||
|
|
||||||
|
assert metadata.platform == :macos
|
||||||
|
assert metadata.version == "0.1.0"
|
||||||
|
assert metadata.archive_path =~ ".tar.gz"
|
||||||
|
assert metadata.payload_root =~ "bds2-macos-0.1.0"
|
||||||
|
assert metadata.resources_root == Path.join(metadata.payload_root, "resources")
|
||||||
|
assert metadata.mcp_root == Path.join(metadata.payload_root, "resources/mcp")
|
||||||
|
assert metadata.app_root == Path.join(metadata.payload_root, "app")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "package creates payload manifest and copies app and mcp releases", context do
|
||||||
|
assert {:ok, metadata} =
|
||||||
|
ReleasePackaging.package(
|
||||||
|
platform: :macos,
|
||||||
|
version: "0.1.0",
|
||||||
|
output_dir: context.output_dir,
|
||||||
|
app_release_source: context.app_release,
|
||||||
|
mcp_release_source: context.mcp_release
|
||||||
|
)
|
||||||
|
|
||||||
|
assert File.exists?(Path.join(metadata.app_root, "bin/bds"))
|
||||||
|
assert File.exists?(Path.join(metadata.mcp_root, "bin/bds-mcp"))
|
||||||
|
assert File.exists?(Path.join(metadata.payload_root, "manifest.json"))
|
||||||
|
|
||||||
|
manifest = metadata.payload_root |> Path.join("manifest.json") |> File.read!() |> Jason.decode!()
|
||||||
|
|
||||||
|
assert manifest == %{
|
||||||
|
"platform" => "macos",
|
||||||
|
"version" => "0.1.0",
|
||||||
|
"layout" => %{
|
||||||
|
"app" => "app",
|
||||||
|
"resources" => "resources",
|
||||||
|
"mcp" => "resources/mcp"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "package creates a platform archive for redistribution", context do
|
||||||
|
assert {:ok, metadata} =
|
||||||
|
ReleasePackaging.package(
|
||||||
|
platform: :windows,
|
||||||
|
version: "0.1.0",
|
||||||
|
output_dir: context.output_dir,
|
||||||
|
app_release_source: context.app_release,
|
||||||
|
mcp_release_source: context.mcp_release
|
||||||
|
)
|
||||||
|
|
||||||
|
assert File.exists?(metadata.archive_path)
|
||||||
|
assert String.ends_with?(metadata.archive_path, ".zip")
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user