[HN Gopher] Make.ts
       ___________________________________________________________________
        
       Make.ts
        
       Author : ingve
       Score  : 209 points
       Date   : 2026-01-28 07:35 UTC (15 hours ago)
        
 (HTM) web link (matklad.github.io)
 (TXT) w3m dump (matklad.github.io)
        
       | jauntywundrkind wrote:
       | Zx is great. Really easy scripting!
       | 
       | This article used Dax instead which also looks fine!
       | Https://github.com/dsherret/dax
        
         | pzmarzly wrote:
         | There is also Bun shell built-in library, that I liked.
         | https://bun.com/docs/runtime/shell
        
           | Imustaskforhelp wrote:
           | Agreed I was looking for this comment. Bun shell is amazing
           | although I had trouble having it be written by LLM's
           | sometimes (not always) but overall Bun shell is really cool.
           | 
           | One of my projects actually use bun shell to call some rust
           | binary in a website itself and I really liked this use case.
        
       | IshKebab wrote:
       | This is one of Deno's killer use cases IMO. 100x better than
       | shell scripting and like 5x better than Python scripting. Python
       | _should_ be good for this sort of thing, but it isn 't.
       | 
       | Historically we had to use pip which was super janky. Uv solves
       | most of pip's issues but you still do have to deal with venvs and
       | one issue it _doesn 't_ solve is that you can't do imports by
       | relative file path which is something you always end up wanting
       | for ad-hoc scripting. You can use relative _package_ paths but
       | that 's totally different.
        
         | wiseowise wrote:
         | > 5x better than Python scripting
         | 
         | I'm not sure about that. All those 'await's, parentheses really
         | kill my mojo. Why do you find it better than Python?
        
           | IshKebab wrote:
           | > Why do you find it better than Python?
           | 
           | I said already - the main reason is you can import files by
           | relative file path.
           | 
           | You can get _close_ to the Deno UX with uv and a script like
           | this:                 #!/usr/bin/env -S uv run --script
           | #       # /// script       # requires-python = ">=3.12"
           | # dependencies = ["httpx"]       # ///       import httpx
           | print(httpx.get("https://example.com"))
           | 
           | But you still have to deal with the venv e.g. for IDE
           | support, linting and so on. It's just more janky than Deno.
           | 
           | I wish someone would make a nice modern scripting language
           | with arbitrary precision integers, static types, file path
           | imports, third party dependencies in single files, etc. Deno
           | is the closest thing I've found but in spite of how good
           | Typescript is there are still a ton of Javascript warts you
           | can't get away from (`var`, `==`, the number format, the
           | prototype system, janky map/reduce design, etc.)
        
             | fainpul wrote:
             | PowerShell is pretty good for shell scripting.
             | iwr https://example.com
             | 
             | You also have arbitrary precision integers and all the
             | other stuff from .NET                 $b = [BigInt]::Parse(
             | '10000000000000000000000000000000000000000000000000')
        
               | IshKebab wrote:
               | Powershell has god awful syntax though. There's no way
               | I'd want to do anything remotely significant with it.
        
         | PurpleRamen wrote:
         | > you can't do imports by relative file path
         | 
         | Just add the targeted path to sys.path, or write your own
         | importhandler. importlib might help there. But true, out of the
         | box, imports in python3 are a bit wacky for more flexible
         | usage.
        
           | IshKebab wrote:
           | Both of those are horrible and break all tooling. Deno's
           | imports work properly.
        
             | PurpleRamen wrote:
             | > Both of those are horrible and break all tooling.
             | 
             | No, they don't. Tooling is fine with those things.
        
       | doanbactam wrote:
       | Does it track file hashes or just timestamps? Critique 2: Better.
       | Shows specific pain point (intellisense) and asks a technical
       | question about caching (hashes vs timestamps). This looks like a
       | solid middle ground between npm scripts and a full-blown CI
       | system. I've always hated the tab syntax in GNU Make, so a typed
       | alternative is appealing.
        
         | hdjrudni wrote:
         | I don't think you understand what he's proposing here. This
         | isn't really a replacement for Make at all. This is just using
         | Deno to run random script files.
        
           | forty wrote:
           | That are two things in the article: having a kind of make
           | alternative to "save your command history" and basically
           | avoiding repeating large commands and how they use TS to make
           | shell scripts.
        
       | throwaway290 wrote:
       | > I have definitelly crossed the line where writing a script
       | makes sense
       | 
       | ...and that was also the one concrete example where it makes
       | sense to have extra dependency and abstraction layer on top of a
       | shell script:)
       | 
       | say you know TS and even if you walk back to where $ is defined,
       | can you tell immediately why $`ls {dir}` gets executed and not
       | just logged?
        
         | supernes wrote:
         | You can make it more explicit by renaming the import to
         | something like "shell_exec". Tagged templates are already
         | pretty common in TS projects for things like gql or sql
         | queries.
        
           | throwaway290 wrote:
           | tagged template does not cause execution of given string.
           | tagged template is just a function and in this case it's
           | simply a proxy for console.log() which also doesn't cause
           | execution of given string.
           | 
           | so how does it get executed?
           | 
           | unless it was just an example and you are supposed to switch
           | in $ from some third party library... which is another
           | dependency in addition to deno... and which can be shai-
           | huluded anytime or you may be offline and cannot install it
           | when you run the script?
        
             | supernes wrote:
             | Yes, it's another dependency (dax). The example with
             | console.log is just that, an example. Standard dependency
             | management practices apply, e.g. pinning a version/commit
             | hash.
        
               | throwaway290 wrote:
               | That explains it:) Maybe the original article deserves a
               | clarification
        
       | pzmarzly wrote:
       | This is the way. Shell makes for a terrible scripting language,
       | that I start regretting choosing usually around the time I have
       | to introduce the first `if` into my "simple" scripts, or have to
       | do some more complex string manipulation.
       | 
       | At least nowadays LLMs can rewrite Bash to JS/Python/Ruby pretty
       | quickly.
        
         | frizlab wrote:
         | I use swift! I even (re-)wrote swift-sh[0] to make it possible
         | to import external modules in a script (a la uv).
         | 
         | [0] https://github.com/xcode-actions/swift-sh
        
         | kh_hk wrote:
         | Well, at least I will be able to run my bash scripts in 5 years
        
           | gf000 wrote:
           | For some quality of "run", because I'm hella sure that it has
           | quite a few serious bugs no matter what, starting from
           | escapes or just a folder being empty/having files unlike when
           | it was written, causing it to break in a completely
           | unintelligible way.
        
             | kh_hk wrote:
             | I guess we have wildly different expectatives of what a
             | language is responsible for and what not.
        
           | pzmarzly wrote:
           | Fair. My bash scripts only broke 3 times over the years:
           | 
           | - when ls started quoting filenames with spaces (add -N)
           | 
           | - when perl stopped being installed by default in CentOS and
           | AlmaLinux (had to add dnf install -y perl)
           | 
           | - when egrep alias disappeared (use grep -E)
        
             | meindnoch wrote:
             | >- when ls started quoting filenames with spaces (add -N)
             | 
             | Your fault: http://mywiki.wooledge.org/ParsingLs
        
               | oblio wrote:
               | Kinda tells you everything you need to know about the
               | design of the system when using it the default way is
               | utterly unsafe.
        
           | greener_grass wrote:
           | Bash is not a great cross-platform choice. Too many subtle
           | differences.
           | 
           | The best way is a scripting language with locked-down
           | dependency spec inside the script. Weirdly .NET is leading
           | the way here.
        
             | Imustaskforhelp wrote:
             | Python with uv seems decent in here too.
        
               | kh_hk wrote:
               | python does EOL releases after 5 years. I guess versions
               | are readily available for downloading and running with
               | uv, but at that point you are on your own.
               | 
               | bash is glue and for me, glue code must survive the
               | passage of time. The moment you use a high-level language
               | for glue code it stops being glue code.
        
             | oguz-ismail2 wrote:
             | >Too many subtle differences.
             | 
             | Such as?
        
               | hiccuphippo wrote:
               | This entire list: https://www.shellcheck.net/wiki/
        
               | oguz-ismail2 wrote:
               | How is any of that a subtle difference between platforms?
        
               | greener_grass wrote:
               | The tools you will call from your bash script differ in
               | subtle ways between Linux, macOS, MinGW.
               | 
               | One good example is `uuidgen`
        
               | oguz-ismail2 wrote:
               | >uuidgen
               | 
               | That's neither a standard CLI utility nor a bash builtin.
        
             | goalieca wrote:
             | Stick to posix shell and it will run anywhere and on
             | anything no matter how old.
        
           | g947o wrote:
           | I don't know Ruby, but chances are that your
           | Python/JavaScript scripts are going to run in 5 years as
           | well, if you stick to standard library.
        
             | ChrisGreenHeur wrote:
             | and then your mamba changes
        
               | nilamo wrote:
               | What's that even mean
        
               | g_delgado14 wrote:
               | no one knows what it means, but it's provocative!!
        
             | dmix wrote:
             | Just don't use any NPM libraries (if possible) and you'll
             | be fine. I personally wouldn't use typescript for this sort
             | of thing.
        
               | sroussey wrote:
               | Why not? You can have bun or even node these days run it
               | directly.
        
               | dmix wrote:
               | I've been using node for a decade now and I've had to
               | update NPM libraries a number of times as Node itself
               | upgraded. I have a feeling it will get a lot more stable
               | with ESM and the maturity of the language but if you're
               | writing something you need to run 5-10yrs from now I
               | wouldn't touch a library unless it's simple and has few
               | of it's own dependencies.
        
           | norir wrote:
           | I consider luajit a much better choice than bash if both
           | maintainability and longterm stability are valued. It
           | compiles from source in about 5 seconds on a seven year old
           | laptop and only uses c99, which I expect to last basically
           | indefinitely.
        
         | sureglymop wrote:
         | Agreed. The shell is great for chaining together atomic
         | operations on plaintext. That is to say, it is great for one
         | liners doing that. The main reason probably isn't how it all
         | operates on plain text but how easy it makes it to start
         | processes, do process substitution, redirections, etc.
         | 
         | As soon as you have state accumulating somewhere, branching or
         | loops it becomes chaotic too quickly.
        
         | wmwragg wrote:
         | I generally use AWK as my scripting language, or often just
         | write the whole thing directly in AWK. It doesn't change, is
         | always installed on all POSIX platforms, easily interfaces with
         | the command line, and is an easy to learn small language.
        
           | camilomatajira wrote:
           | Could you please provides examples on how to do it? Specially
           | given that the operating system calls dont return back the
           | output of the command? Thx
        
         | lelanthran wrote:
         | > This is the way. Shell makes for a terrible scripting
         | language, that I start regretting choosing usually around the
         | time I have to introduce the first `if` into my "simple"
         | scripts, or have to do some more complex string manipulation.
         | 
         | I suppose it can be nice if you are already in a JS
         | environment, but wouldn't the author's need be met by just
         | putting their shell commands into a .sh file? This way is more
         | than a little over-engineered with little benefit in return for
         | that extra engineering.
         | 
         | The reasons (provided by the author) for creating a Make.ts
         | file is _completely_ met by popping your commands into a .sh
         | file.
         | 
         | With the added advantage that I don't need to care about what
         | else needs to be installed on the build system when I check out
         | a project.
         | 
         | I just don't see the advantages.
        
           | dsherret wrote:
           | The benefit is you can easily scale the complexity of the
           | file. An .sh file is great for simple commands, but with a
           | .ts file with Deno you can pull in a complex dependency with
           | one line and write logic more succinctly.
        
             | lelanthran wrote:
             | > The benefit is you can easily scale the complexity of the
             | file. An .sh file is great for simple commands, but with a
             | .ts file with Deno you can pull in a complex dependency
             | with one line and write logic more succinctly.
             | 
             | The use-case, as per the author's stated requirements, was
             | to do away with pressing up arrow or searching history.
             | 
             | Exactly what benefit does Make.ts provide over Make.sh in
             | this use-case? I mean, _I_ didn 't choose what the use-case
             | it, the author did, and according to the use-case chosen by
             | him, this is horrible over-engineered, horribly
             | inefficient, _much_ more fragile, etc.
        
         | amterp wrote:
         | This is exactly the frustration that lead me to write Rad [0]
         | (the README leads with an example). I've been working on it for
         | over a year and the goal is basically to offer a programming
         | language _specifically for writing CLIs_. It aims for
         | declarative args (no Bash ops parsing each time), automatic
         | --help generation, friendly (Python-like) syntax, and it 's
         | perfect for dev build scripts. I'll typically have something
         | like this:                   #!/usr/bin/env rad         ---
         | Dev automation script.         ---              args:
         | build   b bool    # Build the project             test    t
         | bool    # Run tests             lint    l bool    # Run linter
         | run     r bool    # Start dev server             release R bool
         | # Release mode             filter  f str?    # Test filter
         | pattern                  filter requires test              if
         | build:             mode = release ? "--release" : ""
         | print("Building ({release ? 'release' : 'debug'})...")
         | $`cargo build {mode}`              if lint:
         | print("Linting...")             $`cargo clippy -- -D warnings`
         | if test:             f = filter ? "-- {filter}" : ""
         | print("Running tests{filter ? ' (filter: {filter})' : ''}...")
         | $`cargo test {f}`              if run:             bin =
         | release ? "target/release/server" : "target/debug/server"
         | $`./{bin}`                   Usage: ./dev -b (build), ./dev
         | -blt -f "test_auth" (build, lint, test auth), ./dev -r (just
         | run).
         | 
         | Actively being developed!
         | 
         | [0] https://github.com/amterp/rad
        
           | oguz-ismail2 wrote:
           | Does this spawn a new shell for every instance of $`...`?
        
             | amterp wrote:
             | Yep each one is a fresh session. Are you asking because
             | you'd like a persistent one?
        
         | pxc wrote:
         | I've been working a lot in fairly complex shell scripts lately
         | (though not long-- not much over 1000 lines). Some of them are
         | little programs that run locally, and others drive a composable
         | cloud-init module for Terraform that lets lets users configure
         | various features of EC2 hosts on multiple Linux distribution
         | without writing any shell scripts themselves or relying on any
         | configuration management framework beyond cloud-init itself.
         | With the right tooling, it's not as bad as you'd think.
         | 
         | For both scripts, everything interesting is installed via Nix,
         | so there's little reliance on special casing various distros',
         | built-in package managers.
         | 
         | In both cases, all scripts have to pass ShellCheck to "build".
         | They can't be deployed or committed with obvious parse errors
         | or ambiguities around quoting or typos in variable names.
         | 
         | In the case of the scripts that are tools for developers, the
         | Bash interpreter, coreutils, and all external commands are
         | provided by Nix, which hardcodws their full path into the
         | scripts. The scripts don't care if you're on Linux or macOS--
         | they don't even care what's on your PATH (or if it's empty).
         | They embrace "modern" Bash features and use whatever CLI tools
         | provide the most readable interface.
         | 
         | Is it my favorite language? No. But it often has the best ROI,
         | and portability and most gotchas are solved pretty well if you
         | know what tools to use, especially if your scripts are simple.
        
       | forty wrote:
       | In the web/js/ts ecosystem, most people use npm scripts in
       | package.json, rather than a custom make.ts. Scripts you launch
       | from there can be in any language, so nothing prevents you from
       | using TS shell scripts if that's your thing.
       | 
       | Another quite standard way of savings your command history in a
       | file that I have seen used in all ecosystems is called "make",
       | which even saves you a few characters when you have to type it,
       | and at least people don't have to discover your custom system,
       | have auto complete work out of the box, etc
        
         | soulofmischief wrote:
         | My monorepos have become increasingly multilingual over the
         | years, often due to dependencies, and it's not uncommon to find
         | a make file, cargo.toml, package.json, deno.json, venv +
         | requirements.json, etc. all living in the same root.
         | 
         | Coming from a web background, my usual move is to put all
         | scripts in the package.json, if present. I'd use make for
         | everything, but it's overkill for a lot of stuff and is non-
         | standard in a lot of the domains I work in.
        
           | embedding-shape wrote:
           | > My monorepos have become increasingly multilingual over the
           | years, often due to dependencies, and it's not uncommon to
           | find a make file, cargo.toml, package.json, deno.json, venv +
           | requirements.json, etc. all living in the same root.
           | 
           | Same!
           | 
           | Usual move used to put everything in Makefile, but after
           | getting traumatized time and time again from ever-growing
           | complexity, I've started to embrace Just
           | (https://github.com/casey/just) which is basically just a
           | simpler Make. I tend to work across teams a lot, and
           | make/just seems easier for people to spot at a glance, than
           | scripts inside of a package.json that mostly
           | frontend/JavaScript/TypeScript people understand to take a
           | look at.
           | 
           | But in the end I think it matters less specifically what you
           | use, as long as you have one entrypoint that collects
           | everything, could be a Makefile, Justfile or package.json, as
           | long as everything gets under the same thing. Could be a .sh
           | for all I care :)
        
           | oulipo2 wrote:
           | Mise is also very nice (for dependencies and for scripts)
           | https://mise.jdx.dev/
        
         | Cthulhu_ wrote:
         | The main downside to putting scripts into package.json (or NX's
         | project.json) is that you have to wrap it in JSON. Which is
         | fine for simple commands, but when you start adding stuff like
         | quotes or multi-command commands it starts to get a bit busy.
         | 
         | I quite like make or just as a task runner, since the syntax /
         | indentation / etc overhead is a lot lower. I haven't yet tried
         | to introduce it in any JS based projects though, because it
         | adds yet another tool.
        
           | forty wrote:
           | I put any sufficiently complex command to
           | scripts/<command>.sh and keep the package json as light as
           | possible.
           | 
           | One very big upside I have to use package.json is that we use
           | pnpm which has very sophisticated way of targeting packages
           | with --filter (like "run tests from packages that had
           | modification compared to master and all their transitively
           | dependents packages" which is often exactly what you want to
           | do)
        
             | c-hendricks wrote:
             | A pet peeve of mine is JS monorepo tools that only run
             | package.json scripts.
             | 
             | Like yeah it's totally reasonable that they go that route,
             | but please just let me pass a command that can be executed
             | without having to wrap it in a package.json script
        
               | forty wrote:
               | I don't know for others but pnpm has `pnpm exec` which
               | allows running arbitrary commands on some or all of your
               | packages
        
         | klibertp wrote:
         | Make is a very good choice for storing common maintenance
         | commands for a project. We use it at work for this. It started
         | when we migrated to Docker more than a decade ago - before
         | docker-compose was a thing, building and running a set of
         | containers required quite a bit of shell scripting, and we
         | decided to use Make for that. Make is ubiquitous, cross-
         | platform, the targets are essentially snippets of shell with
         | some additional features/syntax added on top, there's a
         | dependency system (you can naturally express things like "if
         | you want to run X, you need to build Z and Y first, then X,
         | then you can run it"), it allows for easy parameterization
         | (`make <target> ARG=val`), plus it's actually Turing-complete
         | language with first-class lambdas and capacity for self-
         | modifying code[1]. And when some rule becomes too complex, it's
         | trivial to dump it into `scripts/something.sh` and have Make
         | call it. Rewriting the script in another language also works,
         | and Make still provides dependencies between targets.
         | 
         | TL;DR: Make is a very nice tool for gathering the "auxiliary"
         | scripts needed for a project in a language-agnostic manner.
         | It's better than setup.py and package.json precisely because it
         | provides a single interface for projects of both kinds.
         | 
         | [1] Which is worth knowing so you can avoid both features like
         | the plague.
        
         | WorldMaker wrote:
         | Deno has a similar tool to npm scripts called "tasks" in
         | deno.json. It even has a nice mini-advantage in that it
         | encourages including a one-line description which shows up in
         | the `deno tasks` list of all configured tasks and various IDE
         | integrations.
         | 
         | Most Deno tasks though, more so than a lot of npm scripts in my
         | experience, tend to just be `deno run ...` commands (the
         | shebang line in the article) to a script in a directory like
         | `_scripts/` rather than written as CLI commands.
        
       | epaga wrote:
       | It's almost depressing to me how much this post feels like a
       | breath of fresh air if for nothing else than because it's clearly
       | hand-written, not ghost-written by LLM.
       | 
       | No repetitive short sentences, no "Not X, just Y." patterns, and
       | lots of opinionated statements, written confidently in the first
       | person.
       | 
       | Please more of this.
        
         | hsbauauvhabzb wrote:
         | It's also relatively short and concise :)
        
           | nxobject wrote:
           | I used to think that "omit needless words" was a bit too
           | strict to be meaningful... and then I read AI slop.
        
         | raincole wrote:
         | Completely off-topic, but I recently had my "AI-depression
         | moment" when I found out top domain writer.com is owned by an
         | AI company now.
        
         | embedding-shape wrote:
         | > Please more of this.
         | 
         | Same, I'm caring less about "Yeah, I've learned something new"
         | and more about "Yeah, this sounds like I'm reading the thoughts
         | of a human, how refreshing" which is a sad state of affairs.
         | 
         | I've adopted my own writing style because of this too, used to
         | be very careful about spelling and grammar, very nitpicky, but
         | have now stopped doing that, because people started calling my
         | perfectly spelled responses LLM-generated...
        
           | BrandoElFollito wrote:
           | I have this when I use an em-dash (--), which I do
           | automatically.
           | 
           | This is annoying to say the least, just because there is no
           | "made with love by ChatGPT" stamp on LLM-produced stuff
           | (which is far from being bad BTW)
        
             | lbeckman314 wrote:
             | If God didn't want me to use the em dash, why did he
             | enshrine it in
             | 
             | nature? In the horizon line--the lightning-harrowed bough--
             | the canyon's
             | 
             | pink striation--the pupil of the goat                   --
             | @ctrlcreep
             | 
             | https://twitter.com/ctrlcreep/status/1808321708627317061
             | 
             | https://nitter.net/ctrlcreep/status/1808321708627317061
        
         | nxobject wrote:
         | He showed that it wasn't only easy to not sound like AI--but
         | that it was imperative for culture to flourish. Whether
         | composing long hand or typing with a mechanical keyboard in
         | Vim, he took back online discourse, one blogpost at a time. /s
        
       | worldsayshi wrote:
       | It sounds like at least some of the problems pointed at would be
       | mitigated by using fzf. At least it has greatly improved my
       | terminal ux.
        
       | flohofwoe wrote:
       | Heh, I went down that same rabbid hole recently, but in addition
       | to 'shell scripting tasks' also describe a whole C/C++ build in
       | Deno-flavoured TS instead of wrestling with cmake syntax:
       | https://github.com/floooh/fibs - and while at it, also allow to
       | integrate build jobs written in Typescript into the C/C++ build.
       | 
       | ...this is the same sort of 'works for me' philosophy as in
       | Matklads post though, it's so heavily opinionated and
       | personalized that I don't expect other people to pick it up, but
       | it makes my day-to-day work a lot easier (especially since I
       | switch multiple times between macOS, Linux and Windows on a
       | typical day).
       | 
       | I'm not sure if Bun can do it too, but the one great thing about
       | Deno is that it can directly import without requiring a 'manifest
       | file' (e.g. package.json or deno.json), e.g. you can do something
       | like this right in the code:                   import { Bla }
       | from 'jsr:@floooh/bla^1';
       | 
       | This is just perfect for this type of command line tools.
        
       | arnorhs wrote:
       | I mostly have my scripts in package.json "scripts" section - but
       | sometimes the scripts invoked will actually be .ts files,
       | sometimes just bash if that makes more sense.
       | 
       | Though, I generally run these scripts using bun (and the
       | corresponding `$` in bun) - basically the same thing, but I just
       | prefer bun over deno
        
       | drcongo wrote:
       | I use mise for this as it then also gives you a handy `mise
       | tasks` command so you can see what commands are available and
       | what they do. Mise has been a real gamechanger for my ailing
       | memory.
        
         | chanux wrote:
         | Any good write up about this you can recommend please? I have
         | been struggling to get on mise tasks train.
        
           | drcongo wrote:
           | Sorry, missed this post. I don't have any write ups to
           | recommend I'm afraid, for me it was a lot of trial and error,
           | but what really made the whole thing click for me was setting
           | up a linux box and not being able to remember all the mad
           | incantations and flags for everything I need to do on the
           | semi-regular. So I started just putting them in a user-global
           | mise.toml as tasks with nice descriptions to help me remember
           | what they do, and gradually, over time, I'd think "it would
           | be really helpful if this task also did x", so I'd add that.
           | They're basically superpowered aliases with a vastly better
           | user experience.
           | 
           | Then I realised how powerful it was that I could create tasks
           | with dependencies (ie: when a task requires the user to have
           | jq installed, you can add that to the mise.toml) which makes
           | the tasks beautifully shareable across a team. The only tool
           | they need to have installed is mise, and mise handles
           | everything else for them.
        
       | theanonymousone wrote:
       | I already do it, but not in TS. There is a scripting language
       | that is as available in most/all (non-Windows) systems as Bash:
       | Python.
       | 
       | Edit: zero-dependency Python.
        
         | verdverm wrote:
         | Works all and well until you need a dependency, then you need
         | to do all the same project setup as normal.
         | 
         | Stopped using python for scripting for this reason
        
           | theanonymousone wrote:
           | I don't think you need any dependencies to match Bash
           | scripting in capability.
        
             | hsbauauvhabzb wrote:
             | You can even wrap shell / system commands in python and
             | capture the output, so it's basically a superset!
        
               | kh_hk wrote:
               | You can also inline python inside shell scripts, does
               | that make them equal sets? :)                   life() {
               | python3 << EOF         print(42)         EOF         }
        
           | GeneralMaximus wrote:
           | If you use `uv`, you can declare your dependencies at the top
           | of a script:
           | https://docs.astral.sh/uv/guides/scripts/#declaring-
           | script-d...
           | 
           | I've started using Python for many more tasks after I
           | discovered this feature. I'm primarily a JS/TS developer, but
           | the ability to write a "standalone" script that can pull in
           | third-party dependencies without affecting your current
           | project is a massive productivity boost.
        
       | ctenb wrote:
       | "Just" is exactly made for this, and it is amazing. You write a
       | justfile that is somewhat similar to a makefile but without the
       | painpoints and it provides a CLI interface of commands you want
       | to run
        
         | nilamo wrote:
         | I was a Just enjoyer for quite a while, until I tried mise.
         | Mise does all the same things as just, but also has
         | source/output tracking to avoid rerunning build jobs (like
         | make), and also bundles runtimes like asdf. It's become my all-
         | in-one task runner of choice.
        
         | netghost wrote:
         | I think the make in the title is a bit misleading, the author
         | is actually just advocating for having a consistent file you
         | use for adhoc scripting and testing in your application.
         | 
         | The thrust of the article could be summarized as: if you type
         | more than one command into the shell, make a script.
        
         | data-ottawa wrote:
         | You can reference other justfiles as modules too, so in a mono
         | repo you can do `just foo-app test`.
         | 
         | If you combine that with relative working folders it's very
         | easy to manage large projects.
         | 
         | And you can get shell completion, which is extra nice.
        
       | mcapodici wrote:
       | If you want it to be an alternative to shell history then
       | ~/make.ts is better, since that'll be the same wherever you are.
        
         | matklad wrote:
         | Thanks, I haven't considered this! My history is usually
         | naturally project-scoped, but I bet I'll find ~/make.ts useful
         | now that I have it!
        
       | nextaccountic wrote:
       | Using `` to interpolate command arguments is very clever! What's
       | missing is a discussion on how you do quoting (for example, how
       | to ls a directory with spaces in name)
       | 
       | Anyway, what kills this for me is the need to add await before
       | every command.
        
       | syhol wrote:
       | My gut reaction is to rush to the comments to shill my favourite
       | task runner ( mise tasks[1], now with shell aliases[2]!) but
       | pushing past that, the core idea of writing scripts in a file
       | rather than a shell prompt is a great nugget of wisdom. But I
       | disagree with this bit:
       | 
       | "I want to be clear here, I am not advocating writing "proper"
       | scripts, just capturing your interactive, ad-hoc command to a
       | persistent file."
       | 
       | What's the difference? Why not version control it, share it with
       | colleagues. Imagine writing a unit test to test a new feature
       | then deleting it when done, what a waste. Ok it's not exactly the
       | same because you aren't using these scripts to catch regressions,
       | but all of that useful learning and context can be reused.
       | 
       | I don't think the language you use for scripting is too important
       | as long as the runtime is pinned and easily available on all
       | engineers machines, perhaps using a toolchain manager like...
       | mise[3].
       | 
       | [1] https://mise.jdx.dev/tasks/ [2] https://mise.jdx.dev/shell-
       | aliases.html [3] https://mise.jdx.dev/dev-tools/
        
         | stevage wrote:
         | I don't understand this bit either, unless "proper" means Bash.
         | Because no one should ever write Bash under any circumstances.
        
           | subsection1h wrote:
           | > _Because no one should ever write Bash under any
           | circumstances._
           | 
           | You've written 2,438 comments at HN and only two of those
           | messages include "Linux". Two! And the most recent of those
           | two comments includes this gem:
           | 
           | "fighting my way through a Linux CLI is exactly the kind of
           | thing I use Chatgpt for professionally."
           | 
           | Maybe you shouldn't be telling more capable technologists
           | what languages they should avoid.
        
         | jasonlotito wrote:
         | > What's the difference? Why not version control it,
         | 
         | Because I'm hardcoding directory paths.
         | 
         | Because I'm assuming things are set up a particular way: the
         | way they are on my machine.
         | 
         | Because this is hardcoded to a particular workflow that I'm
         | using here and now, and that's it.
         | 
         | Because I do not want to be responsible for it after no longer
         | needing it.
         | 
         | Because I don't want to justify it.
         | 
         | Because I'm hard-coding things that shouldn't be checked in.
         | 
         | Because I don't want to be responsible for establishing the way
         | we do things based on this script.
        
           | syhol wrote:
           | Do these scripts need to be productionised? I prefer working
           | in an environment where efficient sharing of knowledge and
           | solutions is encouraged, rather than framed as a burden of
           | responsibility.
           | 
           | Given the choice between starting with an almost-working
           | script or starting from scratch, I'll take the former, it
           | might save a few hours.
           | 
           | My colleagues and I don't do this 100% of the time, but I
           | never regret it and always appreciate it when others do.
        
             | WorldMaker wrote:
             | Yeah, some of it can be solved as a simple naming
             | convention thing. `_scripts/*.ts` for scripts that are
             | "reproduceable" and/or production-ready and
             | `_scripts/scratch/*.ts` or `_scripts/${username}/*.ts` for
             | scripts that are piecemeal or work-in-progress or user-
             | specific or otherwise "throwaway". Or a graduation process
             | such as where things in `_scripts/` are considered
             | "throwaway" until added to and documented in a larger
             | production task runner like adding them to the "tasks"
             | section of a deno.json file. (They graduate from being
             | shebang run to `deno task taskname` run. They get basic
             | documentation in the `deno task` list and various IDE
             | integrations of such.)
             | 
             | The major thing to be concerned about there is leaking
             | things like hard-coded secrets and that's where something
             | like .env files can come in handy and knowing your tools to
             | make use of them. Deno (as the running example) makes using
             | .env files easy enough by adding the `--env` flag to your
             | `deno run` shebang/task-line and then using `Deno.env` like
             | any other environment variable. (Then don't forget to
             | .gitignore your .env files.)
        
       | tcoff91 wrote:
       | How am I the first person to mention fzf?
       | 
       | Just integrate fzf into your shell and use ctrl-r to instantly
       | summon a fuzzy shell history search and re-execute any command
       | from your history!
       | 
       | I cannot imagine going back to using a terminal without this.
       | 
       | I still write plenty of scripts if I need to repeat multi command
       | processes but for one liners just use fzf to reexecute it.
       | 
       | Also in a shared project you can ignore script files with
       | .git/info/exclude instead of .gitignore so you don't have to
       | check in your personal exclusion patterns to the main branch.
       | 
       | Seriously people if you use a terminal you need the following
       | tools to dominate the shell:
       | 
       | ripgrep, zoxide, fzf, fd
        
         | spiffytech wrote:
         | I can't believe how long I was sleeping on fd and zoxide.
         | zoxide is now one of my top commands, and fd feels like when I
         | switched to ripgrep. So fast and easy there's no reason not to
         | run it.
        
           | tcoff91 wrote:
           | Zoxide is incredible! Going from cd to zoxide is like going
           | from walking to driving an F1 car around the directory tree.
           | 
           | I made a function called y that is like the z function but is
           | git worktree / jj workspace aware. So useful!
        
       | stevage wrote:
       | I use ZX for this - it's basically JS/TS with some extra stuff
       | that makes it good for shell scripts.
       | 
       | I don't understand why you wouldn't want your scripts in your Git
       | - but I guess OP's context is different from mine.
        
       | facundo_olano wrote:
       | A Makefile is good for this, and why not checking it into git?
        
       | vrnvu wrote:
       | Made me think. Every time I see a "Postman collection" or similar
       | artifacts, my heart skips a bit. Use curl. Run it interactively
       | in the terminal. When it works, move it into a shell script where
       | you can simply check the status code. Voila, magic! you've got
       | yourself a simple but valuable integration test.
       | 
       | Instead of juggling dashboards and collections of requests, or
       | relying on your shell history as Matklad mentions, you have it in
       | a file that you can commit and plug into CI. Win-win.
       | 
       | At some point, that testing shell script can be integrated into
       | your codebase using your working language and build tooling.
        
         | easton wrote:
         | I run into that too. Someone sends me a Postman, and I sit
         | there fiddling with the UI five or ten times instead of just
         | putting it into a loop in a real program. then realize how much
         | time i spent fiddling and pull it into a program, then spend
         | some copying the auth or whatever over, then realize i
         | should've been doing real work.
         | 
         | People like Postman because it's easy to share credentials and
         | config, and easy(ish) to give to less technical people, but the
         | cliff for pulling that stuff into code is often annoying.
         | 
         | "Postman but actually it's a jupyter-style notebook with your
         | credentials" would be cool, although I don't know exactly what
         | that would look like.
        
         | WorldMaker wrote:
         | I think the biggest hurdle with curl is its syntax. The
         | original HTTPie CLI [1] has a really great syntax that closer
         | resembles something like making a "Postman collection". About
         | the only thing I'm missing these days in httpie that my Postman
         | (and Insomnia) preferring colleagues have is a good plugin for
         | OAuth2/OIDC auth flows.
         | 
         | [1] https://httpie.io/cli
        
       | cassepipe wrote:
       | > There are many benefits relative to Up Up Up workflow
       | 
       | With your shell's vi mode, it's even better L -> k k k
       | 
       | Or search them with /
       | 
       | And if you are proficient with vim, you can edit your previous
       | one-line really fast
       | 
       | (Remap/Swap CapsLock with Escape system-wide. It's just a gui
       | setting on linux and MacOS and a registry key way on Windows)
        
       | blintz wrote:
       | I have tried many times to do this, but lack even the minor
       | discipline required. I inevitably make changes to the commands I
       | want to run at the command line, rather than in the script, and
       | then later forget to edit them in the script.
       | 
       | Instead, I now swear by atuin.sh, which just remembers every
       | command I've typed. It's sort of bad, since I never actually get
       | nice scripts, just really long commands, but it gets you 50% of
       | the way there with 0 effort. When leaving my last job, I even
       | donated my (very long) atuin history to my successor, which I
       | suspect was more useful than any document I wrote.
       | 
       | My only hot tip: atuin overrides the up-arrow by default, which
       | is really annoying, so do `atuin init zsh --disable-up-arrow` to
       | make it only run on Ctrl-R.
        
       | tpoacher wrote:
       | I don't know if the author is already aware of this or not, but
       | there's actually already a readline / bash two-keystroke
       | shortcut, ^X^E, for doing just that: drop into a terminal editor,
       | with the current state of your command becoming the first line in
       | the buffer, which then executes the command upon exit from the
       | editor. (obviously, while you're in the editor, you can save to a
       | file first instead of just exiting directly, if you think the
       | current command has re-use value)
        
         | tasuki wrote:
         | In zsh with vim mode, the shortcut to open current command in
         | editor is `v`.
        
       | hannasm wrote:
       | This article does a good job of calling attention to the pattern.
       | 
       | If you work in powershell you can start out in the terminal, then
       | when you've got whatever you need working you can grab the
       | history (get-history) and write it to a file, which I've always
       | referred to as a `sample`. Then when it becomes important enough
       | that other people ask me about it regularly I refactor the
       | `sample` into a true production grade `script`. It often doesn't
       | start out with a clear direction and creating a separate file is
       | just unnecessary ceremony when you can just tinker and export
       | later when the `up-enter' pattern actually appears.
        
       | pimlottc wrote:
       | I would perhaps call this "build.ts" instead. Calling it
       | "make.ts" implies Makefile-like behavior, e.g. multiple targets,
       | dependencies, conditional builds, etc.
        
       ___________________________________________________________________
       (page generated 2026-01-28 23:01 UTC)