@runfile/cli 1.2.0 → 1.7.1
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 +299 -51
- 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
|
@@ -23,7 +23,8 @@ web/runfiles/build.run → run web:build
|
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
A target is a file, its name is its path, and its first comment block is its description. There is no central
|
|
26
|
-
file to merge, so two people adding a task never conflict.
|
|
26
|
+
file to merge, so two people adding a task never conflict. `run :list` shows the first line of each description
|
|
27
|
+
— one line per target, cut to the terminal's width — and `run <target> --help` shows the whole block.
|
|
27
28
|
|
|
28
29
|
```sh
|
|
29
30
|
# runfiles/dev.run
|
|
@@ -44,8 +45,23 @@ $ run :list
|
|
|
44
45
|
|
|
45
46
|
## Quick start
|
|
46
47
|
|
|
48
|
+
Linux and macOS:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
curl -fsSL https://git.joaoverona.com/joaaoverona/runfile/releases/download/latest/install.sh | sh
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Windows, in PowerShell:
|
|
55
|
+
|
|
56
|
+
```powershell
|
|
57
|
+
irm https://git.joaoverona.com/joaaoverona/runfile/releases/download/latest/install.ps1 | iex
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Either one installs a single binary: `~/.local/bin/run`, or `run.exe` under `%LOCALAPPDATA%\runfile\bin`, which
|
|
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.
|
|
63
|
+
|
|
47
64
|
```bash
|
|
48
|
-
curl -fsSL https://github.com/JoaaoVerona/runfile/releases/latest/download/install.sh | sh
|
|
49
65
|
run :init # creates runfiles/ with an example
|
|
50
66
|
run :list # every target, with descriptions
|
|
51
67
|
run <target> --help # what one target does, and what it reads
|
|
@@ -136,27 +152,27 @@ $ docker exec -i db psql -U app < seed.sql
|
|
|
136
152
|
|
|
137
153
|
### Doing several things at once
|
|
138
154
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
nothing else, and at the top of the file it would cover every command in the target.
|
|
155
|
+
`parallel for` runs every iteration at once and waits for the last. What is inside one iteration still runs in
|
|
156
|
+
order, so each service is built before it is pushed, and every line carries the name of the iteration it came
|
|
157
|
+
from.
|
|
143
158
|
|
|
144
159
|
```sh
|
|
145
|
-
# runfiles/
|
|
146
|
-
#
|
|
160
|
+
# runfiles/release.run
|
|
161
|
+
# Build and push every service image, then say so
|
|
147
162
|
|
|
148
|
-
for
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
$ docker compose -f {{ compose }} pull
|
|
163
|
+
parallel for svc in ["api", "web", "worker"]
|
|
164
|
+
$ docker build -t {{ svc }} services/{{ svc }}
|
|
165
|
+
$ docker push {{ svc }}
|
|
153
166
|
end
|
|
167
|
+
|
|
168
|
+
print("all three pushed")
|
|
154
169
|
```
|
|
155
170
|
|
|
156
171
|
```
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
[docker]
|
|
172
|
+
web | Successfully built 9a0c41e2b7d3
|
|
173
|
+
api | Successfully built 1f2e86c0a4b5
|
|
174
|
+
worker | The push refers to repository [docker.io/library/worker]
|
|
175
|
+
all three pushed
|
|
160
176
|
```
|
|
161
177
|
|
|
162
178
|
### JSON without the backslashes
|
|
@@ -190,7 +206,7 @@ end
|
|
|
190
206
|
run _aws -- s3api put-bucket-policy --bucket {{ bucket }} --policy {{ policy }}
|
|
191
207
|
```
|
|
192
208
|
|
|
193
|
-
`run :
|
|
209
|
+
`run :lint` lays the block out, and a missing brace is underlined in your editor as you type — not reported
|
|
194
210
|
by the far end an hour later.
|
|
195
211
|
|
|
196
212
|
### Reading JSON without jq
|
|
@@ -365,22 +381,28 @@ Line-oriented, with one rule: **the language is the default, the shell is marked
|
|
|
365
381
|
|
|
366
382
|
| Line | Meaning |
|
|
367
383
|
| --- | --- |
|
|
368
|
-
| `# text` | A comment. The leading block is the target's description. |
|
|
384
|
+
| `# text` | A comment, to the end of the line. It may follow code. The leading block is the target's description. |
|
|
369
385
|
| `.name = value` | A property. |
|
|
370
386
|
| `$ echo hi` | Hand this line to a shell. |
|
|
371
387
|
| `exec python3` … `end` | Run a command with the block as its stdin. |
|
|
388
|
+
| `detach $ npm run dev` | Start this one command and do not wait for it. Also `detach exec …`. |
|
|
372
389
|
| `json` … `end` | A block of JSON, as one value. |
|
|
373
|
-
| `let x = 1` | Bind a value. `x = 2` rebinds. `let a, b = pair` takes a list apart. |
|
|
390
|
+
| `let x = 1` | Bind a value. `x = 2` rebinds. `let a, b = pair` takes a list apart. A name is bound before it is read. |
|
|
374
391
|
| `if` / `else if` / `else` / `end` | Branch. However many `else if`s, one `end`. |
|
|
375
392
|
| `for x in list` / `end` | Loop over a list. `for k, v in pairs` unpacks each item. |
|
|
393
|
+
| `parallel do` / `parallel for x in list` … `end` | Run each statement inside, or each iteration, at once. |
|
|
376
394
|
| `while c` / `until c` / `loop` / `end` | Loop on a condition, on its negation, or forever. |
|
|
377
395
|
| `break` / `continue` | Leave the innermost loop, or start its next pass. |
|
|
378
396
|
| `match` / `case` / `default` / `end` | Dispatch on a value. A label is a quoted string: `case "linux"`. |
|
|
379
397
|
| `retry n [every s]` … `end` | Run the block again while it fails, up to `n` times. |
|
|
380
398
|
| `do` … `end` | A block with no condition, so a property can cover a few commands. |
|
|
381
|
-
| `run other-target` | Run another target, in this process. |
|
|
399
|
+
| `run other-target` | Run another target, in this process, starting from this one's environment. |
|
|
382
400
|
| `print(…)` | Anything else is an expression, evaluated for its effect. |
|
|
383
401
|
|
|
402
|
+
A `#` opens a comment where it begins a word — the shell's own rule, so it reads the same on both sides of the
|
|
403
|
+
marker, and `a#b` is one word in either half. The text after `$ ` is the shell's, `#` and all, and so is an
|
|
404
|
+
`exec` block's body; everything else this language reads takes a comment at the end of the line.
|
|
405
|
+
|
|
384
406
|
### Values
|
|
385
407
|
|
|
386
408
|
Strings, numbers, booleans and lists — **strict, with no coercion.** `"a" + 1` is an error telling you to use
|
|
@@ -390,7 +412,8 @@ Lists nest as deep as you like, which beats packing several fields into one stri
|
|
|
390
412
|
|
|
391
413
|
```sh
|
|
392
414
|
for volume, owner in [
|
|
393
|
-
["prometheus-data", "65534:65534"],
|
|
415
|
+
["prometheus-data", "65534:65534"], # nobody
|
|
416
|
+
# the rest run as themselves
|
|
394
417
|
["kvrocks-data", "999:999"],
|
|
395
418
|
]
|
|
396
419
|
$ docker run --rm -v {{ volume }}:/v alpine chown -R {{ owner }} /v
|
|
@@ -448,6 +471,47 @@ end
|
|
|
448
471
|
included. Under `--dry-run` a conditional loop walks its body once: a preview performs none of the effects the
|
|
449
472
|
condition is waiting on, so how often is not a thing it can honestly answer.
|
|
450
473
|
|
|
474
|
+
### Parallel blocks
|
|
475
|
+
|
|
476
|
+
`parallel do` runs every statement directly inside it at once — a `$` line, a `run`, a call, a whole `if` or
|
|
477
|
+
`for` — and waits for the last. `parallel for` does the same with every iteration. Inside a branch everything
|
|
478
|
+
runs in order, as it does anywhere else: only what is marked `parallel` fans out, so nesting one fans out again.
|
|
479
|
+
|
|
480
|
+
```sh
|
|
481
|
+
# runfiles/check.run
|
|
482
|
+
# Every check at once: tests, lint, and each subproject's own
|
|
483
|
+
|
|
484
|
+
parallel do
|
|
485
|
+
$ cargo test
|
|
486
|
+
$ cargo clippy -- -D warnings
|
|
487
|
+
|
|
488
|
+
parallel for ns in RUN.namespaces
|
|
489
|
+
run {{ ns }}:check
|
|
490
|
+
end
|
|
491
|
+
end
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
- Each branch works on **its own copy of the variables**. Nothing it binds reaches the other branches or the code
|
|
495
|
+
after the block, so assigning a name bound outside the branch is a parse error — that value would be lost. A
|
|
496
|
+
`let` directly inside `parallel do` binds nothing anyone can read, and is one too.
|
|
497
|
+
- Every branch **runs to completion before a failure is reported**, so a set of services is never left half
|
|
498
|
+
started. `.ignore-errors` on the block forgives each branch.
|
|
499
|
+
- Every line a branch prints carries its name, **in a colour of its own**: the target it runs, the iteration's
|
|
500
|
+
value, or as many words of its command as tell it from its siblings — `cargo test` and `cargo clippy`.
|
|
501
|
+
Siblings take consecutive colours, so the output you are reading side by side is the output guaranteed to
|
|
502
|
+
look different, and the names are padded to the widest of them so the `|` stands in one column:
|
|
503
|
+
|
|
504
|
+
```
|
|
505
|
+
web-admin | ready on :3001
|
|
506
|
+
web-app | ready on :3000
|
|
507
|
+
web-blog | ready on :3002
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
`NO_COLOR` turns the colour off, `FORCE_COLOR` turns it on through a pipe. A branch reads nothing from the
|
|
511
|
+
terminal, and `confirm()` refuses to ask inside one.
|
|
512
|
+
- Branches run at once, so neither `break` nor `continue` can reach a loop outside the block, and `break` cannot
|
|
513
|
+
stop a `parallel for`, whose iterations are already running; `continue` ends one of them.
|
|
514
|
+
|
|
451
515
|
### Where values come from
|
|
452
516
|
|
|
453
517
|
| Source | From |
|
|
@@ -490,7 +554,9 @@ Environment
|
|
|
490
554
|
```
|
|
491
555
|
|
|
492
556
|
Nothing is declared for that — a `?` chain is what makes a value optional, and the literal it ends in is the
|
|
493
|
-
default. `run --stdin-args deploy` asks for the same list, in the same order, **before anything runs**.
|
|
557
|
+
default. `run --stdin-args deploy` asks for the same list, in the same order, **before anything runs**. A name
|
|
558
|
+
the target sets for itself with `.env.NAME` — above the read, or in a `_shared.run` — is not on the list at all,
|
|
559
|
+
since nothing the caller passes would reach it.
|
|
494
560
|
|
|
495
561
|
That refusal has one exception, and it is the useful one: **a target that reads `ARGS`**. A wrapper can read
|
|
496
562
|
any word, so it gets any word — a `--flag` it does not claim for itself joins the positionals where it was
|
|
@@ -622,28 +688,148 @@ skipped by `-y`, in CI, and under `--dry-run`, where there is nothing to approve
|
|
|
622
688
|
A line that is only a value — `exit`, `abc`, `35` — is a parse error, since it computes something and throws
|
|
623
689
|
it away. Most often it is a call with the parentheses left off, and the message says so.
|
|
624
690
|
|
|
691
|
+
### Names are checked before anything runs
|
|
692
|
+
|
|
693
|
+
A call to a function the language does not have, a read of a name nothing has bound, or a `RUN` key that does not
|
|
694
|
+
exist stops the run **before its first line** — wherever it sits, a branch that would not have been taken included —
|
|
695
|
+
and every one in the file is reported at once, with what you probably meant:
|
|
696
|
+
|
|
697
|
+
```bash
|
|
698
|
+
$ run backup
|
|
699
|
+
[runfile] error: /home/you/shop/runfiles/backup.run: line 12: unknown function `exists`; did you mean `directory_exists` or `file_exists`?
|
|
700
|
+
[runfile] error: /home/you/shop/runfiles/backup.run: line 15: `regoin` is not defined; did you mean `region`?
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
A name counts once any path to the line can have bound it: a `let` inside an `if` still binds for what follows
|
|
704
|
+
its `end`, and a pass of a loop sees what an earlier pass bound. What does not reach past its block is a loop's
|
|
705
|
+
own name and anything a `parallel` branch binds; a `_shared.run` binds with its top-level `let`s. A read behind
|
|
706
|
+
a `?` is checked too, since a name nothing binds takes the fallback every time. And `x = 2` needs a `let x`
|
|
707
|
+
above it — otherwise a typo in the name being updated would quietly start a new one.
|
|
708
|
+
|
|
709
|
+
Your editor underlines the same names as you type them — it is the runner's own check, served by `run :lsp` —
|
|
710
|
+
and `run :lint` reports them for every runfile in the project at once.
|
|
711
|
+
|
|
712
|
+
### So is what fails every time
|
|
713
|
+
|
|
714
|
+
Some lines fail whenever they run, whatever the run is given, and some never do what they say. Those stop the run
|
|
715
|
+
before its first line too, with what to write instead:
|
|
716
|
+
|
|
717
|
+
```bash
|
|
718
|
+
$ run release
|
|
719
|
+
[runfile] error: /home/you/app/runfiles/release.run: line 4: `+` needs numbers, and `ARG.build` is a string [wrong-type]; `number(ARG.build)` reads it as one
|
|
720
|
+
[runfile] error: /home/you/app/runfiles/release.run: line 9: `RUN.os == "darwin"` is always false: `RUN.os` is only ever `linux`, `mac` or `windows` [never-equal]; write `"mac"`
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
Nothing is converted, and an argument or an environment variable is always a string — so `ARG.build + 1` fails every
|
|
724
|
+
time, and so does `if ENV.CI`, since an `if` needs `true` or `false`. A call given one argument too many, a regex that
|
|
725
|
+
does not compile, `xs[-1]`, `case "ok"` under `match $ make`, `if code_of($ make)`: each is reported wherever it sits.
|
|
726
|
+
What a value can be is worked out only as far as the file is sure of it — `ENV.PORT ? 3000` is a string or a number, so
|
|
727
|
+
nothing is said about `+` on it. [LANGUAGE-CHECK-RULES.md](LANGUAGE-CHECK-RULES.md) lists every rule, what it leaves
|
|
728
|
+
alone, and why.
|
|
729
|
+
|
|
730
|
+
### The shell is checked too
|
|
731
|
+
|
|
732
|
+
The shell in a runfile is read before anything runs as well — every `$` line, every `exec` block for `sh` or
|
|
733
|
+
`bash`, every capture — the way bash will read it, with each `{{ … }}` as the one quoted word it becomes. What
|
|
734
|
+
bash would refuse, and what is wrong every time it runs, stops the run with what to write instead:
|
|
735
|
+
|
|
736
|
+
```bash
|
|
737
|
+
$ run emulate
|
|
738
|
+
[runfile] error: /home/you/app/runfiles/emulate.run: line 4: `$HOME` in `"$HOME/Android/Sdk"` is kept as written, since a string is not shell -- and on line 9 this value is used as a path, which then starts with `$HOME` itself [unexpanded-string]; write `{{ ENV.HOME }}` in its place
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
A quote left open, a `fi` with no `if`, an interpolation wrapped in quotes of its own, a `*` the shell never
|
|
742
|
+
expands, `cd` as the last command of its shell, `$1` in a shell that is given no arguments, `[ -f .env; then`,
|
|
743
|
+
`sort names.txt > names.txt`, a `find -exec` with nothing to end it: each rule reports only what is wrong every
|
|
744
|
+
time, and never a line that could be right — no style, no guesses. Your editor
|
|
745
|
+
underlines the same findings and `run :lint` reports them; [SHELL-CHECK-RULES.md](SHELL-CHECK-RULES.md) lists
|
|
746
|
+
every rule, what it leaves alone, and why.
|
|
747
|
+
|
|
625
748
|
## Properties
|
|
626
749
|
|
|
627
750
|
Set at the top of the file, or inside a block where marked.
|
|
628
751
|
|
|
629
|
-
| Property | Effect | In a block? |
|
|
630
|
-
| --- | --- | :-: |
|
|
631
|
-
| `.shell` | Which shell `$` lines use. |
|
|
632
|
-
| `.env.NAME` | Set an environment variable. | ✅ |
|
|
633
|
-
| `.workdir` | Where commands run. | ✅ |
|
|
634
|
-
| `.
|
|
635
|
-
| `.
|
|
636
|
-
| `.
|
|
637
|
-
| `.
|
|
638
|
-
| `.
|
|
639
|
-
| `.
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
752
|
+
| Property | Effect | In a block? | Flag? |
|
|
753
|
+
| --- | --- | :-: | :-: |
|
|
754
|
+
| `.shell` | Which POSIX shell `$` lines use. | | |
|
|
755
|
+
| `.env.NAME` | Set an environment variable. | ✅ | |
|
|
756
|
+
| `.workdir` | Where commands run. | ✅ | |
|
|
757
|
+
| `.ignore-errors` | Keep going when a command fails. | ✅ | ✅ |
|
|
758
|
+
| `.logging` | Announce each command on stderr before it runs. Off unless set. | | ✅ |
|
|
759
|
+
| `.env-file` | Load a `.env` file (encrypted values are decrypted in memory). Appends. | ✅ | |
|
|
760
|
+
| `.add-path` | Prepend a directory to `PATH`. Appends. | ✅ | |
|
|
761
|
+
| `.watch` | Re-run when matching files change. A `!` prefix excludes. | | |
|
|
762
|
+
| `.only-in-directories` | Machine-wide targets only: offer this one inside these directories. Appends. | | |
|
|
763
|
+
|
|
764
|
+
A **flag** is written bare for `= true`, or given a bool. A constant that can never be one is refused where it
|
|
765
|
+
is written — `.ignore-errors = 23` and `.ignore-errors = "true"` are both errors, and the second says to drop
|
|
766
|
+
the quotes. A value the run works out is left to the run: `.ignore-errors = ENV.CI` and
|
|
767
|
+
`.ignore-errors = ARG.p ? ENV.CI ? "false"` are how a flag is decided from outside the file, and there `true`,
|
|
768
|
+
`false`, `1` and `0` are all understood. Anything else stops the run rather than reading as `false`, because
|
|
769
|
+
a flag that quietly did not take effect is found out much later.
|
|
770
|
+
|
|
771
|
+
A property is applied **where it is written**: it can read a binding above it, and it takes effect from there
|
|
772
|
+
down. The environment works the same way — a value reads it as it stands at its own line, so a property can be
|
|
773
|
+
built from what a `.env-file` above it loaded, or from another `.env` above it:
|
|
774
|
+
|
|
775
|
+
```sh
|
|
776
|
+
# runfiles/build.run
|
|
777
|
+
# Build into a directory the command line picks
|
|
778
|
+
|
|
779
|
+
let out = concat("target-", ARG.profile ? "debug")
|
|
780
|
+
|
|
781
|
+
.workdir = out
|
|
782
|
+
|
|
783
|
+
$ ls
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
```sh
|
|
787
|
+
# runfiles/migrate.run
|
|
788
|
+
# Migrate the database the local .env points at
|
|
789
|
+
|
|
790
|
+
.env-file = ".env"
|
|
791
|
+
.env.DATABASE_URL = concat("postgres://", ENV.DB_HOST, "/app")
|
|
792
|
+
|
|
793
|
+
$ sqlx migrate run
|
|
794
|
+
```
|
|
795
|
+
|
|
796
|
+
Four of them describe the whole file rather than the commands under them — `.shell`, `.logging`, `.watch` and
|
|
797
|
+
`.only-in-directories` — and those have to be written above its first statement.
|
|
798
|
+
|
|
799
|
+
`.shell` names one of the eight shells `$` knows how to drive: `sh`, `bash`, `dash`, `ash`, `zsh`, `ksh`,
|
|
800
|
+
`busybox` or `brush`. Any other interpreter is named on the line instead, with `exec` — which is the same
|
|
801
|
+
mechanism, and says so where it can be read:
|
|
802
|
+
|
|
803
|
+
```sh
|
|
804
|
+
# runfiles/list.run
|
|
805
|
+
# List the current directory, whatever the platform
|
|
806
|
+
|
|
807
|
+
if RUN.os == "windows"
|
|
808
|
+
exec pwsh
|
|
809
|
+
Get-ChildItem
|
|
810
|
+
end
|
|
811
|
+
else
|
|
812
|
+
$ ls -la
|
|
813
|
+
end
|
|
814
|
+
```
|
|
815
|
+
|
|
816
|
+
**The target's own `.env.NAME` wins over the caller's environment; a `.env-file` does not.** An assignment
|
|
817
|
+
written in the file is the one way a target can force a value, so `PORT=4000 run serve` still serves on 3000
|
|
818
|
+
under `.env.PORT = "3000"` — and `ENV.PORT` and a command's `$PORT` both say 3000. A file is a default, the
|
|
819
|
+
dotenv convention: what the caller exported beats it. A default the caller *may* override is spelled out, and
|
|
820
|
+
`--help` shows it as one:
|
|
821
|
+
|
|
822
|
+
```sh
|
|
823
|
+
# runfiles/serve.run
|
|
824
|
+
# Serve locally
|
|
825
|
+
|
|
826
|
+
.env.PORT = ENV.PORT ? "3000"
|
|
827
|
+
|
|
828
|
+
$ node server.js
|
|
829
|
+
```
|
|
643
830
|
|
|
644
831
|
`.env-file` and `.add-path` **append**, so a block adds to what it inherited rather than replacing it, and a
|
|
645
|
-
block's file can be named by something
|
|
646
|
-
resolves before any statement runs:
|
|
832
|
+
block's file can be named by something that block worked out:
|
|
647
833
|
|
|
648
834
|
```sh
|
|
649
835
|
# runfiles/deploy.run
|
|
@@ -670,6 +856,33 @@ runfiles/_aws.run → run _aws (a helper other targets call)
|
|
|
670
856
|
runfiles/deploy.run → run deploy
|
|
671
857
|
```
|
|
672
858
|
|
|
859
|
+
**A target that `run`s another hands it its environment.** Whatever a command on that line would be given —
|
|
860
|
+
the caller's `.env`, `.env-file` and `.add-path`, a block's as well as the header's, and what the caller was run
|
|
861
|
+
with itself — is where the target it runs starts. That target layers its own settings over it the way it would
|
|
862
|
+
over your shell: its own `.env` wins, and its own `.add-path` goes in front. So a helper needs no environment of
|
|
863
|
+
its own:
|
|
864
|
+
|
|
865
|
+
```sh
|
|
866
|
+
# runfiles/_aws.run
|
|
867
|
+
# The AWS CLI, with whatever credentials the calling target loaded
|
|
868
|
+
|
|
869
|
+
$ docker run --rm -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY amazon/aws-cli {{ ARGS }}
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
```sh
|
|
873
|
+
# runfiles/backups.run
|
|
874
|
+
# List the production backups
|
|
875
|
+
|
|
876
|
+
.env-file = ".env.production"
|
|
877
|
+
|
|
878
|
+
run _aws s3 ls s3://example-backups
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
A value that only a `.env-file` supplied is still a default when it arrives, so the target's own `.env-file`
|
|
882
|
+
replaces it: a `ci` target that loads `.env` and runs `test` does not push the development database over
|
|
883
|
+
`test`'s own `.env.test`. `$ run test` is different. A new process can only be handed variables, so there the
|
|
884
|
+
caller's `.env-file` values arrive as if you had exported them, and they beat `test`'s own file.
|
|
885
|
+
|
|
673
886
|
`do … end` is a block with no condition — somewhere for a property to go when it should cover a few commands
|
|
674
887
|
and not the whole target:
|
|
675
888
|
|
|
@@ -738,6 +951,10 @@ excluded it. Relative entries are relative to your home. It is the one property
|
|
|
738
951
|
set: their targets are visible to anyone reading the repository, so hiding some by working directory would
|
|
739
952
|
bring back the very invisibility this exists to fix.
|
|
740
953
|
|
|
954
|
+
A scope decides where a target is offered, not what gets checked. `run :lint` inside the machine-wide directory
|
|
955
|
+
checks every file in it, scoped or not — and so does `run :lint --include-global` from anywhere else, since a
|
|
956
|
+
project's lint leaves the directory out otherwise.
|
|
957
|
+
|
|
741
958
|
**Not in CI.** A runner's home directory is nobody's, so `run` reads none of the three there: what runs is
|
|
742
959
|
what is checked in and reviewable. This is also why nothing has to be cleaned up after a job.
|
|
743
960
|
|
|
@@ -770,11 +987,11 @@ whose state is still worth keeping, so a `setup` run under it is still recorded.
|
|
|
770
987
|
| `run <target> [args…]` | Run a target |
|
|
771
988
|
| `run :list` | List every target (`--names`, `--json` for tooling) |
|
|
772
989
|
| `run :init` | Create `runfiles/` with an example |
|
|
773
|
-
| `run :
|
|
990
|
+
| `run :lint [path…]` | Format runfiles in place and report anything the runner would refuse (`--check` writes nothing, `--stdout`) |
|
|
774
991
|
| `run :env <sub>` | Manage `.env` files: `init`, `get`, `set`, `encrypt`, `decrypt`, `rotate`, `inject`, `secret-keys` |
|
|
775
992
|
| `run :completions <command> <shell>` | `install`, `uninstall` or `output` a completion script for `bash`, `zsh`, `fish` or `powershell` |
|
|
776
993
|
| `run :generate <editor>` | Task files for `zed`, `jetbrains` or `vscode`, merged into what is there |
|
|
777
|
-
| `run :update` | Update the binary |
|
|
994
|
+
| `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 |
|
|
778
995
|
|
|
779
996
|
| Flag | |
|
|
780
997
|
| --- | --- |
|
|
@@ -787,11 +1004,29 @@ whose state is still worth keeping, so a `setup` run under it is still recorded.
|
|
|
787
1004
|
|
|
788
1005
|
Flags belong **before** the target name; everything after it is passed to the target.
|
|
789
1006
|
|
|
790
|
-
`run :
|
|
1007
|
+
`run :lint` puts every runfile into the one shape there is, then reports everything the runner would refuse — a
|
|
1008
|
+
syntax error, an unknown or misplaced property, a call to a function that does not exist, a name nothing binds,
|
|
1009
|
+
a `run` of a target that is not there, a line that [fails every time it runs](LANGUAGE-CHECK-RULES.md), shell the
|
|
1010
|
+
[shell checker](SHELL-CHECK-RULES.md) finds wrong — as
|
|
1011
|
+
`file:line:column`, which is the list your editor underlines. **It
|
|
1012
|
+
exits 0 only when every file is formatted and none of that is wrong**, so a file that passes runs without a
|
|
1013
|
+
parse-time error and opens with nothing underlined. `--check` writes nothing and fails on a file that needs
|
|
1014
|
+
formatting too, which is what a CI step wants.
|
|
1015
|
+
|
|
1016
|
+
```bash
|
|
1017
|
+
$ run :lint --check
|
|
1018
|
+
runfiles/deploy.run: needs formatting
|
|
1019
|
+
runfiles/deploy.run:12:9: error: unknown function `exists`; did you mean `directory_exists` or `file_exists`?
|
|
1020
|
+
14 files checked: 1 needs formatting, 1 error in 1 file
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
A shell finding says what to write instead on the line under it. `run :lint` reports it and leaves the shell as
|
|
1024
|
+
you wrote it: only the layout of a file is ever rewritten.
|
|
1025
|
+
|
|
1026
|
+
Formatting has no settings: one shape, everywhere. It reindents with tabs, spaces expressions, places blank
|
|
791
1027
|
lines, lays out `json` blocks, and leaves the three things that are not the language's to touch — strings, the
|
|
792
|
-
text after `$ `, and `exec` bodies — exactly as written.
|
|
793
|
-
its
|
|
794
|
-
which is what a CI step wants.
|
|
1028
|
+
text after `$ `, and `exec` bodies — exactly as written. A file that does not parse is left alone, and the
|
|
1029
|
+
formatter checks that its output still means the same thing before writing it.
|
|
795
1030
|
|
|
796
1031
|
## Encrypted environment variables
|
|
797
1032
|
|
|
@@ -813,7 +1048,9 @@ Keys live in the OS credential store — Keychain, Credential Manager, or Secret
|
|
|
813
1048
|
fallback. In CI, pass them as `RUNFILE_PRIVATE_KEYS` (newline-separated) and no credential store is involved.
|
|
814
1049
|
|
|
815
1050
|
Decryption happens in memory; secrets never reach disk. The credential store is only touched when something
|
|
816
|
-
actually decrypts, so a locked keyring never gets in the way of an unrelated target
|
|
1051
|
+
actually decrypts, so a locked keyring never gets in the way of an unrelated target — and it is asked once in
|
|
1052
|
+
a run, however many of the targets that run calls decrypt. In watch mode each re-run asks again, so unlocking
|
|
1053
|
+
the keyring or adding a missing key takes effect on the next save.
|
|
817
1054
|
|
|
818
1055
|
## Editor support
|
|
819
1056
|
|
|
@@ -821,19 +1058,30 @@ actually decrypts, so a locked keyring never gets in the way of an unrelated tar
|
|
|
821
1058
|
go-to-definition and documentation on hover — from the same parser `run` itself uses, so your editor never
|
|
822
1059
|
disagrees with what will actually happen. There is nothing extra to install.
|
|
823
1060
|
|
|
1061
|
+
Diagnostics include every call to a function that does not exist and every name nothing has bound, underlined
|
|
1062
|
+
where it is written, with the name you probably meant. It is the check that stops a run before it starts, so it
|
|
1063
|
+
knows what the `_shared.run` above the file binds.
|
|
1064
|
+
|
|
824
1065
|
Hover anything: a function shows its signature, what it does and a worked example; a property adds whether it
|
|
825
1066
|
may sit inside a block; `$`, `exec`, `run`, `retry`, `match` and the rest explain the line form itself.
|
|
826
1067
|
|
|
1068
|
+
Completion knows where it is. At the start of a line it offers the keywords that fit there — `break` inside a
|
|
1069
|
+
loop, `case` inside a `match`, `end` only when something is open — and everywhere a value goes, the variables in
|
|
1070
|
+
scope: the file's own, its loop's, and those bound in the `_shared.run` above it. Inside `{{ … }}` on a `$` line
|
|
1071
|
+
it completes the same way, and in the rest of a shell line or an `exec` body it stays out of the way.
|
|
1072
|
+
|
|
827
1073
|
Ctrl+click a `run <target>` to open that target's file, or a variable to jump to where it was bound — including
|
|
828
1074
|
into the `_shared.run` above it, which is the one definition you cannot find by reading the file in front of
|
|
829
1075
|
you.
|
|
830
1076
|
|
|
831
|
-
|
|
832
|
-
|
|
1077
|
+
The shell in `$` lines and shell `exec` bodies is checked by the runner's own [shell checker](SHELL-CHECK-RULES.md),
|
|
1078
|
+
which reads each `{{ … }}` as the word it becomes — so nothing is reported against text nobody wrote, and there
|
|
1079
|
+
is nothing to install.
|
|
833
1080
|
|
|
834
|
-
**VS Code** — install the `.vsix` from the [latest release](https://
|
|
835
|
-
You get a Run button on every target, a task provider, a sidebar tree,
|
|
836
|
-
same command in a `.sh` file
|
|
1081
|
+
**VS Code** — install the `.vsix` from the [latest release](https://git.joaoverona.com/joaaoverona/runfile/releases).
|
|
1082
|
+
You get a Run button on every target, a task provider, a sidebar tree, shell lines coloured exactly as the
|
|
1083
|
+
same command in a `.sh` file, and a call to a function that does not exist coloured as an error as soon as you
|
|
1084
|
+
type its `(`. Set `"editor.formatOnSave": true` and saving formats.
|
|
837
1085
|
|
|
838
1086
|
**JetBrains IDEs** read the same grammar: *Settings → Editor → TextMate Bundles*, add the `editors/vscode`
|
|
839
1087
|
directory from a checkout.
|
|
@@ -907,7 +1155,7 @@ Shell is still there. It is just marked.
|
|
|
907
1155
|
| Windows | x86-64, arm64 |
|
|
908
1156
|
|
|
909
1157
|
`$` lines use bash where it exists, Git Bash on Windows, and `sh` otherwise — so one file works everywhere. Set
|
|
910
|
-
`.shell` to pin
|
|
1158
|
+
`.shell` to pin one of the other POSIX shells, or name any other interpreter with `exec`.
|
|
911
1159
|
|
|
912
1160
|
## License
|
|
913
1161
|
|
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