Project Scripts
paul-samuels.com
paul-samuels.com
Useful commands would be copied over into the directory that contains the commands accessible to all. Usefulness was determined at the coffee machine, the smokers' room or in conversations after presentations on their work given by devs.
Also, interested interns would be tasked to spot "optimization potential" - so they read through these commands, learned to read and write shell scripts and they had to learn to efficiently address senior devs.
Everytime this was more a social thing than actual workflow optimization: Tooling became a permant side project, people could (and would) look their peers' scripts, one could take a short break from the main project by working on this. One observation I made: the more senior the devs, the less interested they were in this "playground" - after all good devs are always faster at hacking solutions to these simple problems than evaluating and adapting existing ones.
I am worried when I have to install a full dependency graph to run your build scripts...that's where things become brittle and annoying.
Python with Typer[0] and Plumbum[1] is great for quickly cranking out ergonomic build scripts, or Invoke[3] which is intended for exactly this purpose.
[0] https://typer.tiangolo.com/ [1] https://plumbum.readthedocs.io/en/latest/ [3] https://www.pyinvoke.org/
But then I've gone from "any Python 3.x can be used as-is" to "setup a Python venv and install these dependencies" as soon as I add any dependency :(
So you either have to constrain yourself to Argparse and Subprocess in the Python stdlib, or accept that your build scripts are a project unto themselves.
This applies similarly to Ruby, TS/JS/Node, Perl, etc.
Just use something like this as a shebang, and you can have your cake and eat it too!
#!/usr/bin/env -S uv run
previous HN thread: https://news.ycombinator.com/item?id=43097006
I wish Python had a native way to build self-contained executables like Go. The binary would be larger since it’s a dynamically interpreted language, but as long as it’s self-contained, I wouldn’t mind.
As the project has grown these bash functions have become more and more esoteric. Discoverability is not as good as could be expected from a cli tool with subcommands and simple help strings.
Additionally, I've found this setup has caused issues with various tooling that an executable cli tool would not have. For example, zellij layout commands do not run through a shell so you cannot specify a bash function/alias as a pane command.
Many people think of Bash as "special", but it's just another interpreted language like Ruby or Python or Perl. It's that many people also use a Bash repl for their interactive shell that they get confused.
I started off using Basecamp sub, then a rust remake called tome and finally simplified it as a golang project https://github.com/zph/tome-cli.
The elegance of this solution is having a helper binary that wraps a folder of sub folders and scripts as a cohesive sdk.
Copied from readme, the features are: - Organize a folder of scripts into a CLI sdk - Any language is supported via normal script #! - Usage text extracted from script header if USAGE: is included in leading comments - Full help text extracted as lines from USAGE: to first empty line - Builtin alias generator allows for embedding configuration flags via tome-cli alias Auto completion of: subcommands (exec, help, etc) root folder's folder names root folder's scripts root script's flags and arguments (when they satisfy the --complete and TOME_COMPLETION interface) Gitignore like syntax for ignoring scripts by using a .tomeignore file at base of root folder example
The trick to allowing any script language is to only allow self contained dependency languages like deno, python with uv inline deps or golang with bash wrapper. And then you need to vendor those runtime references in repo as well using hermit or dotslash so that the toolkit is self contained.
It saves a good bit of time to have a central cli andale it easy to extend
Both Bash and Python are a given anywhere. I use Bash for simple stuff and Python for the complex stuff. Thanks to each script being a separate file, I can mix Bash and Python, but using only Python is very valid.
But doing it in Kotlin is pushing it too far in my book. Kotlin compilation is slow, and running the scripts is nowhere near as ergonomic as running python -m script or go run script.go.
I prefer using a Makefile over creating a CLI. Adding and removing Make targets is trivial and doesn’t require me to fiddle with CLI entry point code. But Make syntax is fugly, so I keep the targets lean, and they mostly just invoke the scripts.
Don't think you necessarily need to use the same language as the rest of the project. Python scripts can do the job just fine, given you can wrangle the appropriate program entry points via the CLI interface.