# Contributing to Elevate Thanks for taking the time. Elevate is a macOS 26 menu bar app and a Windows 11 tray app for just-in-time Microsoft Entra and Azure PIM role activation. Bugs, feature ideas and pull requests are all welcome — please read [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) first, and report anything security-sensitive privately as described in [SECURITY.md](SECURITY.md) rather than in an issue. ## Prerequisites macOS app (`macos/`): - macOS 26 (Tahoe) and **Xcode 26.6 or newer**. - `brew install xcodegen` — the Xcode project is generated, never committed. Windows app (`windows/`): - Windows 11 (23H2 or newer), the **.NET 10 SDK** and the Windows 11 SDK 10.0.22621 (the Visual Studio *Windows application development* workload, or standalone); `dotnet build` needs no Visual Studio. `Elevate.Core` and its tests also build on macOS and Linux with the .NET SDK. - **WiX 5.0.2** (`dotnet tool install --global wix --version 5.0.2`) only to build the MSI; WiX 6 and 7 require accepting a maintenance-fee EULA and are not used. CLI (`cli/`): - The **.NET 10 SDK** on Linux, macOS or Windows; nothing else. The solution references `windows/src/Elevate.Core` directly. Either app: to sign in from a local build, an Entra app registration; see [docs/entra-app-registration.md](docs/entra-app-registration.md). Only the own-app method needs one. The Azure CLI and Azure PowerShell methods need no registration, but they cover Azure resource roles only — no Entra roles and no PIM for Groups — so a registration is needed to work on or test those parts. ## Build and run (Windows) ```powershell cd windows dotnet build src/Elevate.App/Elevate.App.csproj src\Elevate.App\bin\x64\Debug\net10.0-windows10.0.22621.0\win-x64\Elevate.exe --flyout ``` `dotnet test Elevate.sln` runs the Core and App suites; both must pass with warnings as errors. [windows/README.md](windows/README.md) has the developer switches and the installer steps, [windows/CONTINUING.md](windows/CONTINUING.md) the rulings and gotchas. ## Build and run (CLI) ```bash cd cli dotnet test Elevate.Cli.sln dotnet run --project src/Elevate.Cli -- --help ./package.sh publish 0.0.0 linux-x64 # a self-contained single file in dist/linux-x64 (any RID works on any OS) ``` [cli/README.md](cli/README.md) has the command reference, the data directory and the packaging steps. Point a development build at a scratch directory with `--data-dir` so it never touches your real accounts. ## Build and run (macOS) ```bash cd macos xcodegen generate xcodebuild -project Elevate.xcodeproj -scheme ElevateApp -configuration Debug -derivedDataPath build -allowProvisioningUpdates build open build/Build/Products/Debug/Elevate.app ``` ## Tests ElevateCore (the Swift package): ```bash cd macos swift test ``` ElevateAppTests (the app target): ```bash cd macos xcodebuild -project Elevate.xcodeproj -scheme ElevateApp -configuration Debug -derivedDataPath build -allowProvisioningUpdates test ``` CI runs the same two, with signing disabled (`CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO`) — see [.github/workflows/macos.yml](.github/workflows/macos.yml). Running the unsigned variant into the same `-derivedDataPath build` replaces your signed Debug app with an unsigned one, whose own-app sign-in then reports "Unavailable on unsigned builds". Use a separate path, e.g. `-derivedDataPath build-unsigned`, when you want an unsigned test run locally. Elevate.Cli.Tests (the CLI): ```bash cd cli dotnet test Elevate.Cli.sln ``` CI runs it on Ubuntu, macOS and Windows ([.github/workflows/cli.yml](.github/workflows/cli.yml)). All suites must pass before a pull request is merged. ## Constraints this repository keeps - **ElevateCore imports only `Foundation`** (plus `CryptoKit`, for PKCE). Anything AppKit, SwiftUI or MSAL belongs in `ElevateApp`. Core stays testable without a UI. - **`Elevate.Core` is a straight port of ElevateCore** with no Windows, WinUI or MSAL reference; the app model lives in `Elevate.App.Model` so it stays testable without a window. Port a Core change to both platforms in the same pull request, with the same fixtures. - **The CLI adds nothing to Core.** `Elevate.Cli` consumes `Elevate.Core` as is; anything the CLI needs from Core is a Core change, ported to Swift too. The CLI's own logic (auth over MSAL without a broker, the headless session, parsing, rendering) stays under `cli/`. It must build and its tests must pass on Linux, macOS and Windows. - **Swift 6 strict concurrency.** The package builds in Swift language mode 6; do not silence concurrency diagnostics to get a change through. - **Swift Testing** (`import Testing`, `@Test`, `#expect`) for new tests, not XCTest. - **Never commit `macos/Elevate.xcodeproj`.** It is generated by `xcodegen generate` from `project.yml`; project changes go into `project.yml`. - **Never commit a real client id, tenant id, token or account name** — not in code, tests, fixtures, screenshots or issue text. Client ids are configured at runtime in Settings. - Keep documentation next to the change: user-visible behaviour in `README.md`, `macos/README.md`, `windows/README.md` or `cli/README.md`, notable changes in [CHANGELOG.md](CHANGELOG.md) under `## [Unreleased]`. The audit tool has its own changelog, [audit/CHANGELOG.md](audit/CHANGELOG.md), because it is released on its own; a change under `audit/` goes there instead. ## Pull requests One short-lived branch per change, branched from `main`, with a pull request back to `main`. Keep it focused, describe what changed and how you verified it, and fill in the checklist in the pull request template. Rebase rather than merge `main` into your branch where you can. ## The Entra roles catalogue `macos/Sources/ElevateCore/Resources/EntraBuiltInRoles.json` is generated, not hand-edited. Rerun the script when Microsoft adds or renames built-in directory roles (a role showing up as a bare GUID in the panel is the usual symptom): save the markdown of the [Entra built-in roles reference](https://learn.microsoft.com/entra/identity/role-based-access-control/permissions-reference) and, from the repository root, run ```bash perl shared/entra-roles/update-role-catalogue.pl page.md > macos/Sources/ElevateCore/Resources/EntraBuiltInRoles.json ``` Commit the regenerated JSON together with a note of when it was refreshed. ## Releases Releases are tag-driven: pushing a `v*` tag builds both apps and the CLI from that commit, publishes one GitHub Release with the DMG, the pkg and MSIs (which carry the CLI), the standalone CLI archives, and updates the Homebrew cask and the deprecated formula. All three share the version number. The audit tool is released separately, from `audit-v*` tags, with its own version and changelog. Maintainers only — the full procedure, including the optional signing secrets, is in [docs/releasing.md](docs/releasing.md).