@runfile/cli 1.8.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +73 -18
- package/bin/darwin-arm64/run +0 -0
- package/bin/darwin-x64/run +0 -0
- package/bin/linux-arm64/run +0 -0
- package/bin/linux-x64/run +0 -0
- package/bin/win32-arm64/run.exe +0 -0
- package/bin/win32-x64/run.exe +0 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -59,7 +59,8 @@ irm https://git.joaoverona.com/joaaoverona/runfile/releases/download/latest/inst
|
|
|
59
59
|
|
|
60
60
|
Either one installs a single binary: `~/.local/bin/run`, or `run.exe` under `%LOCALAPPDATA%\runfile\bin`, which
|
|
61
61
|
the PowerShell script adds to your PATH for you — open a new shell and `run` is there. `RUNFILE_VERSION` installs
|
|
62
|
-
a release other than the latest, and `RUNFILE_INSTALL_DIR` chooses where it goes.
|
|
62
|
+
a release other than the latest (`v1.8.4`, or `1.8.4`), and `RUNFILE_INSTALL_DIR` chooses where it goes. Both
|
|
63
|
+
are read by the shell running the script, so that is where they go: `curl -fsSL … | RUNFILE_VERSION=v1.8.4 sh`.
|
|
63
64
|
|
|
64
65
|
```bash
|
|
65
66
|
run :init # creates runfiles/ with an example
|
|
@@ -74,8 +75,9 @@ run :completions install bash # tab-completion (or zsh, fish, powershell)
|
|
|
74
75
|
```
|
|
75
76
|
|
|
76
77
|
Runner flags go **before** the target name; everything after it belongs to the target. `run build --dry-run`
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
hands `--dry-run` to `build` and runs it for real — as `FLAG.dry-run` if `build` reads that, or in `ARGS`, its
|
|
79
|
+
positional arguments, if it reads those — so the position is the whole meaning. A target that reads neither
|
|
80
|
+
refuses the flag rather than running without it, which is what catches a `--dry-run` on the wrong side.
|
|
79
81
|
|
|
80
82
|
In CI, the setup action installs `run` and puts it on PATH, so every step after it is a target name:
|
|
81
83
|
|
|
@@ -88,7 +90,9 @@ In CI, the setup action installs `run` and puts it on PATH, so every step after
|
|
|
88
90
|
```
|
|
89
91
|
|
|
90
92
|
`@v1` is the major alias, moved to each release as it ships — so a fix arrives without editing every
|
|
91
|
-
workflow, and a new major never arrives unannounced. Pin `@v1.
|
|
93
|
+
workflow, and a new major never arrives unannounced. Pin `@v1.8.4`, or a commit SHA, instead to hold one exact
|
|
94
|
+
version: the action installs the release its own ref belongs to, so the pin holds the binary as well as the
|
|
95
|
+
action. `version:` overrides that — a tag such as `v1.8.4`, or `latest` for whatever was published last.
|
|
92
96
|
|
|
93
97
|
## What a runfile looks like
|
|
94
98
|
|
|
@@ -341,8 +345,11 @@ confirm("Deploy to production?")
|
|
|
341
345
|
$ terraform apply -auto-approve
|
|
342
346
|
```
|
|
343
347
|
|
|
344
|
-
The temp file is deleted when the run ends, however it ends — including a failure half-way,
|
|
345
|
-
when a decoded credential must not be left behind
|
|
348
|
+
The temp file is deleted when the run ends, however it ends — including a failure half-way, or a SIGTERM/SIGHUP,
|
|
349
|
+
which is exactly when a decoded credential must not be left behind — and it is created readable only by you
|
|
350
|
+
(`0600`). The runner also keeps interpolated secrets out of **its own** output: a failed command and a
|
|
351
|
+
`.logging` line show `{{ ENV.TOKEN }}` where the value would be, not the value, so a secret does not land in
|
|
352
|
+
terminal scrollback or a CI log.
|
|
346
353
|
|
|
347
354
|
### A pre-commit hook
|
|
348
355
|
|
|
@@ -545,7 +552,7 @@ run deploy [--env=<value>] --token=<value> [--force]
|
|
|
545
552
|
Deploy to an environment.
|
|
546
553
|
|
|
547
554
|
Arguments
|
|
548
|
-
--env=<value> defaults to staging
|
|
555
|
+
--env=<value> defaults to staging; one of staging, production
|
|
549
556
|
--token=<value> required
|
|
550
557
|
--force off unless passed
|
|
551
558
|
|
|
@@ -554,9 +561,21 @@ Environment
|
|
|
554
561
|
```
|
|
555
562
|
|
|
556
563
|
Nothing is declared for that — a `?` chain is what makes a value optional, and the literal it ends in is the
|
|
557
|
-
default.
|
|
558
|
-
|
|
559
|
-
|
|
564
|
+
default. A `one_of` with its options written out, `one_of(ARG.env ? "staging", "staging", "production")`, is
|
|
565
|
+
what lists the values a name takes. `run --stdin-args deploy` asks for the same list, in the same order,
|
|
566
|
+
**before anything runs**, and shows the same options; the target's own `one_of` is still what refuses a wrong
|
|
567
|
+
answer. A name the target sets for itself with `.env.NAME` — above the read, or in a `_shared.run` — is not on
|
|
568
|
+
the list at all, since nothing the caller passes would reach it.
|
|
569
|
+
|
|
570
|
+
A target that reads `ARGS` is asked for its positionals too, when none were given: one line, split the way a
|
|
571
|
+
command line is, so `"two words"` stays one. Enter passes none, which is what a wrapper usually wants. A
|
|
572
|
+
`confirm()` the target reaches is asked the same way. The answers come from stdin whether or not it is a
|
|
573
|
+
terminal, so they can be piped in. In CI nothing is asked unless stdin is a terminal.
|
|
574
|
+
|
|
575
|
+
```bash
|
|
576
|
+
$ run --stdin-args release
|
|
577
|
+
[runfile] ARGS (one of major, minor, patch): minor
|
|
578
|
+
```
|
|
560
579
|
|
|
561
580
|
That refusal has one exception, and it is the useful one: **a target that reads `ARGS`**. A wrapper can read
|
|
562
581
|
any word, so it gets any word — a `--flag` it does not claim for itself joins the positionals where it was
|
|
@@ -594,6 +613,21 @@ $ cp {{ ARG.src }} {{ ARG.dest }}
|
|
|
594
613
|
left to quote: the substitution already did it. The same rule holds one layer up inside a `json` block, where
|
|
595
614
|
an interpolation becomes one JSON value.
|
|
596
615
|
|
|
616
|
+
Where the shell around an interpolation is quoted already — a message in `"…"`, a heredoc, a command string for
|
|
617
|
+
another shell — the value is written for those quotes too, so it stays text whatever it holds:
|
|
618
|
+
|
|
619
|
+
```sh
|
|
620
|
+
$ ssh deploy@host "cd {{ ARG.dir }} && ./restart"
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
The remote shell is handed `cd 'my dir' && ./restart`, quoted for it, and a `$( )` or a backtick in the value
|
|
624
|
+
is text to both shells. A value that would end the heredoc or the shell comment it sits in is refused rather
|
|
625
|
+
than written.
|
|
626
|
+
|
|
627
|
+
A list becomes one argument per item, so give it a word of its own rather than gluing text onto its end —
|
|
628
|
+
`{{ files }} suffix`, not `{{ files }}suffix`. An empty list is no words at all, so `rm -rf {{ dirs }}/cache`
|
|
629
|
+
with an empty `dirs` is `rm -rf /cache`; `run :lint` flags that glued shape.
|
|
630
|
+
|
|
597
631
|
### Asking whether a command worked
|
|
598
632
|
|
|
599
633
|
A `$` run may stand as a condition or as a `match` subject. The condition is true when the command exits 0;
|
|
@@ -704,7 +738,8 @@ forgives it. `confirm("…")` asks, and stops the whole run if the answer is no.
|
|
|
704
738
|
target that ran this one.
|
|
705
739
|
|
|
706
740
|
`confirm` is a function rather than a property, so the question can depend on what is about to happen — and is
|
|
707
|
-
skipped by `-y`, in CI, and under `--dry-run`, where there is nothing to approve.
|
|
741
|
+
skipped by `-y`, in CI, and under `--dry-run`, where there is nothing to approve. It is asked on a terminal, or
|
|
742
|
+
under `--stdin-args` on whatever stdin is: that flag is what says somebody answers there.
|
|
708
743
|
|
|
709
744
|
A line that is only a value — `exit`, `abc`, `35` — is a parse error, since it computes something and throws
|
|
710
745
|
it away. Most often it is a call with the parentheses left off, and the message says so.
|
|
@@ -950,6 +985,13 @@ runfiles/api/deploy.run → run api:deploy
|
|
|
950
985
|
Inside a subproject, `run build` means *that* subproject's `build` — so a file reads the same wherever you
|
|
951
986
|
invoke `run` from.
|
|
952
987
|
|
|
988
|
+
**The `runfiles/` the walk finds has to be yours.** A directory any account can write — `/tmp`, or the root of
|
|
989
|
+
a Windows drive — would otherwise hand its targets to every directory beneath it that has none of its own. So
|
|
990
|
+
it must belong to you, to the machine's administrators, or to whoever owns the directory you started in, which
|
|
991
|
+
keeps working inside a colleague's checkout as deliberate as it always was. A directory meant to be shared
|
|
992
|
+
between accounts is trusted by naming it, or the project holding it, in `RUNFILE_SAFE_DIRECTORIES` — paths
|
|
993
|
+
separated the way `PATH` separates them, or `*` for all.
|
|
994
|
+
|
|
953
995
|
`$HOME/.runfiles/` holds machine-wide targets, available in every project. If you would rather see the
|
|
954
996
|
directory than hide it, `$HOME/runfiles/` and `$HOME/Runfiles/` are read too — but only one of the three may
|
|
955
997
|
hold anything. `run :list` puts them first, under `global:`: they are the part of the listing you cannot see
|
|
@@ -980,7 +1022,8 @@ project's lint leaves the directory out otherwise.
|
|
|
980
1022
|
what is checked in and reviewable. This is also why nothing has to be cleaned up after a job.
|
|
981
1023
|
|
|
982
1024
|
Everything relative — `.env-file`, `.add-path`, `glob`, `read_file`, `{{ RUN.parent }}`, the working directory
|
|
983
|
-
— resolves against **the parent of `runfiles/`**. One anchor, one rule.
|
|
1025
|
+
— resolves against **the parent of `runfiles/`**. One anchor, one rule. An absolute path is taken as written,
|
|
1026
|
+
and an absolute `glob` pattern answers with absolute paths.
|
|
984
1027
|
|
|
985
1028
|
## Setup targets
|
|
986
1029
|
|
|
@@ -1011,15 +1054,15 @@ whose state is still worth keeping, so a `setup` run under it is still recorded.
|
|
|
1011
1054
|
| `run :init` | Create `runfiles/` with an example |
|
|
1012
1055
|
| `run :lint [path…]` | Format runfiles in place and report anything the runner would refuse (`--check` writes nothing, `--stdout`) |
|
|
1013
1056
|
| `run :env <sub>` | Manage `.env` files: `init`, `get`, `set`, `encrypt`, `decrypt`, `rotate`, `inject`, `secret-keys` |
|
|
1014
|
-
| `run :completions <command> <shell>` | `install`, `uninstall` or `output` a completion script for `bash`, `zsh`, `fish` or `powershell` |
|
|
1057
|
+
| `run :completions <command> <shell>` | `install`, `uninstall` or `output` a completion script for `bash`, `zsh`, `fish` or `powershell` — after upgrading from 1.8.2 or older, `install bash` again: the installed script is a copy |
|
|
1015
1058
|
| `run :generate <editor>` | Task files for `zed`, `jetbrains` or `vscode`, merged into what is there |
|
|
1016
1059
|
| `run :update [version]` | Update the binary to the newest release, or to the tag named; `--channel=github` takes it from the GitHub mirror, `--force` reinstalls |
|
|
1017
1060
|
|
|
1018
1061
|
| Flag | |
|
|
1019
1062
|
| --- | --- |
|
|
1020
1063
|
| `-y`, `--yes` | Skip confirmation prompts |
|
|
1021
|
-
| `--stdin-args` | Prompt for every input the target reads, before it runs |
|
|
1022
|
-
| `--dry-run` | Print what would run, without running it |
|
|
1064
|
+
| `--stdin-args` | Prompt for every input the target reads, positionals included, before it runs, and answer its `confirm()` on stdin too |
|
|
1065
|
+
| `--dry-run` | Print what would run, without running it — a preview reads no files outside the project, probes none, and never unlocks the credential store |
|
|
1023
1066
|
| `--dir <path>` | Start discovery somewhere else |
|
|
1024
1067
|
| `-h`, `--help` | Show the help for `run` or any of its commands |
|
|
1025
1068
|
| `-v`, `--version` | Print the version |
|
|
@@ -1048,7 +1091,8 @@ you wrote it: only the layout of a file is ever rewritten.
|
|
|
1048
1091
|
Formatting has no settings: one shape, everywhere. It reindents with tabs, spaces expressions, places blank
|
|
1049
1092
|
lines, lays out `json` blocks, and leaves the three things that are not the language's to touch — strings, the
|
|
1050
1093
|
text after `$ `, and `exec` bodies — exactly as written. A file that does not parse is left alone, and the
|
|
1051
|
-
formatter checks that its output still means the same thing before writing it.
|
|
1094
|
+
formatter checks that its output still means the same thing before writing it. The file is then replaced in one
|
|
1095
|
+
step, keeping its permissions, so an interrupted write never leaves half of one behind.
|
|
1052
1096
|
|
|
1053
1097
|
## Encrypted environment variables
|
|
1054
1098
|
|
|
@@ -1066,13 +1110,23 @@ run :env get .env.production KEY # decrypts on read
|
|
|
1066
1110
|
run :env rotate .env.production # re-encrypt under a new key
|
|
1067
1111
|
```
|
|
1068
1112
|
|
|
1113
|
+
`set` encrypts under the public key the file itself records — `:env init` writes it at the top — and never
|
|
1114
|
+
falls back to plaintext: for a file that records none, `--key <prefix>` names the key to use, and `--plain`
|
|
1115
|
+
stores the value as it is.
|
|
1116
|
+
|
|
1069
1117
|
Keys live in the OS credential store — Keychain, Credential Manager, or Secret Service with a keyutils
|
|
1070
1118
|
fallback. In CI, pass them as `RUNFILE_PRIVATE_KEYS` (newline-separated) and no credential store is involved.
|
|
1071
1119
|
|
|
1120
|
+
Only a value a `.env` file holds is decrypted — the one place an `encrypted:` value is the author's own. A
|
|
1121
|
+
ciphertext arriving any other way (exported in the caller's shell, or copied into `.env.X = {{ ARG.x }}` from
|
|
1122
|
+
data an outsider controls) is passed through unchanged, never decrypted with the run's keys, so it cannot be
|
|
1123
|
+
turned back into plaintext by a target that merely echoes a variable.
|
|
1124
|
+
|
|
1072
1125
|
Decryption happens in memory; secrets never reach disk. The credential store is only touched when something
|
|
1073
1126
|
actually decrypts, so a locked keyring never gets in the way of an unrelated target — and it is asked once in
|
|
1074
1127
|
a run, however many of the targets that run calls decrypt. In watch mode each re-run asks again, so unlocking
|
|
1075
|
-
the keyring or adding a missing key takes effect on the next save.
|
|
1128
|
+
the keyring or adding a missing key takes effect on the next save. `--dry-run` never touches it: a preview
|
|
1129
|
+
reads no `.env` files and asks for no key.
|
|
1076
1130
|
|
|
1077
1131
|
## Editor support
|
|
1078
1132
|
|
|
@@ -1126,7 +1180,8 @@ vim.filetype.add({ extension = { run = "runfile" } })
|
|
|
1126
1180
|
```
|
|
1127
1181
|
|
|
1128
1182
|
`run :generate zed` writes every target into `.zed/tasks.json`, and `run :generate jetbrains` into
|
|
1129
|
-
`.idea/runConfigurations/`; both leave entries you wrote yourself alone and replace only their own
|
|
1183
|
+
`.idea/runConfigurations/`; both leave entries you wrote yourself alone and replace only their own, and a file
|
|
1184
|
+
they cannot read is left as it is rather than written over.
|
|
1130
1185
|
|
|
1131
1186
|
## Why a language
|
|
1132
1187
|
|
package/bin/darwin-arm64/run
CHANGED
|
Binary file
|
package/bin/darwin-x64/run
CHANGED
|
Binary file
|
package/bin/linux-arm64/run
CHANGED
|
Binary file
|
package/bin/linux-x64/run
CHANGED
|
Binary file
|
package/bin/win32-arm64/run.exe
CHANGED
|
Binary file
|
package/bin/win32-x64/run.exe
CHANGED
|
Binary file
|
package/package.json
CHANGED