diff options
Diffstat (limited to 'Omni/Ide')
| -rw-r--r-- | Omni/Ide/README.md | 143 | ||||
| -rwxr-xr-x | Omni/Ide/ailint.sh | 11 | ||||
| -rwxr-xr-x | Omni/Ide/hooks/commit-msg | 2 | ||||
| -rwxr-xr-x | Omni/Ide/hooks/post-checkout | 1 | ||||
| -rwxr-xr-x | Omni/Ide/hooks/post-merge | 1 | ||||
| -rwxr-xr-x | Omni/Ide/hooks/pre-push | 6 | ||||
| -rwxr-xr-x | Omni/Ide/push.sh | 168 | ||||
| -rwxr-xr-x | Omni/Ide/repl.sh | 7 | ||||
| -rwxr-xr-x | Omni/Ide/run.sh | 4 | ||||
| -rwxr-xr-x | Omni/Ide/typecheck.sh | 37 |
10 files changed, 343 insertions, 37 deletions
diff --git a/Omni/Ide/README.md b/Omni/Ide/README.md new file mode 100644 index 0000000..7511090 --- /dev/null +++ b/Omni/Ide/README.md @@ -0,0 +1,143 @@ +# Development Tools and Workflow + +## Tools + +### run.sh + +`run.sh` is a convenience wrapper that builds (if needed) and runs a namespace. + +Examples: +```bash +Omni/Ide/run.sh Omni/Task.hs # Build and run task manager +Omni/Ide/run.sh Biz/PodcastItLater/Web.py # Build and run web server +``` + +This script will: +1. Check if the binary exists in `_/bin/` +2. Build it if it doesn't exist (exits on build failure) +3. Execute the binary with any additional arguments + +### lint + +Universal lint and formatting tool. Errors if lints fail or code is not formatted properly. + +Examples: +```bash +lint Omni/Cli.hs # Lint a namespace +lint --fix **/*.py # Lint and fix all Python files +``` + +### repl.sh + +Like `nix-shell` but specific to this repo. Analyzes the namespace, pulls dependencies, and starts a shell or repl. + +Examples: +```bash +repl.sh Omni/Bild.hs # Start Haskell repl with namespace loaded +repl.sh --bash Omni/Log.py # Start bash shell for namespace +``` + +### typecheck.sh + +Like `lint` but only runs type checkers. Currently just supports Python with `mypy`, but eventually will support everything that `bild` supports. + +Examples: +```bash +typecheck.sh Omni/Bild/Example.py # Run the typechecker and report any errors +``` + +### Test Commands + +Run tests: +```bash +bild --test Omni/Task.hs # Build and test a namespace +``` + +The convention for all programs in the omnirepo is to run their tests if the first argument is `test`. So for example: + +```bash +# this will build a the latest executable and then run tests +bild --test Omni/Task.hs + +# this will just run the tests from the existing executable +_/bin/task test +``` + +## Git Workflow + +### Use git-branchless + +This repository uses **git-branchless** for a patch-based workflow instead of traditional branch-based git. + +Key concepts: +- Work with **patches** (commits) directly rather than branches +- Use **stacking** to organize related changes +- Leverage **smartlog** to visualize commit history + +### Common git-branchless Commands + +**View commit graph:** +```bash +git smartlog +``` + +**Create a new commit:** +```bash +# Make your changes +git add . +git commit -m "Your commit message" +``` + +**Amend the current commit:** +```bash +# Make additional changes +git add . +git amend +``` + +**Move/restack commits:** +```bash +git move -s <source> -d <destination> +git restack +``` + +### When to Record Changes in Git + +**DO record in git:** +- Completed features or bug fixes +- Working code that passes tests and linting +- Significant milestones in task completion + +**DO NOT record in git:** +- Work in progress (unless specifically requested) +- Broken or untested code +- Temporary debugging changes + +**NEVER do these git operations without explicit user request:** +- ❌ `git push` - NEVER push to remote unless explicitly asked +- ❌ `git pull` - NEVER pull from remote unless explicitly asked +- ❌ Force pushes or destructive operations +- ❌ Branch deletion or remote branch operations + +**Why:** The user maintains control over when code is shared with collaborators. Always ask before syncing with remote repositories. + +### Workflow Best Practices + +1. **Make small, focused commits** - Each commit should represent one logical change +2. **Write descriptive commit messages** - Explain what and why, not just what +3. **Rebase and clean up history** - Use `git commit --amend` and `git restack` to keep history clean +4. **Test before committing** - Run `bild --test` and `lint` on affected namespaces + +### Required Checks Before Completing Tasks + +After completing a task, **always** run these commands for the namespace(s) you modified: + +```bash +# Run tests +bild --test Omni/YourNamespace.hs + +# Run linter +lint Omni/YourNamespace.hs +``` + +**Fix all reported errors** related to your changes before marking the task as complete. This ensures code quality and prevents breaking the build for other contributors. diff --git a/Omni/Ide/ailint.sh b/Omni/Ide/ailint.sh new file mode 100755 index 0000000..a107be8 --- /dev/null +++ b/Omni/Ide/ailint.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -xu +target=${1:?} +instructions=$(mktemp) +echo "Fix the following lint errors, or silence them with a `# noqa:` comment if they aren't problematic:" >> "$instructions" +if lint -f "$target" >> "$instructions" 2>&1 +then + echo "no bad lints" +else + aider --yes --message-file "$instructions" "$target" +fi diff --git a/Omni/Ide/hooks/commit-msg b/Omni/Ide/hooks/commit-msg index e07d1f4..a91b651 100755 --- a/Omni/Ide/hooks/commit-msg +++ b/Omni/Ide/hooks/commit-msg @@ -1,7 +1,7 @@ #!/usr/bin/env bash if ! gitlint --ignore-stdin --staged --msg-filename "$1" run-hook; then backup="$CODEROOT"/.git/COMMIT_EDITMSG.backup - cp "$CODEROOT"/.git/COMMIT_EDITMSG "$backup" + cp "$1" "$backup" echo "error: gitlint failed, saved your commit msg as $backup" exit 1 fi diff --git a/Omni/Ide/hooks/post-checkout b/Omni/Ide/hooks/post-checkout index 85541a2..a360517 100755 --- a/Omni/Ide/hooks/post-checkout +++ b/Omni/Ide/hooks/post-checkout @@ -14,6 +14,7 @@ elif [[ ${#changed[@]} -gt 0 ]] then MakeTags "${changed[@]}" fi + ## START BRANCHLESS CONFIG git branchless hook post-checkout "$@" diff --git a/Omni/Ide/hooks/post-merge b/Omni/Ide/hooks/post-merge index fcfd314..bf0e996 100755 --- a/Omni/Ide/hooks/post-merge +++ b/Omni/Ide/hooks/post-merge @@ -1,5 +1,6 @@ #!/usr/bin/env bash "${CODEROOT:?}"/Omni/Ide/hooks/post-checkout 'HEAD@{1}' HEAD + ## START BRANCHLESS CONFIG git branchless hook post-merge "$@" diff --git a/Omni/Ide/hooks/pre-push b/Omni/Ide/hooks/pre-push index 00110bd..adbf858 100755 --- a/Omni/Ide/hooks/pre-push +++ b/Omni/Ide/hooks/pre-push @@ -1,5 +1,11 @@ #!/usr/bin/env bash set -euo pipefail + +# Task manager: Ensure tasks are exported before push +if [ -d .tasks ]; then + task export --flush 2>/dev/null || true +fi + remote="$1" z40=0000000000000000000000000000000000000000 IFS=" " diff --git a/Omni/Ide/push.sh b/Omni/Ide/push.sh index 5c22e07..f6d67f4 100755 --- a/Omni/Ide/push.sh +++ b/Omni/Ide/push.sh @@ -1,34 +1,136 @@ #!/usr/bin/env bash -# Eventually convert to haskell, see: -# - https://github.com/awakesecurity/nix-deploy/blob/master/src/Main.hs -# - http://www.haskellforall.com/2018/08/nixos-in-production.html -prefix=${PWD/$CODEROOT} -if [[ "$prefix" == "" ]] -then - target="$1" -else - target="$prefix.$1" -fi -what=$(realpath "${CODEROOT:?}/_/nix/$target") -# hack: get the domain from the systemd service. there does not seem to be a way -# to get it from nix-instantiate. (or, maybe i should put this in bild --plan?) -where=$(rg --only-matching --replace '$2' --regexp '(domainname ")(.*)(")' \ - "$what/etc/systemd/system/domainname.service") -nix copy --to ssh://"$USER"@"$where" "$what" -ssh "$USER"@"$where" sudo nix-env --profile /nix/var/nix/profiles/system --set "$what" -switch_cmd=( - systemd-run - -E LOCALE_ARCHIVE - --collect - --no-ask-password - --pipe - --quiet - --service-type=exec - --unit=push-switch-to-configuration - --wait - "$what/bin/switch-to-configuration" - "switch" -) -# shellcheck disable=SC2029 -ssh "$USER"@"$where" sudo "${switch_cmd[@]}" -echo "${GRN}good: push: $target${NC}" +# Deployment script for both NixOS configs and individual services. +# +# Usage: +# push.sh Biz.nix # NixOS deploy (existing behavior) +# push.sh Biz/PodcastItLater/Web.py # Service deploy (new behavior) +# +# For service deploys: +# 1. Builds the target with bild (caches to S3 by default) +# 2. Updates the manifest.json in S3 with new store path +# 3. Deployer on target picks up change within 5 minutes +# +# Environment: +# CODEROOT - Root of the codebase (required) +# NIX_CACHE_KEY - Path to signing key (required for service deploys) + +set -euo pipefail + +# Colors +GRN='\033[0;32m' +YLW='\033[0;33m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +# Derive service name from target path +# Biz/PodcastItLater/Web.py -> podcastitlater-web +# Biz/Storybook.py -> storybook +derive_service_name() { + local target="$1" + # Remove extension + local base="${target%.*}" + # Remove Biz/ prefix if present + base="${base#Biz/}" + # Convert slashes to hyphens and lowercase + echo "$base" | tr '/' '-' | tr '[:upper:]' '[:lower:]' +} + +# NixOS deploy (existing behavior) +nixos_deploy() { + local target="$1" + prefix=${PWD/$CODEROOT} + if [[ "$prefix" == "" ]]; then + target="$1" + else + target="$prefix.$1" + fi + what=$(realpath "${CODEROOT:?}/_/nix/$target") + # hack: get the domain from the systemd service + where=$(rg --only-matching --replace '$2' --regexp '(domainname ")(.*)(")' \ + "$what/etc/systemd/system/domainname.service") + nix copy --to ssh://"$USER"@"$where" "$what" + ssh "$USER"@"$where" sudo nix-env --profile /nix/var/nix/profiles/system --set "$what" + switch_cmd=( + systemd-run + -E LOCALE_ARCHIVE + --setenv=XDG_RUNTIME_DIR="" + --collect + --no-ask-password + --pipe + --quiet + --service-type=exec + --unit=push-switch-to-configuration + --wait + "$what/bin/switch-to-configuration" + "switch" + ) + # shellcheck disable=SC2029 + ssh "$USER"@"$where" sudo "${switch_cmd[@]}" + echo -e "${GRN}good: push: $target${NC}" +} + +# Service deploy (new behavior) +service_deploy() { + local target="$1" + local service_name + service_name=$(derive_service_name "$target") + + echo -e "${YLW}info: push: deploying service $service_name${NC}" + + # 1. Build and cache + echo -e "${YLW}info: push: building $target${NC}" + if ! bild "$target"; then + echo -e "${RED}fail: push: bild failed${NC}" + exit 1 + fi + + # Get store path from symlink in _/nix/ + local symlink_path="${CODEROOT}/_/nix/${service_name}" + if [[ ! -L "$symlink_path" ]]; then + echo -e "${RED}fail: push: symlink not found: $symlink_path${NC}" + exit 1 + fi + + local store_path + store_path=$(readlink "$symlink_path") + + if [[ -z "$store_path" ]]; then + echo -e "${RED}fail: push: could not read store path from symlink${NC}" + exit 1 + fi + + echo -e "${YLW}info: push: cached $store_path${NC}" + + # 2. Get git revision + local revision + revision=$(git rev-parse --short HEAD 2>/dev/null || echo "unknown") + + # 3. Update manifest in S3 + echo -e "${YLW}info: push: updating manifest${NC}" + "${CODEROOT}/_/nix/deploy-manifest/bin/deploy-manifest" update "$service_name" "$store_path" "$revision" || { + echo -e "${RED}fail: push: manifest update failed${NC}" + exit 1 + } + + echo -e "${GRN}good: push: $service_name deployed (deployer will pick up in <5 min)${NC}" +} + +# Main +main() { + if [[ $# -lt 1 ]]; then + echo "Usage: push.sh <target>" + echo " target.nix -> NixOS deploy" + echo " target.py/.hs/.. -> Service deploy" + exit 1 + fi + + local target="$1" + + if [[ "$target" == *.nix ]]; then + nixos_deploy "$target" + else + service_deploy "$target" + fi +} + +main "$@" diff --git a/Omni/Ide/repl.sh b/Omni/Ide/repl.sh index 3b6a536..6225078 100755 --- a/Omni/Ide/repl.sh +++ b/Omni/Ide/repl.sh @@ -10,6 +10,7 @@ ### ### Options: ### --bash start bash instead of the target language repl +### --cmd x run 'x' instead of bash, or the target language repl help() { sed -rn 's/^### ?//;T;p' "$0" } @@ -23,9 +24,13 @@ fi if [[ "$1" == "--bash" ]]; then CMD="bash" shift + elif [[ "$1" == "--cmd" ]]; then + shift + CMD="$1" + shift fi targets="${*:?}" - json=$(bild --plan "${targets[@]}") + json=$(bild --plan "${targets[@]}" 2>&1) mapfile -t langdeps < <(jq --raw-output '.[].langdeps | select(length > 0) | join("\n")' <<< "$json") mapfile -t sysdeps < <(jq --raw-output '.[].sysdeps | select(length > 0) | join("\n")' <<< "$json") mapfile -t rundeps < <(jq --raw-output '.[].rundeps | select(length > 0) | join("\n")' <<< "$json") diff --git a/Omni/Ide/run.sh b/Omni/Ide/run.sh index e300fcc..c426969 100755 --- a/Omni/Ide/run.sh +++ b/Omni/Ide/run.sh @@ -2,6 +2,6 @@ set -eu target=$1 shift -out=$(bild --plan "$target" | jq --raw-output ".\"${target}\".out") -[[ -f "$out" ]] || bild "$target" +out=$(bild --plan "$target" 2>&1 | tail -1 | jq --raw-output ".\"${target}\".out") +bild "$target" || exit 1 exec "${CODEROOT:?}/_/bin/$out" "$@" diff --git a/Omni/Ide/typecheck.sh b/Omni/Ide/typecheck.sh new file mode 100755 index 0000000..fe11ef5 --- /dev/null +++ b/Omni/Ide/typecheck.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env bash +### +### typecheck a given target +### +### > typecheck.sh <target..> +### +### Uses repl.sh to provision the environment for target, then runs the +### appropriate typechecker for the given module. +### +help() { + sed -rn 's/^### ?//;T;p' "$0" +} +if [[ $# == 0 ]] || [[ "$1" == "-h" ]]; then + help + exit 1 +fi +target="$1" + +# Determine file extension +ext="${target##*.}" + +case "$ext" in + py) + # Python: use mypy via repl.sh environment + repl.sh --cmd "python -m mypy $target" "$target" + ;; + hs) + # Haskell: use ghc -fno-code for typechecking without code generation + # Use repl.sh to provision the right GHC environment with dependencies + repl.sh --cmd "ghc -fno-code -i${CODEROOT:?} $target" "$target" + ;; + *) + echo "Unknown file extension: $ext" + echo "Typechecking not supported for this file type" + exit 1 + ;; +esac |
