summaryrefslogtreecommitdiff
path: root/Omni/Ide
diff options
context:
space:
mode:
Diffstat (limited to 'Omni/Ide')
-rw-r--r--Omni/Ide/README.md143
-rwxr-xr-xOmni/Ide/ailint.sh11
-rwxr-xr-xOmni/Ide/hooks/commit-msg2
-rwxr-xr-xOmni/Ide/hooks/post-checkout1
-rwxr-xr-xOmni/Ide/hooks/post-merge1
-rwxr-xr-xOmni/Ide/hooks/pre-push6
-rwxr-xr-xOmni/Ide/push.sh168
-rwxr-xr-xOmni/Ide/repl.sh7
-rwxr-xr-xOmni/Ide/run.sh4
-rwxr-xr-xOmni/Ide/typecheck.sh37
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