@runfile/cli 1.9.3 → 2.0.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 +151 -124
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Runfile
|
|
2
2
|
|
|
3
|
-
[Quick start](#quick-start) · [Examples](#what-a-runfile-looks-like) · [Language](#the-language) · [Properties](#properties) · [Commands](#commands) · [Editors](#editor-support)
|
|
3
|
+
[Quick start](#quick-start) · [Examples](#what-a-runfile-looks-like) · [Language](#the-language) · [Environment](#the-environment-the-working-directory-and-failures) · [Properties](#properties) · [Commands](#commands) · [Editors](#editor-support)
|
|
4
4
|
|
|
5
5
|
**One directory. One binary. Every OS.**
|
|
6
6
|
|
|
@@ -30,8 +30,8 @@ file to merge, so two people adding a task never conflict. `run :list` shows the
|
|
|
30
30
|
# runfiles/dev.run
|
|
31
31
|
# Start the dev server
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
.
|
|
33
|
+
import_env_file(".env.local")
|
|
34
|
+
ENV.PORT = ARG.port ? "3000"
|
|
35
35
|
|
|
36
36
|
$ vite
|
|
37
37
|
```
|
|
@@ -255,7 +255,7 @@ script, a SQL file — whatever is already on the machine. The body is that comm
|
|
|
255
255
|
# runfiles/i18n/check.run
|
|
256
256
|
# Check that every translation catalogue has the same keys as the reference
|
|
257
257
|
|
|
258
|
-
.
|
|
258
|
+
ENV.LOCALES = "src/locales"
|
|
259
259
|
|
|
260
260
|
exec python3
|
|
261
261
|
import json, os, pathlib, sys
|
|
@@ -277,7 +277,7 @@ end
|
|
|
277
277
|
# runfiles/deps/list.run
|
|
278
278
|
# Print the first few runtime dependencies
|
|
279
279
|
|
|
280
|
-
.
|
|
280
|
+
ENV.LIMIT = ARG.limit ? "10"
|
|
281
281
|
|
|
282
282
|
exec node
|
|
283
283
|
const { readFileSync } = require("node:fs")
|
|
@@ -326,7 +326,7 @@ end
|
|
|
326
326
|
```
|
|
327
327
|
|
|
328
328
|
One thing to know: the body **is** the command's stdin, so a script cannot also read data from stdin — give it
|
|
329
|
-
what it needs through
|
|
329
|
+
what it needs through `ENV`, a file, or the command's own arguments.
|
|
330
330
|
|
|
331
331
|
An interpolation inside the body arrives **as itself**, unquoted, when the command is not a shell — shell
|
|
332
332
|
quoting is the wrong quoting for anybody else's language, so quote it the way that language wants. A shell
|
|
@@ -338,8 +338,8 @@ body still self-quotes, and `$ cp {{ src }} {{ dst }}` is as safe as it ever was
|
|
|
338
338
|
# runfiles/deploy.run
|
|
339
339
|
# Deploy to production
|
|
340
340
|
|
|
341
|
-
|
|
342
|
-
.
|
|
341
|
+
import_env_file(".env.production")
|
|
342
|
+
ENV.GOOGLE_APPLICATION_CREDENTIALS = temp_file(base64_decode(ENV.SERVICE_ACCOUNT_B64), "json")
|
|
343
343
|
|
|
344
344
|
confirm("Deploy to production?")
|
|
345
345
|
$ terraform apply -auto-approve
|
|
@@ -389,7 +389,8 @@ Line-oriented, with one rule: **the language is the default, the shell is marked
|
|
|
389
389
|
| Line | Meaning |
|
|
390
390
|
| --- | --- |
|
|
391
391
|
| `# text` | A comment, to the end of the line. It may follow code. The leading block is the target's description. |
|
|
392
|
-
| `.name = value` | A property. |
|
|
392
|
+
| `.name = value` | A property: a fact about the whole file, at its top. |
|
|
393
|
+
| `ENV.NAME = value` | Set an environment variable for every line below. |
|
|
393
394
|
| `$ echo hi` | Hand this line to a shell. |
|
|
394
395
|
| `exec python3` … `end` | Run a command with the block as its stdin. |
|
|
395
396
|
| `detach $ npm run dev` | Start this one command and do not wait for it. Also `detach exec …`. |
|
|
@@ -402,7 +403,7 @@ Line-oriented, with one rule: **the language is the default, the shell is marked
|
|
|
402
403
|
| `break` / `continue` | Leave the innermost loop, or start its next pass. |
|
|
403
404
|
| `match` / `case` / `default` / `end` | Dispatch on a value. A label is a quoted string: `case "linux"`. |
|
|
404
405
|
| `retry n [every s]` … `end` | Run the block again while it fails, up to `n` times. |
|
|
405
|
-
| `do` … `end` | A block with no condition,
|
|
406
|
+
| `do` … `end` | A block with no condition, and the one that puts back the environment, working directory and `ignore_errors()` at its `end`. |
|
|
406
407
|
| `run other-target` | Run another target, in this process, starting from this one's environment. |
|
|
407
408
|
| `print(…)` | Anything else is an expression, evaluated for its effect. |
|
|
408
409
|
|
|
@@ -502,7 +503,10 @@ end
|
|
|
502
503
|
after the block, so assigning a name bound outside the branch is a parse error — that value would be lost. A
|
|
503
504
|
`let` directly inside `parallel do` binds nothing anyone can read, and is one too.
|
|
504
505
|
- Every branch **runs to completion before a failure is reported**, so a set of services is never left half
|
|
505
|
-
started.
|
|
506
|
+
started. `ignore_errors()` in effect around the block forgives each branch.
|
|
507
|
+
- Each branch also changes **its own copy of the environment and working directory**: an `ENV.X =` or a
|
|
508
|
+
`set_cwd()` inside a branch reaches that branch's commands and nothing else. One that is a whole branch of its
|
|
509
|
+
own would reach nothing at all, and is a parse error.
|
|
506
510
|
- Every line a branch prints carries its name, **in a colour of its own**: the target it runs, the iteration's
|
|
507
511
|
value, or as many words of its command as tell it from its siblings — `cargo test` and `cargo clippy`.
|
|
508
512
|
Siblings take consecutive colours, so the output you are reading side by side is the output guaranteed to
|
|
@@ -564,8 +568,9 @@ Nothing is declared for that — a `?` chain is what makes a value optional, and
|
|
|
564
568
|
default. A `one_of` with its options written out, `one_of(ARG.env ? "staging", "staging", "production")`, is
|
|
565
569
|
what lists the values a name takes. `run --stdin-args deploy` asks for the same list, in the same order,
|
|
566
570
|
**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
|
|
568
|
-
the list at all, since nothing the caller passes would reach it.
|
|
571
|
+
answer. A name the target sets for itself with `ENV.NAME =` on every path to the read — above it, or in a
|
|
572
|
+
`_shared.run` — is not on the list at all, since nothing the caller passes would reach it. One set on only some of
|
|
573
|
+
the paths, or by a file, is still listed.
|
|
569
574
|
|
|
570
575
|
A target that reads `ARGS` is asked for its positionals too, when none were given: one line, split the way a
|
|
571
576
|
command line is, so `"two words"` stays one. Enter passes none, which is what a wrapper usually wants. A
|
|
@@ -717,7 +722,7 @@ end
|
|
|
717
722
|
### Stopping early
|
|
718
723
|
|
|
719
724
|
`exit()` ends the target it is written in, with a status — 0 when none is given. Nothing inside that target
|
|
720
|
-
catches it: not a `?` fallback, not
|
|
725
|
+
catches it: not a `?` fallback, not `ignore_errors()`, not a `retry`. A target may forgive a command that failed,
|
|
721
726
|
but being told to stop is not that. It is also how a helper finishes early:
|
|
722
727
|
|
|
723
728
|
```sh
|
|
@@ -734,10 +739,10 @@ $ ./scripts/setup-toolchain.sh
|
|
|
734
739
|
|
|
735
740
|
To a target that ran it, the status is how it went — exactly what `$ run _setup-build` would make of it. After a
|
|
736
741
|
0, the `run _setup-build` line is done and the next line runs. Anything else fails that `run` line, like a
|
|
737
|
-
command that failed:
|
|
742
|
+
command that failed: `ignore_errors()` forgives it, `retry` has another go, `code_of` scores it, and if nothing
|
|
738
743
|
handles it, the whole run ends with that status.
|
|
739
744
|
|
|
740
|
-
`error("…")` fails with a message, the way a command fails, so a `?` falls back from it and
|
|
745
|
+
`error("…")` fails with a message, the way a command fails, so a `?` falls back from it and `ignore_errors()`
|
|
741
746
|
forgives it. `confirm("…")` asks, and stops the whole run if the answer is no. Nothing catches that, not even a
|
|
742
747
|
target that ran this one.
|
|
743
748
|
|
|
@@ -805,91 +810,79 @@ time, and never a line that could be right — no style, no guesses. Your editor
|
|
|
805
810
|
underlines the same findings and `run :lint` reports them; [SHELL-CHECK-RULES.md](SHELL-CHECK-RULES.md) lists
|
|
806
811
|
every rule, what it leaves alone, and why.
|
|
807
812
|
|
|
808
|
-
##
|
|
809
|
-
|
|
810
|
-
Set at the top of the file, or inside a block where marked.
|
|
813
|
+
## The environment, the working directory, and failures
|
|
811
814
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
| `.shell` | Which POSIX shell `$` lines use. | | |
|
|
815
|
-
| `.env.NAME` | Set an environment variable. | ✅ | |
|
|
816
|
-
| `.workdir` | Where commands run, and what every relative path below it resolves against. | ✅ | |
|
|
817
|
-
| `.ignore-errors` | Keep going when a command fails. | ✅ | ✅ |
|
|
818
|
-
| `.logging` | Announce each command on stderr before it runs. Off unless set. | | ✅ |
|
|
819
|
-
| `.env-file` | Load a `.env` file (encrypted values are decrypted in memory). Appends. | ✅ | |
|
|
820
|
-
| `.add-path` | Prepend a directory to `PATH`. Appends. | ✅ | |
|
|
821
|
-
| `.watch` | Re-run when matching files change. A `!` prefix excludes. | | |
|
|
822
|
-
| `.only-in-directories` | Machine-wide targets only: offer this one inside these directories. Appends. | | |
|
|
815
|
+
Every line runs in what the lines above it left: an **environment**, a **working directory**, and whether a
|
|
816
|
+
failure **stops** the target. Five lines change them, each a line of its own:
|
|
823
817
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
a
|
|
818
|
+
| Line | Effect |
|
|
819
|
+
| --- | --- |
|
|
820
|
+
| `ENV.NAME = value` | Set an environment variable — for `ENV.NAME` below, and every command and target the lines below start. |
|
|
821
|
+
| `set_cwd(dir)` | Change where commands run, and what every relative path resolves against. Relative to the one in effect, the way `cd` is. |
|
|
822
|
+
| `add_path(dir)` | Put a directory, or a list of them, at the front of `PATH`. |
|
|
823
|
+
| `import_env_file(path)` | Load a `.env` file, or a list of them. Encrypted values are decrypted in memory; a file that is not there is skipped. |
|
|
824
|
+
| `ignore_errors()` | Keep going when a command fails. |
|
|
830
825
|
|
|
831
|
-
A
|
|
832
|
-
|
|
833
|
-
built from what a `.env-file` above it loaded, or from another `.env` above it:
|
|
826
|
+
A change **lasts past the block it is written in**, the way a `let` does — so a condition can decide something
|
|
827
|
+
for the rest of the target:
|
|
834
828
|
|
|
835
829
|
```sh
|
|
836
830
|
# runfiles/build.run
|
|
837
|
-
# Build
|
|
838
|
-
|
|
839
|
-
let out = concat("target-", ARG.profile ? "debug")
|
|
831
|
+
# Build with whatever toolchain this machine has
|
|
840
832
|
|
|
841
|
-
.
|
|
833
|
+
if RUN.os == "mac"
|
|
834
|
+
add_path("/opt/homebrew/bin")
|
|
835
|
+
ENV.CC = "clang"
|
|
836
|
+
end
|
|
842
837
|
|
|
843
|
-
$
|
|
838
|
+
$ make
|
|
844
839
|
```
|
|
845
840
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
.env-file = ".env"
|
|
851
|
-
.env.DATABASE_URL = concat("postgres://", ENV.DB_HOST, "/app")
|
|
841
|
+
**`do … end` is the block that puts it all back.** What changes inside it — the environment, the working
|
|
842
|
+
directory, `ignore_errors()` — ends at its `end`, however the block is left. That is what replaced writing
|
|
843
|
+
`cd web &&` on every line:
|
|
852
844
|
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
Four of them describe the whole file rather than the commands under them — `.shell`, `.logging`, `.watch` and
|
|
857
|
-
`.only-in-directories` — and those have to be written above its first statement.
|
|
845
|
+
```sh
|
|
846
|
+
# runfiles/setup.run
|
|
847
|
+
# One-time per clone: hooks, then the web dependencies
|
|
858
848
|
|
|
859
|
-
|
|
860
|
-
`busybox` or `brush`. Any other interpreter is named on the line instead, with `exec` — which is the same
|
|
861
|
-
mechanism, and says so where it can be read:
|
|
849
|
+
$ git config core.hooksPath .githooks
|
|
862
850
|
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
# List the current directory, whatever the platform
|
|
851
|
+
do
|
|
852
|
+
set_cwd("web")
|
|
866
853
|
|
|
867
|
-
|
|
868
|
-
exec
|
|
869
|
-
Get-ChildItem
|
|
870
|
-
end
|
|
871
|
-
else
|
|
872
|
-
$ ls -la
|
|
854
|
+
$ pnpm install
|
|
855
|
+
$ pnpm exec playwright install chromium
|
|
873
856
|
end
|
|
874
857
|
```
|
|
875
858
|
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
859
|
+
A loop's next pass starts where the last one left, as any block but a `do` does — so a relative `set_cwd()` in a
|
|
860
|
+
loop wants a `do` around it, and is refused without one where every pass would move on from the last
|
|
861
|
+
(`relative-cwd-in-loop`). Each attempt of a `retry` starts from where the `retry` began, and the attempt that
|
|
862
|
+
succeeds keeps what it changed. A branch of a `parallel` block changes a copy: what it sets reaches its own
|
|
863
|
+
commands and nothing else.
|
|
864
|
+
|
|
865
|
+
`ignore_errors()` goes at the top of a file or directly inside a `do`, where it is plain how far it reaches;
|
|
866
|
+
anywhere else it would forgive every failure to the end of the target, and is refused. A single command that may
|
|
867
|
+
fail is `code_of($ …)` on a line of its own.
|
|
868
|
+
|
|
869
|
+
**What a target sets wins over the caller's environment; a file does not.** An assignment written in the file is
|
|
870
|
+
the one way a target can force a value, so `PORT=4000 run serve` still serves on 3000 under `ENV.PORT = "3000"`.
|
|
871
|
+
A file is a default, the dotenv convention: a value it holds fills a name nothing has set, or replaces an earlier
|
|
872
|
+
file's — never what the caller exported, and never an `ENV.NAME =`, whichever line comes first. A default the
|
|
873
|
+
caller *may* override is spelled out, and `--help` shows it as one:
|
|
881
874
|
|
|
882
875
|
```sh
|
|
883
876
|
# runfiles/serve.run
|
|
884
877
|
# Serve locally
|
|
885
878
|
|
|
886
|
-
.
|
|
879
|
+
ENV.PORT = ENV.PORT ? "3000"
|
|
887
880
|
|
|
888
881
|
$ node server.js
|
|
889
882
|
```
|
|
890
883
|
|
|
891
|
-
|
|
892
|
-
|
|
884
|
+
Every value is an expression, read where its line is — so it can be built from what a file above it loaded, or
|
|
885
|
+
named by something the target worked out first:
|
|
893
886
|
|
|
894
887
|
```sh
|
|
895
888
|
# runfiles/deploy.run
|
|
@@ -897,10 +890,11 @@ block's file can be named by something that block worked out:
|
|
|
897
890
|
|
|
898
891
|
let env = one_of(first(ARGS), "staging", "production")
|
|
899
892
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
.add-path = "vendor/prod-tools"
|
|
893
|
+
import_env_file(".env.{{ env }}")
|
|
894
|
+
ENV.DATABASE_URL = concat("postgres://", ENV.DB_HOST, "/app")
|
|
903
895
|
|
|
896
|
+
if env == "production"
|
|
897
|
+
add_path("vendor/prod-tools")
|
|
904
898
|
confirm("Deploy to production?")
|
|
905
899
|
end
|
|
906
900
|
|
|
@@ -916,11 +910,10 @@ runfiles/_aws.run → run _aws (a helper other targets call)
|
|
|
916
910
|
runfiles/deploy.run → run deploy
|
|
917
911
|
```
|
|
918
912
|
|
|
919
|
-
**A target that `run`s another hands it its environment.** Whatever a command on that line would be given —
|
|
920
|
-
the caller
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
its own:
|
|
913
|
+
**A target that `run`s another hands it its environment.** Whatever a command on that line would be given — what
|
|
914
|
+
the caller set, imported and added, and what the caller was run with itself — is where the target it runs starts.
|
|
915
|
+
That target changes its own copy the way it would your shell's: its own `ENV.NAME =` wins, and its own
|
|
916
|
+
`add_path()` goes in front. Nothing it changes comes back. So a helper needs no environment of its own:
|
|
924
917
|
|
|
925
918
|
```sh
|
|
926
919
|
# runfiles/_aws.run
|
|
@@ -933,46 +926,73 @@ $ docker run --rm -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY amazon/aws-cli {
|
|
|
933
926
|
# runfiles/backups.run
|
|
934
927
|
# List the production backups
|
|
935
928
|
|
|
936
|
-
|
|
929
|
+
import_env_file(".env.production")
|
|
937
930
|
|
|
938
931
|
run _aws s3 ls s3://example-backups
|
|
939
932
|
```
|
|
940
933
|
|
|
941
|
-
A value that only a
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
934
|
+
A value that only a file supplied is still a default when it arrives, so the target's own file replaces it: a `ci`
|
|
935
|
+
target that loads `.env` and runs `test` does not push the development database over `test`'s own `.env.test`.
|
|
936
|
+
`$ run test` is different. A new process can only be handed variables, so there the caller's file values arrive
|
|
937
|
+
as if you had exported them, and they beat `test`'s own file.
|
|
945
938
|
|
|
946
|
-
|
|
947
|
-
|
|
939
|
+
**A value handed to `run` stays a value.** `run deploy {{ ARG.branch }}` gives `deploy` a positional however the
|
|
940
|
+
branch is spelled — a branch named `--force` does not set `deploy`'s `FLAG.force` — and `run deploy --env
|
|
941
|
+
{{ ARG.env }}` gives `--env` its value whatever it begins with. What you write yourself is read as written, and
|
|
942
|
+
so is a list handed on whole, which is how a wrapper forwards its command line: `run build {{ ARGS }}` still
|
|
943
|
+
passes `--release` along as a flag.
|
|
948
944
|
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
945
|
+
`_shared.run` runs ahead of every target in its directory, line for line the way the target does, and the target
|
|
946
|
+
starts from what it left — its bindings, the environment, the working directory. A nested one runs after the
|
|
947
|
+
directory above it, so `runfiles/api/_shared.run` adds to `runfiles/_shared.run`. Its commands run once for each
|
|
948
|
+
target, every time it runs, including a target another one `run`s.
|
|
952
949
|
|
|
953
|
-
|
|
950
|
+
```sh
|
|
951
|
+
# runfiles/_shared.run
|
|
952
|
+
# Settings every target here inherits
|
|
954
953
|
|
|
955
|
-
|
|
956
|
-
|
|
954
|
+
add_path("node_modules/.bin")
|
|
955
|
+
ENV.CARGO_TERM_COLOR = "always"
|
|
957
956
|
|
|
958
|
-
|
|
959
|
-
$ pnpm exec playwright install chromium
|
|
960
|
-
end
|
|
957
|
+
let image = "ghcr.io/example/builder:v2"
|
|
961
958
|
```
|
|
962
959
|
|
|
963
|
-
|
|
960
|
+
## Properties
|
|
964
961
|
|
|
965
|
-
|
|
966
|
-
one layers over the directory above it, so `runfiles/api/_shared.run` adds to `runfiles/_shared.run`.
|
|
962
|
+
A property is a fact about the whole file, written at its top, above the first statement.
|
|
967
963
|
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
964
|
+
| Property | Effect | Flag? |
|
|
965
|
+
| --- | --- | :-: |
|
|
966
|
+
| `.shell` | Which POSIX shell `$` lines use. | |
|
|
967
|
+
| `.logging` | Announce each command on stderr before it runs. Off unless set. | ✅ |
|
|
968
|
+
| `.watch` | Re-run when matching files change. A `!` prefix excludes. | |
|
|
969
|
+
| `.only-in-directories` | Machine-wide targets only: offer this one inside these directories. | |
|
|
971
970
|
|
|
972
|
-
.
|
|
973
|
-
|
|
971
|
+
A **flag** is written bare for `= true`, or given a bool. A constant that can never be one is refused where it
|
|
972
|
+
is written — `.logging = 23` and `.logging = "true"` are both errors, and the second says to drop the quotes. A
|
|
973
|
+
value the run works out is left to the run: `.logging = ENV.CI` and `.logging = ARG.verbose ? "false"` are how a
|
|
974
|
+
flag is decided from outside the file, and there `true`, `false`, `1` and `0` are all understood. Anything else
|
|
975
|
+
stops the run rather than reading as `false`, because a flag that quietly did not take effect is found out much
|
|
976
|
+
later.
|
|
974
977
|
|
|
975
|
-
|
|
978
|
+
A property's value is worked out before the file's first line runs, after every `_shared.run` above it has — so a
|
|
979
|
+
`.watch` is relative to the working directory those left, and reads what they set.
|
|
980
|
+
|
|
981
|
+
`.shell` names one of the eight shells `$` knows how to drive: `sh`, `bash`, `dash`, `ash`, `zsh`, `ksh`,
|
|
982
|
+
`busybox` or `brush`. Any other interpreter is named on the line instead, with `exec` — which is the same
|
|
983
|
+
mechanism, and says so where it can be read:
|
|
984
|
+
|
|
985
|
+
```sh
|
|
986
|
+
# runfiles/list.run
|
|
987
|
+
# List the current directory, whatever the platform
|
|
988
|
+
|
|
989
|
+
if RUN.os == "windows"
|
|
990
|
+
exec pwsh
|
|
991
|
+
Get-ChildItem
|
|
992
|
+
end
|
|
993
|
+
else
|
|
994
|
+
$ ls -la
|
|
995
|
+
end
|
|
976
996
|
```
|
|
977
997
|
|
|
978
998
|
## How targets are found
|
|
@@ -1004,8 +1024,8 @@ your home directory looks: it is never taken for a project, so from a directory
|
|
|
1004
1024
|
own you get your global targets — and not every project under your home as a subproject.
|
|
1005
1025
|
|
|
1006
1026
|
A machine-wide target's `runfiles/` parent is your home directory, so that is where its commands run. One that
|
|
1007
|
-
acts on the project in front of you says so with
|
|
1008
|
-
`read_file("package.json")` and
|
|
1027
|
+
acts on the project in front of you says so with `set_cwd(RUN.cwd)` — and every relative path follows it, so
|
|
1028
|
+
`read_file("package.json")` and `import_env_file(".env")` read the project's files, not your home's. A `_shared.run`
|
|
1009
1029
|
in the machine-wide directory can say it once for all of them.
|
|
1010
1030
|
|
|
1011
1031
|
A machine-wide target can also say where it belongs, so one directory can hold work for several places at once:
|
|
@@ -1015,7 +1035,8 @@ A machine-wide target can also say where it belongs, so one directory can hold w
|
|
|
1015
1035
|
# Ship the current branch
|
|
1016
1036
|
|
|
1017
1037
|
.only-in-directories = ["~/work/acme", "~/work/zed"]
|
|
1018
|
-
|
|
1038
|
+
|
|
1039
|
+
set_cwd(RUN.cwd)
|
|
1019
1040
|
|
|
1020
1041
|
$ ./scripts/ship
|
|
1021
1042
|
```
|
|
@@ -1033,14 +1054,14 @@ project's lint leaves the directory out otherwise.
|
|
|
1033
1054
|
**Not in CI.** A runner's home directory is nobody's, so `run` reads none of the three there: what runs is
|
|
1034
1055
|
what is checked in and reviewable. This is also why nothing has to be cleaned up after a job.
|
|
1035
1056
|
|
|
1036
|
-
Everything relative — a command, `glob`, `read_file` and the other file functions,
|
|
1037
|
-
`.watch`, and
|
|
1038
|
-
rule, so a path names the same file to the language as it does to the shell on the next line. The
|
|
1039
|
-
directory starts at the parent of `runfiles/`, which is `{{ RUN.parent }}`, and a
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1057
|
+
Everything relative — a command, `glob`, `read_file` and the other file functions, `import_env_file()`,
|
|
1058
|
+
`add_path()`, `.watch`, and `set_cwd()` itself — resolves against **the working directory in effect where it is
|
|
1059
|
+
written**. One rule, so a path names the same file to the language as it does to the shell on the next line. The
|
|
1060
|
+
working directory starts at the parent of `runfiles/`, which is `{{ RUN.parent }}`, and a `set_cwd()` moves it
|
|
1061
|
+
for every line below, to the end of the `do` block it is in, if any. It is relative to the one in effect, the way
|
|
1062
|
+
`cd` is: `set_cwd("src")` after `set_cwd("web")` is `web/src`. A path given *before* a `set_cwd()` keeps the
|
|
1063
|
+
directory it was given in, so `add_path("web/node_modules/.bin")` above `set_cwd("web")` still names the
|
|
1064
|
+
subproject's tools. An absolute path is taken as written, and an absolute `glob` pattern answers with
|
|
1044
1065
|
absolute paths. `RUN.parent` and `RUN.cwd` never move: the first is the project, the second is where you typed
|
|
1045
1066
|
`run`.
|
|
1046
1067
|
|
|
@@ -1073,7 +1094,7 @@ whose state is still worth keeping, so a `setup` run under it is still recorded.
|
|
|
1073
1094
|
| `run :init` | Create `runfiles/` with an example |
|
|
1074
1095
|
| `run :lint [path…]` | Format runfiles in place and report anything the runner would refuse (`--check` writes nothing, `--stdout`) |
|
|
1075
1096
|
| `run :env <sub>` | Manage `.env` files: `init`, `get`, `set`, `encrypt`, `decrypt`, `rotate`, `inject`, `secret-keys` |
|
|
1076
|
-
| `run :completions <command> <shell>` | `install`, `uninstall` or `output` a completion script for `bash`, `zsh`, `fish` or `powershell` — after upgrading from 1.
|
|
1097
|
+
| `run :completions <command> <shell>` | `install`, `uninstall` or `output` a completion script for `bash`, `zsh`, `fish` or `powershell` — after upgrading from 1.11 or older, `install bash` again: the installed script is a copy, and an older one completes a session's first `run :comp<Tab>` to `run ::completions` |
|
|
1077
1098
|
| `run :generate <editor>` | Task files for `zed`, `jetbrains` or `vscode`, merged into what is there |
|
|
1078
1099
|
| `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 |
|
|
1079
1100
|
|
|
@@ -1133,11 +1154,17 @@ run :env rotate .env.production # re-encrypt under a new key
|
|
|
1133
1154
|
falls back to plaintext: for a file that records none, `--key <prefix>` names the key to use, and `--plain`
|
|
1134
1155
|
stores the value as it is.
|
|
1135
1156
|
|
|
1136
|
-
Keys live in the OS credential store — Keychain, Credential Manager, or Secret Service with
|
|
1137
|
-
|
|
1157
|
+
Keys live in the OS credential store — Keychain, Credential Manager, or Secret Service. A Linux machine with no
|
|
1158
|
+
Secret Service, such as a server, keeps them in `~/.local/share/runfile/keys.json` (under `$XDG_DATA_HOME` when
|
|
1159
|
+
it is set) instead, so nothing has to be installed there and they survive a reboot. The file is created
|
|
1160
|
+
owner-only, and refused if anyone else owns it or can read or write it, the same rule ssh applies to a private
|
|
1161
|
+
key; it is not encrypted on disk, so a backup of the home directory holds the keys. Keys stored there are still
|
|
1162
|
+
read when a Secret Service is available. Keys an older version left in kernel keyutils, which a reboot clears,
|
|
1163
|
+
are moved into the store the first time they are read. `run :env secret-keys list` shows where each key is. In
|
|
1164
|
+
CI, pass them as `RUNFILE_PRIVATE_KEYS` (newline-separated) and no credential store is involved.
|
|
1138
1165
|
|
|
1139
1166
|
Only a value a `.env` file holds is decrypted — the one place an `encrypted:` value is the author's own. A
|
|
1140
|
-
ciphertext arriving any other way (exported in the caller's shell, or
|
|
1167
|
+
ciphertext arriving any other way (exported in the caller's shell, or set with `ENV.X = ARG.x` from
|
|
1141
1168
|
data an outsider controls) is passed through unchanged, never decrypted with the run's keys, so it cannot be
|
|
1142
1169
|
turned back into plaintext by a target that merely echoes a variable.
|
|
1143
1170
|
|
|
@@ -1158,7 +1185,7 @@ where it is written, with the name you probably meant. It is the check that stop
|
|
|
1158
1185
|
knows what the `_shared.run` above the file binds.
|
|
1159
1186
|
|
|
1160
1187
|
Hover anything: a function shows its signature, what it does and a worked example; a property adds whether it
|
|
1161
|
-
|
|
1188
|
+
is a flag; `$`, `exec`, `run`, `retry`, `match` and the rest explain the line form itself.
|
|
1162
1189
|
|
|
1163
1190
|
Completion knows where it is. At the start of a line it offers the keywords that fit there — `break` inside a
|
|
1164
1191
|
loop, `case` inside a `match`, `end` only when something is open — and everywhere a value goes, the variables in
|
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