Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.git
.github
bin
dist
Formula
**/node_modules
**/target
**/dist
**/coverage
**/.dart_tool
**/.fvm
**/.mason
**/.DS_Store
30 changes: 30 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,33 @@ jobs:
shell: pwsh
run: ./scripts/check.ps1
working-directory: ${{ runner.temp }}/foundry-rust

docker:
name: Docker distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: docker build --build-arg VERSION=ci -t foundry:test .
- run: ./scripts/test-docker.sh foundry:test
- run: test "$(docker run --rm foundry:test version)" = 'foundry ci'

homebrew:
name: Homebrew distribution
runs-on: macos-latest
env:
HOMEBREW_NO_AUTO_UPDATE: '1'
HOMEBREW_NO_INSTALL_CLEANUP: '1'
steps:
- uses: actions/checkout@v4
- name: Check and install the release formula
env:
GH_TOKEN: ${{ github.token }}
run: |
version=$(sed -n 's/^ version "\(.*\)"/\1/p' Formula/foundry.rb)
gh release download "v$version" --repo "$GITHUB_REPOSITORY" --pattern SHA256SUMS --dir "$RUNNER_TEMP/homebrew-release"
./scripts/homebrew-formula.sh "$version" "$RUNNER_TEMP/homebrew-release/SHA256SUMS" > "$RUNNER_TEMP/foundry.rb"
diff -u Formula/foundry.rb "$RUNNER_TEMP/foundry.rb"
ruby -c Formula/foundry.rb
brew tap --custom-remote mohuganda/foundry "$GITHUB_WORKSPACE"
brew install mohuganda/foundry/foundry
brew test mohuganda/foundry/foundry
106 changes: 106 additions & 0 deletions .github/workflows/distribute.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: Distribute release

on:
workflow_run:
workflows: [Release]
types: [completed]
workflow_dispatch:
inputs:
version:
description: Existing stable release tag to distribute (for example v1.3.0)
required: true
default: v1.3.0

permissions:
contents: read

concurrency:
group: distribute-release
cancel-in-progress: false

jobs:
distribute:
if: >-
github.event_name == 'workflow_dispatch' ||
(github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_repository.full_name == github.repository &&
startsWith(github.event.workflow_run.head_branch, 'v'))
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
steps:
- uses: actions/checkout@v4
- name: Resolve published release
id: release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ github.event.inputs.version || github.event.workflow_run.head_branch }}
shell: bash
run: |
[[ "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo 'Only stable vX.Y.Z tags are supported.'; exit 1; }
gh release view "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --json isDraft,isPrerelease --jq 'if .isDraft or .isPrerelease then error("Expected a published stable release") else . end'
image="ghcr.io/${GITHUB_REPOSITORY,,}"
echo "tag=$RELEASE_TAG" >> "$GITHUB_OUTPUT"
echo "version=${RELEASE_TAG#v}" >> "$GITHUB_OUTPUT"
echo "image=$image" >> "$GITHUB_OUTPUT"
if [[ "$(gh release view --repo "$GITHUB_REPOSITORY" --json tagName --jq .tagName)" == "$RELEASE_TAG" ]]; then
echo "latest-tag=$image:latest" >> "$GITHUB_OUTPUT"
fi
- uses: actions/checkout@v4
with:
ref: ${{ steps.release.outputs.tag }}
path: source
persist-credentials: false
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- name: Build and test the native container
env:
VERSION: ${{ steps.release.outputs.version }}
run: |
docker build --build-arg VERSION="$VERSION" -f Dockerfile -t foundry:test source
./scripts/test-docker.sh foundry:test
test "$(docker run --rm foundry:test version)" = "foundry $VERSION"
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Publish multi-platform image
uses: docker/build-push-action@v6
with:
context: source
file: Dockerfile
platforms: linux/amd64,linux/arm64
build-args: VERSION=${{ steps.release.outputs.version }}
push: true
tags: |
${{ steps.release.outputs.image }}:${{ steps.release.outputs.version }}
${{ steps.release.outputs.latest-tag }}
- name: Update the Homebrew tap for the latest stable release
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ steps.release.outputs.tag }}
shell: bash
run: |
latest=$(gh release view --repo "$GITHUB_REPOSITORY" --json tagName --jq .tagName)
if [[ "$latest" != "$RELEASE_TAG" ]]; then
echo 'Historical image published; retaining the latest Homebrew version.'
exit 0
fi
mkdir -p "$RUNNER_TEMP/homebrew-release"
gh release download "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" --pattern SHA256SUMS --dir "$RUNNER_TEMP/homebrew-release"
./scripts/homebrew-formula.sh "$RELEASE_TAG" "$RUNNER_TEMP/homebrew-release/SHA256SUMS" > "$RUNNER_TEMP/foundry.rb"
ruby -c "$RUNNER_TEMP/foundry.rb"
branch=$(gh api "repos/$GITHUB_REPOSITORY" --jq .default_branch)
endpoint="repos/$GITHUB_REPOSITORY/contents/Formula/foundry.rb"
current=$(gh api "$endpoint?ref=$branch" --jq .sha)
if [[ "$(git hash-object "$RUNNER_TEMP/foundry.rb")" == "$current" ]]; then
echo 'Homebrew formula is already current.'
exit 0
fi
gh api "$endpoint" --method PUT \
-f message="chore: update Homebrew to $RELEASE_TAG [skip ci]" \
-f branch="$branch" -f sha="$current" \
-f content="$(base64 -w 0 "$RUNNER_TEMP/foundry.rb")" --silent
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

- Add a Homebrew tap backed by this repository's Formula directory and verified macOS/Linux release archives.
- Add a non-root Docker image with all five embedded foundations, offline generation tests, and multi-platform GHCR publishing after stable releases.
- Automate Homebrew version/checksum updates after successful release publication and document distribution usage and maintenance.

## 1.3.0 - 2026-10-24

### Compatibility notes
Expand Down
24 changes: 24 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# syntax=docker/dockerfile:1
FROM --platform=$BUILDPLATFORM golang:1.23-alpine AS build
ARG TARGETOS=linux
ARG TARGETARCH
ARG VERSION=dev
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build \
-trimpath -ldflags "-s -w -X github.com/jabahum/foundry/internal/cli.Version=${VERSION}" \
-o /out/foundry ./cmd/foundry \
&& mkdir -p /out/workspace

FROM scratch
ARG VERSION=dev
LABEL org.opencontainers.image.title="Foundry" \
org.opencontainers.image.description="Offline project generation from five embedded foundations" \
org.opencontainers.image.source="https://github.com/mohuganda/foundry" \
org.opencontainers.image.version=$VERSION
COPY --from=build /out/foundry /usr/local/bin/foundry
COPY --from=build --chown=65532:65532 /out/workspace /workspace
USER 65532:65532
WORKDIR /workspace
ENTRYPOINT ["/usr/local/bin/foundry"]
CMD ["help"]
38 changes: 38 additions & 0 deletions Formula/foundry.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
class Foundry < Formula
desc "Create and extend projects from reusable foundations"
homepage "https://github.com/mohuganda/foundry"
version "1.3.0"

on_macos do
on_arm do
url "https://github.com/mohuganda/foundry/releases/download/v1.3.0/foundry_1.3.0_darwin_arm64.tar.gz"
sha256 "66b4ee1bda6fbc1f4aa78529f2ab0d1d24ce49c3748180f753d3cd2b25ab048b"
end
on_intel do
url "https://github.com/mohuganda/foundry/releases/download/v1.3.0/foundry_1.3.0_darwin_amd64.tar.gz"
sha256 "7084a8760ab09a7c0d56afbd30862e09a4deba15e706d5a21adde277192d8886"
end
end

on_linux do
on_arm do
url "https://github.com/mohuganda/foundry/releases/download/v1.3.0/foundry_1.3.0_linux_arm64.tar.gz"
sha256 "1a7ea9791e800a9a75334cad036a8cbed3e25fd56748ce34d38f10f4a95e113e"
end
on_intel do
url "https://github.com/mohuganda/foundry/releases/download/v1.3.0/foundry_1.3.0_linux_amd64.tar.gz"
sha256 "396b9211215abc1467daeba4ebd20159009845de72b7b61fa68c29c1d6d6cf59"
end
end

def install
bin.install "foundry"
doc.install "README.md", "CHANGELOG.md", "docs/commands.md", "docs/workspaces.md"
end

test do
assert_match "foundry #{version}", shell_output("#{bin}/foundry version")
system bin/"foundry", "new", "brew-smoke", "--template", "rust", "--no-hooks"
assert_path_exists testpath/"brew-smoke/Cargo.toml"
end
end
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,30 @@ archives are built for:
Download the archive for your platform from GitHub Releases, verify it against
`SHA256SUMS`, extract it, and place `foundry` (or `foundry.exe`) on your `PATH`.

### Homebrew (macOS and Linux)

```sh
brew tap mohuganda/foundry https://github.com/mohuganda/foundry
brew install mohuganda/foundry/foundry
foundry version
```

The tap uses this repository's `Formula/` directory and installs verified release binaries for Apple Silicon/Intel macOS and arm64/amd64 Linux. Upgrade with `brew update && brew upgrade mohuganda/foundry/foundry`.

### Docker (Linux containers)

```sh
docker run --rm ghcr.io/mohuganda/foundry:1.3.0 version
# From the parent directory where the generated project should be written:
docker run --rm --user "$(id -u):$(id -g)" \
--mount "type=bind,source=$(pwd),target=/workspace" \
ghcr.io/mohuganda/foundry:1.3.0 new my-api --template go --no-hooks
```

Images support linux/amd64 and linux/arm64. Pin a version for reproducibility; `latest` follows the latest stable release. The image contains the CLI and embedded templates. Install language SDKs and run generated-project development commands on the host or in a separate development container. Use `--no-hooks` because generation hooks need SDKs that this image does not include. See [distribution details](docs/distribution.md) for Windows mounts, interactive use, and publishing.

### Go source installation

Install directly from source with Go 1.23 or newer:

```bash
Expand Down
2 changes: 2 additions & 0 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

This guide covers the Foundry executable, development of Foundry itself, and commands inside generated projects. Run `foundry help` for CLI syntax, `make help` in either Make-based project for targets, and a shell script with `--help` for its inputs. Rust's PowerShell script uses `-Help`.

See [Distribution](distribution.md) for Homebrew installation, Docker usage, and release publication automation.

## Choose a command category

The [workspace guide](workspaces.md) explains which commands apply to each workspace and gives creation-to-validation recipes. This page is the detailed reference.
Expand Down
117 changes: 117 additions & 0 deletions docs/distribution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Distribution

Foundry ships as a standalone executable with five embedded source foundations. GitHub Releases, Homebrew, and Docker distribute the same CLI. Template downloads and separate template packages are unnecessary.

## Homebrew

The existing Foundry repository doubles as a custom tap; the explicit repository URL is required when first adding it:

```sh
brew tap mohuganda/foundry https://github.com/mohuganda/foundry
brew install mohuganda/foundry/foundry
foundry version
```

The formula selects the macOS/Linux archive for arm64 or amd64, verifies its SHA-256, and installs the executable and documentation. No Go SDK is needed. The fully qualified formula name avoids collisions with other tools named Foundry.

```sh
brew update
brew upgrade mohuganda/foundry/foundry
brew test mohuganda/foundry/foundry
brew uninstall mohuganda/foundry/foundry
```

Homebrew's formula test checks the version and generates a Rust project without hooks. Generated files stay independent of the installed formula; upgrading/uninstalling the CLI does not upgrade/delete your applications.

## Docker

Images are published to `ghcr.io/mohuganda/foundry` for Linux amd64 and arm64. Docker Desktop runs these Linux images on macOS and Windows. Version tags such as `1.3.0` identify a release; `latest` follows the latest stable GitHub release. Use a pinned version or image digest for repeatable automation.

```sh
docker run --rm ghcr.io/mohuganda/foundry:1.3.0 help
docker run --rm ghcr.io/mohuganda/foundry:1.3.0 list
```

### Generate into the host directory

On macOS/Linux, run from the parent directory where the new project belongs:

```sh
docker run --rm --user "$(id -u):$(id -g)" \
--mount "type=bind,source=$(pwd),target=/workspace" \
ghcr.io/mohuganda/foundry:1.3.0 \
new customer-mobile --template flutter --no-hooks
```

`/workspace` is the image's working directory. The bind mount persists generated output on the host; `--rm` removes the container afterward. Supplying your UID/GID gives the files your host ownership. The image defaults to non-root UID/GID 65532; on Linux, grant that user access or use the UID/GID override above. The host directory must be writable by the selected user.

Native Windows PowerShell with Docker Desktop in Linux-container mode:

```powershell
docker run --rm --mount "type=bind,source=$($PWD.Path),target=/workspace" `
ghcr.io/mohuganda/foundry:1.3.0 new dashboard --template react --no-hooks
```

Docker Desktop handles host file access on Windows. For interactive creation on macOS/Linux, add `-it` and run the executable without arguments, overriding the image's default help command:

```sh
docker run --rm -it --user "$(id -u):$(id -g)" \
--mount "type=bind,source=$(pwd),target=/workspace" \
--entrypoint "" ghcr.io/mohuganda/foundry:1.3.0 /usr/local/bin/foundry
```

The image's default command is `help`; the example explicitly runs the executable without arguments to select interactive creation.

### Extend or inspect a mounted project

```sh
# Run from the parent containing my-api/:
docker run --rm --user "$(id -u):$(id -g)" \
--mount "type=bind,source=$(pwd),target=/workspace" \
ghcr.io/mohuganda/foundry:1.3.0 add service orders --project my-api
```

`--project` and `--destination` refer to container paths. A destination outside the mounted directory will not persist after the container is removed. The image supports generation, built-in Go/Flutter scaffolding, and read-only template version checks. It contains no shell, Go/Flutter/Node/Rust SDK, Mason executable, database, or Docker daemon. Use `--no-hooks` when generating; develop and validate the result using the host's native tools. `doctor` inside the image reports container tooling, rather than the host's environment.

### Build and check locally

From the Foundry repository with Docker running:

```sh
docker build --build-arg VERSION=dev -t foundry:test .
./scripts/test-docker.sh foundry:test
```

The multi-stage Dockerfile compiles a static CLI and copies it into a minimal scratch image. Build context exclusions omit local dependencies and build outputs. `test-docker.sh` runs version/help and generates all five templates into a mounted temporary directory with networking disabled, checking manifests and executable script permissions. It cleans up the temporary directory on exit. Docker is required; generated-project SDKs are not.

## Release automation

1. Merge validated code, create a stable `vX.Y.Z` tag, and push it.
2. `Release` checks Foundry, builds six executable archives plus `SHA256SUMS`, and publishes the GitHub release.
3. `Distribute release` runs after that successful tag-triggered workflow. It verifies a published stable release exists, builds/tests its CLI image, then publishes Linux amd64/arm64 images to GHCR.
4. For the latest stable release, it also updates `Formula/foundry.rb` on the default branch using that release's checksums. Historical backfills publish only their versioned image and preserve the latest formula/image alias.

The distribution workflow uses the repository's `GITHUB_TOKEN` with `packages: write` and `contents: write`. No cross-repository token or separate tap repository is needed. The default branch must permit its formula-update commit; if branch rules prohibit it, apply the generated formula through a normal reviewed PR instead. Formula-only automation commits include `[skip ci]`; CI already tests the generator and installation of the checked-in formula.

On first GHCR publication, check the package's settings and ensure visibility is **Public** for anonymous pulls. GitHub initially creates packages as private; the image's source label links it to this repository. Repository/workflow policies may restrict package creation or token permissions.

To backfill an already published stable release, or retry a failed distribution run:

```sh
gh workflow run distribute.yml --repo mohuganda/foundry -f version=v1.3.0
```

Manual `Release` workflow runs only validate archives; they do not start automatic distribution. Stable tags are supported; prerelease tags are not distributed by this workflow.

### Regenerate a formula manually

Download the release checksum manifest, then run from the Foundry repository:

```sh
gh release download v1.3.0 --repo mohuganda/foundry \
--pattern SHA256SUMS --dir /tmp/foundry-homebrew
./scripts/homebrew-formula.sh 1.3.0 /tmp/foundry-homebrew/SHA256SUMS > Formula/foundry.rb
ruby -c Formula/foundry.rb
```

The generator accepts a stable version with or without its `v` prefix, requires one valid SHA-256 per supported macOS/Linux archive, and writes the formula to stdout. Review the diff and commit it through the usual workflow. Archive URLs and hashes always refer to an existing release; building local snapshots does not change the tap.
Loading
Loading