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
4 changes: 4 additions & 0 deletions .cursorrules
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ Critical Rules:
6. Use the shared logging library. No raw `echo` for status messages. Use
`log_info`, `log_warn`, `log_error`, `log_debug`, and `die` from
`lib/log.sh`.
7. Never suppress failing checks. When a lint, format, security, or test
check fails, fix the underlying issue. Never comment out code, add
suppression annotations, disable rules, or mark CI jobs as
allowed-to-fail to bypass a failing check.

Quick Reference:

Expand Down
4 changes: 4 additions & 0 deletions .opencode/agents.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ agents:
6. Use the shared logging library. No raw `echo` for status messages. Use
`log_info`, `log_warn`, `log_error`, `log_debug`, and `die` from
`lib/log.sh`.
7. Never suppress failing checks. When a lint, format, security, or test
check fails, fix the underlying issue. Never comment out code, add
suppression annotations, disable rules, or mark CI jobs as
allowed-to-fail to bypass a failing check.
Quick Reference:
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ See DEVELOPMENT.md for the complete reference.
4. **Respect `.editorconfig`.** Never override formatting rules (indent style, line endings, trailing whitespace) without explicit instruction.
5. **Write idempotent scripts.** Every script must be safe to re-run. Check before acting: `command -v tool || install_tool`, `mkdir -p`, guard file writes with existence checks.
6. **Use the shared logging library.** No raw `echo` for status messages. Use `log_info`, `log_warn`, `log_error`, `log_debug`, and `die` from `lib/log.sh`.
7. **Never suppress failing checks.** When a lint, format, security, or test check fails, fix the underlying issue. Never comment out code, add suppression annotations, disable rules, or mark CI jobs as allowed-to-fail to bypass a failing check.

## Quick Reference

Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ See DEVELOPMENT.md for the complete reference.
4. **Respect `.editorconfig`.** Never override formatting rules (indent style, line endings, trailing whitespace) without explicit instruction.
5. **Write idempotent scripts.** Every script must be safe to re-run. Check before acting: `command -v tool || install_tool`, `mkdir -p`, guard file writes with existence checks.
6. **Use the shared logging library.** No raw `echo` for status messages. Use `log_info`, `log_warn`, `log_error`, `log_debug`, and `die` from `lib/log.sh`.
7. **Never suppress failing checks.** When a lint, format, security, or test check fails, fix the underlying issue. Never comment out code, add suppression annotations, disable rules, or mark CI jobs as allowed-to-fail to bypass a failing check.

## Quick Reference

Expand Down
20 changes: 19 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ RUN go install mvdan.cc/gofumpt@latest
# Install govulncheck
RUN go install golang.org/x/vuln/cmd/govulncheck@latest

# === Rust builder stage ===
# Provides Rust toolchain (rustup + cargo + rustc + clippy + rustfmt) and
# installs cargo-audit and cargo-deny via cargo-binstall.
FROM rust:1-slim-bookworm AS rust-builder
RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*
RUN curl -L --proto '=https' --tlsv1.2 -sSf \
https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh \
| bash
RUN cargo binstall --no-confirm cargo-audit cargo-deny

# === Node.js base: provides Node runtime for JS/TS tooling ===
FROM node:22-bookworm-slim AS node-base

Expand Down Expand Up @@ -93,8 +104,14 @@ COPY --from=node-base /usr/local/lib/node_modules /usr/local/lib/node_modules
RUN ln -sf ../lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm && \
ln -sf ../lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx

# Copy Rust toolchain from rust-builder (rustup + cargo + rustc + clippy + rustfmt + cargo-audit + cargo-deny)
COPY --from=rust-builder /usr/local/rustup /usr/local/rustup
COPY --from=rust-builder /usr/local/cargo /usr/local/cargo
ENV RUSTUP_HOME=/usr/local/rustup
ENV CARGO_HOME=/usr/local/cargo

# Set up environment
ENV PATH="/opt/devrail/bin:/usr/local/go/bin:${PATH}"
ENV PATH="/opt/devrail/bin:/usr/local/cargo/bin:/usr/local/go/bin:${PATH}"
ENV DEVRAIL_LIB="/opt/devrail/lib"

# Copy Go SDK from builder (required at runtime by golangci-lint, govulncheck)
Expand All @@ -118,6 +135,7 @@ RUN bash /opt/devrail/scripts/install-ansible.sh
RUN bash /opt/devrail/scripts/install-ruby.sh
RUN bash /opt/devrail/scripts/install-go.sh
RUN bash /opt/devrail/scripts/install-javascript.sh
RUN bash /opt/devrail/scripts/install-rust.sh
RUN bash /opt/devrail/scripts/install-universal.sh

# Allow git operations on mounted workspaces with different ownership
Expand Down
135 changes: 135 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ HAS_ANSIBLE := $(filter ansible,$(LANGUAGES))
HAS_RUBY := $(filter ruby,$(LANGUAGES))
HAS_GO := $(filter go,$(LANGUAGES))
HAS_JAVASCRIPT := $(filter javascript,$(LANGUAGES))
HAS_RUST := $(filter rust,$(LANGUAGES))

# ---------------------------------------------------------------------------
# .PHONY declarations
Expand Down Expand Up @@ -260,6 +261,21 @@ _lint: _check-config
exit $$overall_exit; \
fi; \
fi; \
if [ -n "$(HAS_RUST)" ]; then \
ran_languages="$${ran_languages}\"rust\","; \
rs_files=$$(find . -name '*.rs' -not -path './.git/*' -not -path './vendor/*' -not -path './target/*' 2>/dev/null); \
if [ -n "$$rs_files" ]; then \
cargo clippy --all-targets --all-features -- -D warnings || { overall_exit=1; failed_languages="$${failed_languages}\"rust\","; }; \
else \
echo '{"level":"info","msg":"skipping rust lint: no .rs files found","language":"rust"}' >&2; \
fi; \
if [ "$(DEVRAIL_FAIL_FAST)" = "1" ] && [ $$overall_exit -ne 0 ]; then \
end_time=$$(date +%s%3N); \
duration=$$((end_time - start_time)); \
echo "{\"target\":\"lint\",\"status\":\"fail\",\"duration_ms\":$$duration,\"languages\":[$${ran_languages%,}],\"failed\":[$${failed_languages%,}]}"; \
exit $$overall_exit; \
fi; \
fi; \
end_time=$$(date +%s%3N); \
duration=$$((end_time - start_time)); \
if [ $$overall_exit -eq 0 ]; then \
Expand Down Expand Up @@ -359,6 +375,21 @@ _format: _check-config
exit $$overall_exit; \
fi; \
fi; \
if [ -n "$(HAS_RUST)" ]; then \
ran_languages="$${ran_languages}\"rust\","; \
rs_files=$$(find . -name '*.rs' -not -path './.git/*' -not -path './vendor/*' -not -path './target/*' 2>/dev/null); \
if [ -n "$$rs_files" ]; then \
cargo fmt --all -- --check || { overall_exit=1; failed_languages="$${failed_languages}\"rust\","; }; \
else \
echo '{"level":"info","msg":"skipping rust format: no .rs files found","language":"rust"}' >&2; \
fi; \
if [ "$(DEVRAIL_FAIL_FAST)" = "1" ] && [ $$overall_exit -ne 0 ]; then \
end_time=$$(date +%s%3N); \
duration=$$((end_time - start_time)); \
echo "{\"target\":\"format\",\"status\":\"fail\",\"duration_ms\":$$duration,\"languages\":[$${ran_languages%,}],\"failed\":[$${failed_languages%,}]}"; \
exit $$overall_exit; \
fi; \
fi; \
end_time=$$(date +%s%3N); \
duration=$$((end_time - start_time)); \
if [ $$overall_exit -eq 0 ]; then \
Expand Down Expand Up @@ -458,6 +489,21 @@ _fix: _check-config
exit $$overall_exit; \
fi; \
fi; \
if [ -n "$(HAS_RUST)" ]; then \
ran_languages="$${ran_languages}\"rust\","; \
rs_files=$$(find . -name '*.rs' -not -path './.git/*' -not -path './vendor/*' -not -path './target/*' 2>/dev/null); \
if [ -n "$$rs_files" ]; then \
cargo fmt --all || { overall_exit=1; failed_languages="$${failed_languages}\"rust\","; }; \
else \
echo '{"level":"info","msg":"skipping rust fix: no .rs files found","language":"rust"}' >&2; \
fi; \
if [ "$(DEVRAIL_FAIL_FAST)" = "1" ] && [ $$overall_exit -ne 0 ]; then \
end_time=$$(date +%s%3N); \
duration=$$((end_time - start_time)); \
echo "{\"target\":\"fix\",\"status\":\"fail\",\"duration_ms\":$$duration,\"languages\":[$${ran_languages%,}],\"failed\":[$${failed_languages%,}]}"; \
exit $$overall_exit; \
fi; \
fi; \
end_time=$$(date +%s%3N); \
duration=$$((end_time - start_time)); \
if [ $$overall_exit -eq 0 ]; then \
Expand Down Expand Up @@ -579,6 +625,22 @@ _test: _check-config
exit $$overall_exit; \
fi; \
fi; \
if [ -n "$(HAS_RUST)" ]; then \
rs_files=$$(find . -name '*.rs' -not -path './.git/*' -not -path './vendor/*' -not -path './target/*' 2>/dev/null); \
if [ -n "$$rs_files" ] && [ -f "Cargo.toml" ]; then \
ran_languages="$${ran_languages}\"rust\","; \
cargo test --all-targets || { overall_exit=1; failed_languages="$${failed_languages}\"rust\","; }; \
else \
skipped_languages="$${skipped_languages}\"rust\","; \
echo '{"level":"info","msg":"skipping rust tests: no .rs files or Cargo.toml found","language":"rust"}' >&2; \
fi; \
if [ "$(DEVRAIL_FAIL_FAST)" = "1" ] && [ $$overall_exit -ne 0 ]; then \
end_time=$$(date +%s%3N); \
duration=$$((end_time - start_time)); \
echo "{\"target\":\"test\",\"status\":\"fail\",\"duration_ms\":$$duration,\"languages\":[$${ran_languages%,}],\"failed\":[$${failed_languages%,}],\"skipped\":[$${skipped_languages%,}]}"; \
exit $$overall_exit; \
fi; \
fi; \
end_time=$$(date +%s%3N); \
duration=$$((end_time - start_time)); \
if [ -z "$${ran_languages}" ] && [ -n "$${skipped_languages}" ]; then \
Expand Down Expand Up @@ -694,6 +756,32 @@ _security: _check-config
exit $$overall_exit; \
fi; \
fi; \
if [ -n "$(HAS_RUST)" ]; then \
if [ -f "Cargo.lock" ]; then \
ran_languages="$${ran_languages}\"rust\","; \
cargo audit || { overall_exit=1; failed_languages="$${failed_languages}\"rust:cargo-audit\","; }; \
else \
skipped_languages="$${skipped_languages}\"rust:cargo-audit\","; \
echo '{"level":"info","msg":"skipping cargo audit: no Cargo.lock found","language":"rust"}' >&2; \
fi; \
if [ "$(DEVRAIL_FAIL_FAST)" = "1" ] && [ $$overall_exit -ne 0 ]; then \
end_time=$$(date +%s%3N); \
duration=$$((end_time - start_time)); \
echo "{\"target\":\"security\",\"status\":\"fail\",\"duration_ms\":$$duration,\"languages\":[$${ran_languages%,}],\"failed\":[$${failed_languages%,}]}"; \
exit $$overall_exit; \
fi; \
if [ -f "deny.toml" ]; then \
cargo deny check || { overall_exit=1; failed_languages="$${failed_languages}\"rust:cargo-deny\","; }; \
else \
echo '{"level":"info","msg":"skipping cargo deny: no deny.toml found","language":"rust"}' >&2; \
fi; \
if [ "$(DEVRAIL_FAIL_FAST)" = "1" ] && [ $$overall_exit -ne 0 ]; then \
end_time=$$(date +%s%3N); \
duration=$$((end_time - start_time)); \
echo "{\"target\":\"security\",\"status\":\"fail\",\"duration_ms\":$$duration,\"languages\":[$${ran_languages%,}],\"failed\":[$${failed_languages%,}]}"; \
exit $$overall_exit; \
fi; \
fi; \
end_time=$$(date +%s%3N); \
duration=$$((end_time - start_time)); \
if [ -z "$${ran_languages}" ] && [ -n "$${skipped_languages}" ]; then \
Expand Down Expand Up @@ -813,6 +901,14 @@ _docs: _check-config
_tv tsc "tsc --version"; \
_tv vitest "vitest --version"; \
fi; \
if [ -n "$(HAS_RUST)" ]; then \
_tv rustc "rustc --version"; \
_tv cargo "cargo --version"; \
_tv clippy "cargo clippy --version"; \
_tv rustfmt "rustfmt --version"; \
_tv cargo-audit "cargo audit --version"; \
_tv cargo-deny "cargo deny --version"; \
fi; \
_tv trivy "trivy --version"; \
_tv gitleaks "gitleaks version"; \
_tv git-cliff "git-cliff --version"; \
Expand Down Expand Up @@ -1019,6 +1115,45 @@ _init: _check-config
'build/' \
'coverage/'; \
fi; \
if [ -n "$(HAS_RUST)" ]; then \
scaffold clippy.toml \
'# clippy.toml -- DevRail Rust clippy configuration' \
'# See: https://doc.rust-lang.org/clippy/lint_configuration.html' \
'too-many-arguments-threshold = 7'; \
scaffold rustfmt.toml \
'# rustfmt.toml -- DevRail Rust formatter configuration' \
'edition = "2021"' \
'max_width = 100' \
'use_field_init_shorthand = true' \
'use_try_shorthand = true'; \
scaffold deny.toml \
'# deny.toml -- DevRail cargo-deny configuration' \
'# See: https://embarkstudios.github.io/cargo-deny/' \
'' \
'[advisories]' \
'vulnerability = "deny"' \
'unmaintained = "warn"' \
'yanked = "warn"' \
'' \
'[licenses]' \
'unlicensed = "deny"' \
'allow = [' \
' "MIT",' \
' "Apache-2.0",' \
' "BSD-2-Clause",' \
' "BSD-3-Clause",' \
' "ISC",' \
' "Unicode-3.0",' \
' "Unicode-DFS-2016",' \
']' \
'' \
'[bans]' \
'multiple-versions = "warn"' \
'' \
'[sources]' \
'unknown-registry = "deny"' \
'unknown-git = "warn"'; \
fi; \
echo "{\"target\":\"init\",\"created\":[$${created%,}],\"skipped\":[$${skipped%,}]}"

# --- _check: orchestrate all targets ---
Expand Down
2 changes: 1 addition & 1 deletion STABILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ DevRail has reached **v1.0** across all repositories. The core standards, toolch
| **Makefile contract** | Stable | Two-layer delegation pattern, JSON summary output, `init` scaffolding. |
| **Shell conventions** | Stable | `lib/log.sh`, `lib/platform.sh`, header format, and idempotency patterns are settled. |
| **Conventional commits** | Stable | Types, scopes, and format are finalized. Pre-commit hook published. |
| **Language standards** | Stable | Python, Bash, Terraform, Ansible, Ruby, Go, JavaScript/TypeScript — all 7 ecosystems shipped. |
| **Language standards** | Stable | Python, Bash, Terraform, Ansible, Ruby, Go, JavaScript/TypeScript, Rust — all 8 ecosystems shipped. |
| **Coding practices** | Stable | General principles (DRY, KISS, testing, error handling) are finalized. |
| **Git workflow** | Stable | Branch strategy, PR process, and merge policy are finalized. |
| **Release & versioning** | Stable | Semver policy is defined and in use across all repos. |
Expand Down
84 changes: 84 additions & 0 deletions scripts/install-rust.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env bash
# scripts/install-rust.sh — Verify Rust tooling for DevRail
#
# Purpose: Verifies that the Rust toolchain and Rust-based tools are available
# in the dev-toolchain container. The Rust SDK (rustup + cargo + rustc)
# and all tools are COPY'd from the rust-builder stage; this script
# only confirms they are on PATH.
# Usage: bash scripts/install-rust.sh [--help]
# Dependencies: lib/log.sh, lib/platform.sh
#
# Tools verified:
# - rustc (Rust compiler — COPY'd from builder)
# - cargo (Rust package manager — COPY'd from builder)
# - clippy (Linter — rustup component, COPY'd from builder)
# - rustfmt (Formatter — rustup component, COPY'd from builder)
# - cargo-audit (Dependency vulnerability scanner — built in builder)
# - cargo-deny (Dependency policy checker — built in builder)

set -euo pipefail

# --- Resolve library path ---
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEVRAIL_LIB="${DEVRAIL_LIB:-${SCRIPT_DIR}/../lib}"

# shellcheck source=../lib/log.sh
source "${DEVRAIL_LIB}/log.sh"
# shellcheck source=../lib/platform.sh
source "${DEVRAIL_LIB}/platform.sh"

# --- Help ---
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
log_info "install-rust.sh — Verify Rust tooling for DevRail"
log_info "Usage: bash scripts/install-rust.sh [--help]"
log_info "Tools: rustc, cargo, clippy, rustfmt, cargo-audit, cargo-deny"
exit 0
fi

# --- Main ---

log_info "Verifying Rust tooling installation"

# Verify rustc is available (COPY'd from builder)
if command -v rustc &>/dev/null; then
log_info "rustc is already installed"
else
log_warn "rustc not found — expected to be copied from Rust builder stage"
fi

# Verify cargo is available (COPY'd from builder)
if command -v cargo &>/dev/null; then
log_info "cargo is already installed"
else
log_warn "cargo not found — expected to be copied from Rust builder stage"
fi

# Verify clippy is available (rustup component, COPY'd from builder)
if command -v cargo-clippy &>/dev/null; then
log_info "clippy is already installed"
else
log_warn "clippy not found — expected to be copied from Rust builder stage"
fi

# Verify rustfmt is available (rustup component, COPY'd from builder)
if command -v rustfmt &>/dev/null; then
log_info "rustfmt is already installed"
else
log_warn "rustfmt not found — expected to be copied from Rust builder stage"
fi

# Verify cargo-audit is available (built in builder)
if command -v cargo-audit &>/dev/null; then
log_info "cargo-audit is already installed"
else
log_warn "cargo-audit not found — expected to be copied from Rust builder stage"
fi

# Verify cargo-deny is available (built in builder)
if command -v cargo-deny &>/dev/null; then
log_info "cargo-deny is already installed"
else
log_warn "cargo-deny not found — expected to be copied from Rust builder stage"
fi

log_info "Rust tooling verification complete"
Loading
Loading