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
113 changes: 113 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,116 @@ jobs:

- name: Staleness gate
run: swift scripts/localizable-overload-sweep.swift --check

# The scaffolder's verification (migration design §7), on every PR (ruled
# 2026-08-22; revisit if too draconian). Everything runs via xcodebuild:
# swift test/build hang on macOS runners (see run_tests matrix note), and
# the bootstrap targets are mac-host-gated so ubuntu never sees them.
#
# Layers:
# 1. Fast suite (config/tokens/emitter/consistency tests; skeletons
# self-skip without the env gate).
# 2. Walking skeletons: emit each shape, run its verification doors.
# TEST_RUNNER_ prefix forwards the gate to the test process.
# 3. Generation matrix: generated UI tests on real destinations — the
# automated BIG TEST. The least-proven leg (design §7): signing and
# simulator behavior on hosted runners may need iteration.
bootstrap_generation_matrix:
name: Scaffolder fast suite + skeletons + generated-app tests
runs-on: macos-latest
steps:
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable

- uses: actions/checkout@v2

- name: Install xcodegen
run: brew install xcodegen

# The Verifier's inner xcodebuild (building the generated app) is a
# child process, so the outer -skipMacroValidation flag never reaches
# it; disable the fingerprint gate runner-wide instead.
- name: Skip macro fingerprint validation
run: defaults write com.apple.dt.Xcode IDESkipMacroFingerprintValidation -bool YES

- name: Fast suite
run: |
set -o pipefail
xcrun xcodebuild -IDEClonedSourcePackagesDirPathOverride="$PWD/.dependencies" \
-skipMacroValidation -skipPackagePluginValidation \
-derivedDataPath "$PWD/.derivedData" \
-scheme FOSUtilities-Package -destination "platform=macOS,arch=arm64,name=My Mac" \
-only-testing:FOSMVVMBootstrapTests test | xcpretty || \
xcrun xcodebuild -IDEClonedSourcePackagesDirPathOverride="$PWD/.dependencies" \
-skipMacroValidation -skipPackagePluginValidation \
-derivedDataPath "$PWD/.derivedData" \
-scheme FOSUtilities-Package -destination "platform=macOS,arch=arm64,name=My Mac" \
-only-testing:FOSMVVMBootstrapTests test

- name: Walking skeletons
env:
TEST_RUNNER_FOSMVVM_BOOTSTRAP_SKELETONS: "1"
run: |
set -o pipefail
xcrun xcodebuild -IDEClonedSourcePackagesDirPathOverride="$PWD/.dependencies" \
-skipMacroValidation -skipPackagePluginValidation \
-derivedDataPath "$PWD/.derivedData" \
-scheme FOSUtilities-Package -destination "platform=macOS,arch=arm64,name=My Mac" \
-only-testing:FOSMVVMBootstrapTests/IntegrationTests test

- name: Generate app shapes for the matrix
run: |
set -o pipefail
BIN="$PWD/.derivedData/Build/Products/Debug/fosmvvm-bootstrap"
if [ ! -x "$BIN" ]; then
xcrun xcodebuild -IDEClonedSourcePackagesDirPathOverride="$PWD/.dependencies" \
-skipMacroValidation -skipPackagePluginValidation \
-derivedDataPath "$PWD/.derivedData" \
-scheme fosmvvm-bootstrap -destination "platform=macOS,arch=arm64,name=My Mac" build
fi
mkdir -p "$RUNNER_TEMP/matrix"
cat > "$RUNNER_TEMP/local.json" <<'CONFIG'
{ "projectName": "LocalDemo", "shape": "localOnly",
"platforms": { "macOS": "14.0", "iOS": "17.0" },
"bundleIdRoot": "com.example.localdemo", "teamId": "ABCDE12345" }
CONFIG
cat > "$RUNNER_TEMP/server.json" <<'CONFIG'
{ "projectName": "ServerDemo", "shape": "clientServer",
"platforms": { "macOS": "14.0", "iOS": "17.0" },
"bundleIdRoot": "com.example.serverdemo", "teamId": "ABCDE12345" }
CONFIG
"$BIN" new --config "$RUNNER_TEMP/local.json" --output "$RUNNER_TEMP/matrix/LocalDemo"
"$BIN" new --config "$RUNNER_TEMP/server.json" --output "$RUNNER_TEMP/matrix/ServerDemo"

# CI simulators are ALWAYS the cold case: fresh boot, first install —
# exactly the measured first-run tap-flake condition (2026-08-22).
# Pre-booting warms the sim; -retry-tests-on-failure keeps a one-off
# settling flake visible in logs without redding the PR, while a real
# failure still fails both iterations. Durable fix is FOSTestingUI's
# first-interaction settle (follow-on) — then retries are belt-and-braces.
- name: Pre-boot iOS simulator
run: |
UDID=$(xcrun simctl list devices available | grep "iPhone 17 (" | head -1 | grep -oE "[0-9A-F-]{36}")
xcrun simctl boot "$UDID" || true
xcrun simctl bootstatus "$UDID" -b || true

- name: Generated UI tests (iOS Simulator)
run: |
set -o pipefail
for APP in LocalDemo ServerDemo; do
xcrun xcodebuild -project "$RUNNER_TEMP/matrix/$APP/$APP.xcodeproj" \
-scheme "$APP" -destination "platform=iOS Simulator,name=iPhone 17" \
-retry-tests-on-failure -test-iterations 2 \
CODE_SIGNING_ALLOWED=NO test
done

- name: Generated UI tests (macOS)
run: |
set -o pipefail
for APP in LocalDemo ServerDemo; do
xcrun xcodebuild -project "$RUNNER_TEMP/matrix/$APP/$APP.xcodeproj" \
-scheme "$APP" -destination "platform=macOS,arch=arm64,name=My Mac" \
-retry-tests-on-failure -test-iterations 2 \
CODE_SIGN_IDENTITY=- DEVELOPMENT_TEAM= CODE_SIGN_STYLE=Manual test
done
11 changes: 10 additions & 1 deletion Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,17 @@ let package = Package(
))
#endif

#if os(macOS)
// The project scaffolder (see docs/superpowers/plans/
// 2026-08-22-fosmvvm-bootstrap-migration-design.md). macOS-only tooling:
// it shells out to xcodegen/xcodebuild, so the manifest gates it to Mac
// hosts rather than guarding call sites in code.
result.append(.executable(
name: "fosmvvm-bootstrap",
targets: ["FOSMVVMBootstrapCLI"]
))
#endif

return result
}(),
dependencies: {
Expand Down Expand Up @@ -88,6 +99,10 @@ let package = Package(
result.append(.package(url: "https://github.com/vapor/leaf-kit.git", .upToNextMajor(from: "1.11.0")))
#endif

#if os(macOS)
result.append(.package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMajor(from: "1.3.0")))
#endif

return result
}(),
targets: {
Expand Down Expand Up @@ -258,6 +273,27 @@ let package = Package(
))
#endif

#if os(macOS)
result.append(.target(
name: "FOSMVVMBootstrap",
resources: [
.copy("Templates")
]
))
result.append(.executableTarget(
name: "FOSMVVMBootstrapCLI",
dependencies: [
"FOSMVVMBootstrap",
"FOSFoundation",
.product(name: "ArgumentParser", package: "swift-argument-parser")
]
))
result.append(.testTarget(
name: "FOSMVVMBootstrapTests",
dependencies: ["FOSMVVMBootstrap"]
))
#endif

return result
}(),
swiftLanguageModes: [.v6]
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,18 @@ For guides, articles, and API documentation see the

[docs]: https://swiftpackageindex.com/foscomputerservices/FOSUtilities/documentation/fosfoundation

## Create a project in one minute

The package ships a scaffolder that generates a complete FOSMVVM application: a SwiftUI app (`localOnly`), a SwiftUI app plus a Vapor server sharing one ViewModel contract (`clientServer`), or a reusable ViewModel package (`sharedLibrary`). It needs Xcode and [xcodegen](https://github.com/yonaskolb/XcodeGen) (`brew install xcodegen`):

```bash
git clone https://github.com/foscomputerservices/FOSUtilities.git
cd FOSUtilities
swift run fosmvvm-bootstrap new --output ~/MyApp
```

A short interview asks for the project name, shape, platforms, bundle id, and team (add `--verbose` to also print the equivalent `--config` JSON so reruns can skip the questions). Add `--verify` to build the generated project and run its tests on your machine (CI already verifies every release this way). Full details: [Creating a Project](https://swiftpackageindex.com/foscomputerservices/FOSUtilities/main/documentation/fosmvvm/creatingaproject).

## FOSFoundation

FOSFoundation is a library of protocols, patterns, types and routines that I have found generally useful in my projects. Support areas include:
Expand Down
2 changes: 2 additions & 0 deletions Sources/FOSFoundation/FOSFoundation.docc/FOSFoundation.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ FOSFoundation is a library that provides extensions and patterns to Apple's Foun

``FOSFoundation`` provides the building blocks for its companion framework [FOSMVVM](https://swiftpackageindex.com/foscomputerservices/FOSUtilities/main/documentation/fosmvvm), which provides full client and server support for building [Model-View-ViewModel](https://w.wiki/4T5B) applications.

> Tip: Starting a new project? [Creating a Project](https://swiftpackageindex.com/foscomputerservices/FOSUtilities/main/documentation/fosmvvm/creatingaproject) scaffolds a complete, verified FOSMVVM application in about a minute.

## Topics

### Networking
Expand Down
60 changes: 60 additions & 0 deletions Sources/FOSMVVM/FOSMVVM.docc/CreatingAProject.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Creating a Project

Scaffold a complete, verified FOSMVVM application in about a minute.

## Overview

FOSUtilities ships a scaffolder, `fosmvvm-bootstrap`, that generates a ready-to-run project wired to the patterns described throughout this documentation: ViewModels with localization, the operations seam, UI-test harnesses, and (for client-server projects) a Vapor + Fluent server with live view-model refresh.

Three project shapes are supported:

| Shape | What you get |
| --- | --- |
| `localOnly` | A SwiftUI app with client-hosted ViewModels. No server. |
| `clientServer` | A SwiftUI app plus a Vapor server sharing one ViewModel contract, including a live server-fetched screen backed by Fluent. |
| `sharedLibrary` | A Swift package that publishes a ViewModel contract for other apps to consume. |

## Prerequisites

- Xcode (the app shapes generate an Xcode project)
- [xcodegen](https://github.com/yonaskolb/XcodeGen): `brew install xcodegen`

## Generate

Clone the package and run the scaffolder. With no `--config`, a short interview collects everything it needs, validating each answer as it is given:

```bash
git clone https://github.com/foscomputerservices/FOSUtilities.git
cd FOSUtilities
swift run fosmvvm-bootstrap new --output ~/MyApp
```

With `--verbose`, the interview also prints the equivalent `--config` JSON. Save it to rerun without the questions, or to script generation:

```bash
swift run fosmvvm-bootstrap new --config myapp.json --output ~/MyApp
```

Configuration fields:

- `projectName`: the app and target name.
- `shape`: `localOnly`, `clientServer`, or `sharedLibrary`.
- `platforms`: platform to minimum-version map. Versions must meet the FOSUtilities floors.
- `bundleIdRoot`: reverse-DNS root for bundle identifiers. App shapes only.
- `teamId`: your Apple Development Team identifier. App shapes only.

## Verifying the generated project

Every release of the scaffolder is verified by CI: the generated projects are built and their test suites run, including the UI tests. To additionally prove the skeleton on your machine, pass `--verify` — the scaffolder then builds the generated project and runs its tests before declaring success: `swift build`, `swift test` (for `clientServer` this includes a real Fluent create-and-refresh round trip on an in-memory database), and an `xcodebuild` build of the app. A successful verified run ends with:

```
✅ Walking skeleton verified green
```

Either way, generation finishes with a short checklist of the few steps tooling cannot do for you, such as committing the new repository and confirming code signing.

> Note: The generated project depends on the FOSUtilities release the scaffolder shipped with, using `from:`, so later releases arrive with a normal package update. Running the scaffolder from a checkout between releases pins the most recently stamped release.

## Diagnosing an existing project

A `doctor` command that audits an existing project against the same rules the scaffolder uses is planned as an SPM command plugin.
1 change: 1 addition & 0 deletions Sources/FOSMVVM/FOSMVVM.docc/FOSMVVM.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ To enable Xcode Cloud builds to build using macros check out this [Stack Overflo

## Topics

- <doc:CreatingAProject>
- <doc:ClientOverview>
- <doc:ApplicationState>
- <doc:ServerOverview>
Expand Down
Loading
Loading