diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..e8dc15b4 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,8 @@ +# GitHub Copilot + +Read and follow [AGENTS.md](../AGENTS.md) at the repository root. It is the +canonical guidance for this repository; do not duplicate project rules here. + +When proposing or reviewing changes, run the checks listed under "Build, +lint, and test" in `AGENTS.md` (`ci-scripts/lint.sh` and `ci-scripts/test.sh`) +and keep `CHANGELOG.md` and `website/docs/` in step with user-visible changes. diff --git a/.github/release-footer.md b/.github/release-footer.md index 03cd813a..6f88f9ee 100644 --- a/.github/release-footer.md +++ b/.github/release-footer.md @@ -6,6 +6,7 @@ | Linux | arm64 | `stackql-deploy-linux-arm64.tar.gz` | | macOS | Universal (Apple Silicon + Intel) | `stackql-deploy-macos-universal.tar.gz` | | Windows | x86_64 | `stackql-deploy-windows-x86_64.zip` | +| Windows | arm64 | `stackql-deploy-windows-arm64.zip` | Each archive contains a single binary named `stackql-deploy` (or `stackql-deploy.exe` on Windows). Verify your download with `SHA256SUMS`. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1ca5a1ff..685b3d89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -118,6 +118,11 @@ jobs: artifact-name: stackql-deploy-windows-x86_64 archive: zip use-cross: false + - target: aarch64-pc-windows-msvc + os: windows-11-arm + artifact-name: stackql-deploy-windows-arm64 + archive: zip + use-cross: false - target: aarch64-apple-darwin os: macos-latest artifact-name: stackql-deploy-macos-arm64 @@ -155,7 +160,7 @@ jobs: if: matrix.use-cross == false run: cargo build --release --target ${{ matrix.target }} - name: Strip binary (Linux / macOS native) - if: matrix.os != 'windows-latest' && matrix.use-cross == false + if: matrix.archive == 'tar.gz' && matrix.use-cross == false run: strip target/${{ matrix.target }}/release/stackql-deploy - name: Package (tar.gz) if: matrix.archive == 'tar.gz' @@ -193,6 +198,9 @@ jobs: - os: windows-latest artifact-name: stackql-deploy-windows-x86_64 archive: zip + - os: windows-11-arm + artifact-name: stackql-deploy-windows-arm64 + archive: zip - os: macos-latest artifact-name: stackql-deploy-macos-arm64 archive: tar.gz diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d52bcf7e..8e2faa72 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -75,6 +75,11 @@ jobs: artifact-name: stackql-deploy-windows-x86_64 archive: zip use-cross: false + - target: aarch64-pc-windows-msvc + os: windows-11-arm + artifact-name: stackql-deploy-windows-arm64 + archive: zip + use-cross: false - target: aarch64-apple-darwin os: macos-latest artifact-name: stackql-deploy-macos-arm64 @@ -112,7 +117,7 @@ jobs: if: matrix.use-cross == false run: cargo build --release --target ${{ matrix.target }} - name: Strip binary (Linux / macOS native) - if: matrix.os != 'windows-latest' && matrix.use-cross == false + if: matrix.archive == 'tar.gz' && matrix.use-cross == false run: strip target/${{ matrix.target }}/release/stackql-deploy - name: Package (tar.gz) if: matrix.archive == 'tar.gz' @@ -177,6 +182,9 @@ jobs: - os: windows-latest artifact-name: stackql-deploy-windows-x86_64 archive: zip + - os: windows-11-arm + artifact-name: stackql-deploy-windows-arm64 + archive: zip - os: macos-latest artifact-name: stackql-deploy-macos-arm64 archive: tar.gz diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..86bff4cc --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,140 @@ +# Agent guidance + +This file is the single source of truth for AI coding agents working in this +repository. Claude Code reads it through `CLAUDE.md`, GitHub Copilot through +`.github/copilot-instructions.md`, and Codex reads it directly. Keep project +rules here; the tool-specific files only point to this one. + +## What this project is + +`stackql-deploy` is a multi-cloud Infrastructure-as-Code CLI, written in Rust, +built on [StackQL](https://github.com/stackql/stackql). A stack is a directory +with a `stackql_manifest.yml` and `resources/*.iql` query files; the CLI +renders them with Tera templates and runs `build`, `test`, and `teardown` +against a stackql server over the PostgreSQL wire protocol. See the +[README](README.md) for the user-facing overview and +[stackql-deploy.io/docs](https://stackql-deploy.io/docs) for the full reference. + +This is the v2.x Rust rewrite; the archived Python v1.x lives in +[stackql/stackql-deploy](https://github.com/stackql/stackql-deploy). + +## Repository layout + +| Path | Contents | +|------|----------| +| `src/main.rs`, `src/app.rs` | Thin CLI entry point (clap) over the library crate | +| `src/lib.rs` | Library target so tests can drive commands in process. **Not a public API** — layout and signatures may change without notice | +| `src/commands/` | One module per subcommand (`build`, `test`, `teardown`, `init`, `info`, `shell`, `start-server`, `stop-server`, `upgrade`) | +| `src/core/` | Config, env loading, errors, secrets and log redaction, templating helpers | +| `src/resource/` | Manifest parsing and validation | +| `src/template/` | Tera engine, custom filters, render context | +| `src/utils/` | stackql binary download and server management, pgwire client, query execution, display and logging | +| `tests/` | Integration tests — see [tests/README.md](tests/README.md) | +| `ci-scripts/` | Bash scripts used by CI and for local checks | +| `examples/`, `template-hub/` | Example stacks per provider and the `init` scaffolding templates | +| `website/` | Docusaurus site for stackql-deploy.io; user docs are under `website/docs/` | +| `docs/` | Maintainer docs: flow diagrams and the [release process](docs/release-process.md) | +| `build.rs` | Creates an empty `contributors.csv` placeholder when absent (CI injects the real one) | + +## Build, lint, and test + +CI (`.github/workflows/ci.yml`) runs `ci-scripts/lint.sh` and +`ci-scripts/test.sh` on every pull request to `main`; both must pass. Run the +same checks locally before pushing: + +```sh +bash ci-scripts/format.sh # cargo fmt --all +bash ci-scripts/lint.sh # cargo clippy -- -D warnings && cargo fmt -- --check +bash ci-scripts/test.sh # cargo test --lib && cargo test --test '*' +cargo check --release # fast compile check, also run in CI +cargo build --release # binary at target/release/stackql-deploy +``` + +- Clippy runs with `-D warnings`; any new warning fails CI. Fix it rather than + allowing it. +- Formatting is checked by `cargo fmt -- --check`; run `cargo fmt --all` before + committing. +- The scripts are bash. On Windows run them from Git Bash or another shell + with `sh` on `PATH`; the underlying `cargo` commands work from any shell. +- `cargo doc --no-deps` (`ci-scripts/doc.sh`) must stay green. Doctests are + deliberately disabled in `Cargo.toml` because module-level doc comments + contain illustrative, non-compilable snippets; do not turn them back on. + +### Tests + +There are three layers, all driven by `cargo test`: + +| Layer | Where | Needs | How to run | +|-------|-------|-------|------------| +| Unit | `#[cfg(test)]` modules in `src/` | nothing | `cargo test --lib` | +| Mock integration | `tests/build.rs`, `tests/teardown.rs`, `tests/test_command.rs`, `tests/fixtures/` | nothing — in-process mock stackql server | `cargo test --test '*'` | +| Live | `tests/live.rs`, `tests/live_stacks/` (`#[ignore]`d) | stackql binary, AWS and GitHub credentials | `bash ci-scripts/integration-test.sh` | + +When you change behaviour in a command flow, add or update a mock integration +test that asserts on the exact SQL sent; that is the primary regression suite +and it needs no network or credentials. Live tests run in the +`Integration Tests` workflow on pull requests; they use free resources only and +must tear down everything they create. Details, including how to add a live +stack, are in [tests/README.md](tests/README.md). + +Do not run the live tests unless you have been given credentials for them. + +## Coding conventions + +- Rust 2021 edition, `rust-version = "1.75"` in `Cargo.toml`. Do not use + features newer than the MSRV without bumping it deliberately. +- Keep `main.rs` thin; put logic in the library modules so it is testable + in process. +- Application errors are variants of `AppError` in `src/error.rs`; add a + variant rather than returning bare strings. `src/core/errors.rs` holds the + pattern list that classifies a stackql error as fatal (network, auth, + planner/compiler errors) versus a provider error that retry loops and + `--on-failure ignore` may tolerate. Keep that distinction when adding + error handling; the 2.1.1 and 2.2.0 entries in `CHANGELOG.md` describe the + intended behaviour. +- Anything that can hold a secret (`protected` globals, props, and exports) + must be masked in every log path, including `--dry-run`, `--show-queries`, + `DEBUG` logging, and `RETURNING *` response logging. Redaction is + value-based at the logger sink (`src/core/secrets.rs`); register new + sensitive values there rather than masking ad hoc. +- Only comment code that needs clarification. Module-level `//!` docs + describing a module's role are welcome. +- Match the surrounding style; do not reformat or rename unrelated code. + +## Documentation and changelog + +- Record every user-visible change in `CHANGELOG.md` under the version being + released, in a `### Features` or `### Fixes` list. Entries are full prose + sentences describing the behaviour before and after, and link GitHub issues + as `([#56](https://github.com/stackql/stackql-deploy-rs/issues/56))`. +- When a CLI flag, manifest field, anchor, or template filter changes, update + the matching page under `website/docs/` (`cli-reference/`, + `manifest_fields/`, `resource-query-files.md`, `template-filters.md`) and, + if the README shows it, the README. +- `docs/` is for maintainer documentation (diagrams, release process), not + user docs. + +## Git workflow and releases + +- Work on a feature or fix branch and open a pull request against `main`. + Never commit directly to `main` and never force-push it. +- Releases are cut by bumping `version` in `Cargo.toml` (commit `Cargo.lock` + too) and pushing an annotated `vX.Y.Z` tag, which triggers + `.github/workflows/release.yml` to build six platform binaries, create the + GitHub Release, and publish to crates.io. Do not bump the version or create + tags unless asked; the full procedure is in + [docs/release-process.md](docs/release-process.md). + +## Secrets and generated files + +- Never commit credentials. Cloud and GitHub credentials are supplied through + the environment or a gitignored `.env` file; do not read `.env`, `.env.*`, + `~/.aws`, or `~/.ssh` and do not echo their contents into logs, tests, or + fixtures. +- The following are generated or local-only and gitignored; do not commit + them or depend on their presence: `target/`, `contributors.csv`, + `.stackql/`, `stackql.log`, `.stackql-deploy-exports`, `stackql-deploy` + binaries, `tmp/`, `.claude/`. +- Stack manifests take inputs from `-e KEY=value` flags and `--env-file`, not + from the process environment; keep that distinction when writing examples + and tests. diff --git a/CHANGELOG.md b/CHANGELOG.md index 03616fbd..048783db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 2.3.0 (2026-10-12) + +### Features + +- Added a native Windows ARM64 release binary (`stackql-deploy-windows-arm64.zip`, target `aarch64-pc-windows-msvc`), built and smoke-tested on a `windows-11-arm` runner alongside the existing x86_64 build. Previously Windows-on-ARM users ran the x86_64 binary under emulation. +- `stackql-deploy` now downloads the native `stackql_windows_arm64.zip` StackQL binary when it is itself an ARM64 Windows build. Previously every Windows build fetched `stackql_windows_amd64.zip` regardless of CPU architecture. +- The `get-stackql-deploy.io` PowerShell installer (`irm https://get-stackql-deploy.io/install | iex`) now detects the native Windows architecture and installs the ARM64 build on ARM64 devices, even when invoked from an x64 PowerShell running under emulation. Previously it always installed the x86_64 build. + ## 2.2.0 (2026-09-10) ### Features diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..0e925807 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,13 @@ +# Claude Code + +Read and follow [AGENTS.md](AGENTS.md). It is the canonical guidance for this +repository; do not duplicate project rules here. + +Claude Code specifics: + +- Local tool permissions live in `.claude/settings.local.json`, which is + gitignored and per-developer. Its deny list (`~/.ssh`, `~/.aws`, `.env`, + `.env.*`) mirrors the secrets rules in `AGENTS.md`. +- The `Claude PR Assistant` workflow (`.github/workflows/claude.yaml`) + responds to `@claude` mentions on issues and pull requests and also reads + `AGENTS.md` through this file. diff --git a/Cargo.lock b/Cargo.lock index 11bb664d..b4fa2bae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1809,7 +1809,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "stackql-deploy" -version = "2.2.0" +version = "2.3.0" dependencies = [ "base64", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 8c5b271a..edf7bf8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "stackql-deploy" -version = "2.2.0" +version = "2.3.0" edition = "2021" rust-version = "1.75" description = "Infrastructure-as-code framework for declarative cloud resource management using StackQL" diff --git a/docs/release-process.md b/docs/release-process.md index b1643ade..30418ea8 100644 --- a/docs/release-process.md +++ b/docs/release-process.md @@ -24,7 +24,7 @@ flowchart TD subgraph release.yml M --> N[prepare: fetch contributors via StackQL] - N --> O[build: cross-compile 5 platform binaries] + N --> O[build: build 6 platform binaries] O --> P[release: collect archives, generate SHA256SUMS] P --> Q[softprops/action-gh-release creates GitHub Release] Q --> R[publish: cargo publish to crates.io] @@ -113,7 +113,7 @@ The workflow runs four jobs in sequence: | Job | Description | |-----|-------------| | **prepare** | Fetches the contributor list via StackQL and uploads it as an artifact | -| **build** | Cross-compiles binaries for all five platform targets in parallel | +| **build** | Builds binaries for all six platform targets in parallel | | **release** | Collects archives, generates `SHA256SUMS`, and creates the GitHub Release | | **publish** | Runs `cargo publish` to push the crate to crates.io | @@ -126,6 +126,7 @@ A successful run results in: - `stackql-deploy-macos-arm64.tar.gz` (contains `stackql-deploy`) - `stackql-deploy-macos-x86_64.tar.gz` (contains `stackql-deploy`) - `stackql-deploy-windows-x86_64.zip` (contains `stackql-deploy.exe`) + - `stackql-deploy-windows-arm64.zip` (contains `stackql-deploy.exe`) - `SHA256SUMS` - The crate published at `https://crates.io/crates/stackql-deploy` diff --git a/get-app/README.md b/get-app/README.md index e64f7f41..7b165913 100644 --- a/get-app/README.md +++ b/get-app/README.md @@ -17,8 +17,13 @@ Behaviour (unchanged from the previous Deno Deploy app): `irm https://get-stackql-deploy.io/install | iex` (Windows). - `GET /install.sh` - always the POSIX `sh` installer. Runs `uname` client-side to pick the right OS + arch asset. -- `GET /install.ps1` - always the PowerShell installer. Downloads and expands the - Windows zip into the current directory. +- `GET /install.ps1` - always the PowerShell installer. Detects the native + Windows architecture and downloads and expands the matching zip + (`stackql-deploy-windows-x86_64.zip` or `stackql-deploy-windows-arm64.zip`, + containing `stackql-deploy.exe`) into the current directory. ARM64 is detected + even when x64 PowerShell runs under emulation, by reading the system + `PROCESSOR_ARCHITECTURE` from the registry before the process environment. + AMD64 is used only when no architecture can be detected. - Any other path - `301`-redirects to `https://stackql-deploy.io`. ### Wrong-shell guards diff --git a/get-app/src/index.ts b/get-app/src/index.ts index ae89fd3f..7abfe86a 100644 --- a/get-app/src/index.ts +++ b/get-app/src/index.ts @@ -92,38 +92,65 @@ echo "Installed ./stackql-deploy ($os/$arch). Run it with ./stackql-deploy or mo const INSTALL_PS1 = `#Requires -Version 5 # stackql-deploy installer - https://get-stackql-deploy.io/install.ps1 -# Downloads the latest Windows release binary into the current directory. +# Detects the native Windows architecture (AMD64 or ARM64), downloads the matching +# latest release archive and extracts stackql-deploy.exe into the current directory. # Usage: irm https://get-stackql-deploy.io/install.ps1 | iex -$ErrorActionPreference = 'Stop' - -# Runtime guard: PowerShell also runs on macOS/Linux, where this Windows build -# won't help. Point those callers at the sh installer instead. -if ($PSVersionTable.Platform -eq 'Unix') { - Write-Host "stackql-deploy: this is the Windows installer, but you're on PowerShell on a non-Windows OS." - Write-Host "Install with:" - Write-Host "" - Write-Host " curl -fsSL https://get-stackql-deploy.io/install.sh | sh" - Write-Host "" - return +# +# Everything runs in a script block so 'irm | iex' leaves no variables or +# preference changes behind in the caller's session. +& { + $ErrorActionPreference = 'Stop' + # Invoke-WebRequest's progress bar slows large downloads badly on Windows + # PowerShell 5.1. + $ProgressPreference = 'SilentlyContinue' + + # Runtime guard: PowerShell also runs on macOS/Linux, where this Windows build + # won't help. Point those callers at the sh installer instead. + if ($PSVersionTable.Platform -eq 'Unix') { + Write-Host "stackql-deploy: this is the Windows installer, but you're on PowerShell on a non-Windows OS." + Write-Host "Install with:" + Write-Host "" + Write-Host " curl -fsSL https://get-stackql-deploy.io/install.sh | sh" + Write-Host "" + return + } + + $base = "${RELEASE_BASE}" + + # Detect the native OS architecture. $env:PROCESSOR_ARCHITECTURE describes this + # PowerShell process, so it reads AMD64 when x64 PowerShell runs under emulation + # on ARM64. The system value in the registry is the same for every process, so + # prefer it, then the WOW64 variable, then the process variable. + $arch = $null + try { + $arch = (Get-ItemProperty -Path 'HKLM:\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment' -Name PROCESSOR_ARCHITECTURE -ErrorAction Stop).PROCESSOR_ARCHITECTURE + } catch {} + if (-not $arch) { $arch = $env:PROCESSOR_ARCHITEW6432 } + if (-not $arch) { $arch = $env:PROCESSOR_ARCHITECTURE } + if (-not $arch) { + Write-Host "stackql-deploy: couldn't detect your CPU architecture, defaulting to x64 (AMD64)." + $arch = 'AMD64' + } + + if ($arch -eq 'ARM64') { + $asset = 'stackql-deploy-windows-arm64.zip' + } elseif ($arch -eq 'AMD64') { + $asset = 'stackql-deploy-windows-x86_64.zip' + } else { + Write-Host "stackql-deploy: there's no prebuilt Windows binary for your CPU ($arch)." + Write-Host "Prebuilt Windows builds cover x64 (AMD64) and ARM64." + Write-Host "Browse all downloads: ${RELEASES_URL}" + return + } + + $dest = (Get-Location).Path + $zip = Join-Path $dest $asset + Write-Host "Downloading $asset ..." + Invoke-WebRequest -Uri "$base/$asset" -OutFile $zip + Expand-Archive -Path $zip -DestinationPath $dest -Force + Remove-Item $zip + Write-Host "Installed .\\stackql-deploy.exe (Windows/$arch). Run it with .\\stackql-deploy.exe or move it onto your PATH." } - -$base = "${RELEASE_BASE}" -$asset = 'stackql-deploy-windows-x86_64.zip' -$arch = $env:PROCESSOR_ARCHITECTURE -if ($arch -ne 'AMD64' -and $arch -ne 'ARM64') { - Write-Host "stackql-deploy: there's no prebuilt Windows binary for your CPU ($arch)." - Write-Host "Prebuilt Windows builds cover x64 (AMD64) and ARM64 (via emulation)." - Write-Host "Browse all downloads: ${RELEASES_URL}" - return -} - -$dest = (Get-Location).Path -$zip = Join-Path $dest $asset -Write-Host "Downloading $asset ..." -Invoke-WebRequest -Uri "$base/$asset" -OutFile $zip -Expand-Archive -Path $zip -DestinationPath $dest -Force -Remove-Item $zip -Write-Host "Installed .\\stackql-deploy.exe. Run it with .\\stackql-deploy.exe or move it onto your PATH." `; // Served when the sh installer is fetched by PowerShell - valid PowerShell that diff --git a/src/utils/download.rs b/src/utils/download.rs index 98433924..4f66287f 100644 --- a/src/utils/download.rs +++ b/src/utils/download.rs @@ -45,26 +45,33 @@ pub fn get_download_url() -> Result { STACKQL_RELEASE_BASE_URL )), Platform::Windows => Ok(format!( - "{}/stackql_windows_amd64.zip", - STACKQL_RELEASE_BASE_URL + "{}/stackql_windows_{}.zip", + STACKQL_RELEASE_BASE_URL, + stackql_arch() + )), + Platform::Linux => Ok(format!( + "{}/stackql_linux_{}.zip", + STACKQL_RELEASE_BASE_URL, + stackql_arch() )), - Platform::Linux => { - let arch = if cfg!(target_arch = "aarch64") { - "arm64" - } else { - "amd64" - }; - Ok(format!( - "{}/stackql_linux_{}.zip", - STACKQL_RELEASE_BASE_URL, arch - )) - } Platform::Unknown => Err(AppError::CommandFailed( "Unsupported platform for stackql download".to_string(), )), } } +/// Maps the compile-time CPU architecture to the suffix used by StackQL release +/// asset names (`arm64` or `amd64`). Decided at compile time so a native ARM64 +/// build of stackql-deploy fetches a native ARM64 stackql rather than relying +/// on emulation. +fn stackql_arch() -> &'static str { + if cfg!(target_arch = "aarch64") { + "arm64" + } else { + "amd64" + } +} + /// Downloads the StackQL binary and extracts it to the current directory. /// /// This function downloads the StackQL binary from a URL and unzips it if necessary. diff --git a/website/docs/getting-started.md b/website/docs/getting-started.md index 3d4a6e08..2890686d 100644 --- a/website/docs/getting-started.md +++ b/website/docs/getting-started.md @@ -104,6 +104,7 @@ Remove-Item stackql-deploy.zip **WSL / Git Bash:** ```bash +# x86_64 (use stackql-deploy-windows-arm64.zip on ARM64 devices) curl -L -o stackql-deploy.zip https://github.com/stackql/stackql-deploy-rs/releases/latest/download/stackql-deploy-windows-x86_64.zip unzip stackql-deploy.zip ``` @@ -121,6 +122,7 @@ Pre-built binaries are attached to every release on the [__GitHub Releases__](ht | Linux ARM64 | `stackql-deploy-linux-arm64.tar.gz` | | macOS Universal (Apple Silicon + Intel) | `stackql-deploy-macos-universal.tar.gz` | | Windows x86_64 | `stackql-deploy-windows-x86_64.zip` | +| Windows ARM64 | `stackql-deploy-windows-arm64.zip` | , 'GitHub Actions')}>