@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 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
- .env-file = ".env.local"
34
- .env.PORT = ARG.port ? "3000"
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
- .env.LOCALES = "src/locales"
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
- .env.LIMIT = ARG.limit ? "10"
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 `.env`, a file, or the command's own arguments.
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
- .env-file = ".env.production"
342
- .env.GOOGLE_APPLICATION_CREDENTIALS = temp_file(base64_decode(ENV.SERVICE_ACCOUNT_B64), "json")
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, so a property can cover a few commands. |
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. `.ignore-errors` on the block forgives each branch.
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 `.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.
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 `.ignore-errors`, not a `retry`. A target may forgive a command that failed,
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: `.ignore-errors` forgives it, `retry` has another go, `code_of` scores it, and if nothing
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 `.ignore-errors`
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
- ## Properties
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
- | Property | Effect | In a block? | Flag? |
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
- A **flag** is written bare for `= true`, or given a bool. A constant that can never be one is refused where it
825
- is written — `.ignore-errors = 23` and `.ignore-errors = "true"` are both errors, and the second says to drop
826
- the quotes. A value the run works out is left to the run: `.ignore-errors = ENV.CI` and
827
- `.ignore-errors = ARG.p ? ENV.CI ? "false"` are how a flag is decided from outside the file, and there `true`,
828
- `false`, `1` and `0` are all understood. Anything else stops the run rather than reading as `false`, because
829
- a flag that quietly did not take effect is found out much later.
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 property is applied **where it is written**: it can read a binding above it, and it takes effect from there
832
- down. The environment works the same way — a value reads it as it stands at its own line, so a property can be
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 into a directory the command line picks
838
-
839
- let out = concat("target-", ARG.profile ? "debug")
831
+ # Build with whatever toolchain this machine has
840
832
 
841
- .workdir = out
833
+ if RUN.os == "mac"
834
+ add_path("/opt/homebrew/bin")
835
+ ENV.CC = "clang"
836
+ end
842
837
 
843
- $ ls
838
+ $ make
844
839
  ```
845
840
 
846
- ```sh
847
- # runfiles/migrate.run
848
- # Migrate the database the local .env points at
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
- $ sqlx migrate run
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
- `.shell` names one of the eight shells `$` knows how to drive: `sh`, `bash`, `dash`, `ash`, `zsh`, `ksh`,
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
- ```sh
864
- # runfiles/list.run
865
- # List the current directory, whatever the platform
851
+ do
852
+ set_cwd("web")
866
853
 
867
- if RUN.os == "windows"
868
- exec pwsh
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
- **The target's own `.env.NAME` wins over the caller's environment; a `.env-file` does not.** An assignment
877
- written in the file is the one way a target can force a value, so `PORT=4000 run serve` still serves on 3000
878
- under `.env.PORT = "3000"` — and `ENV.PORT` and a command's `$PORT` both say 3000. A file is a default, the
879
- dotenv convention: what the caller exported beats it. A default the caller *may* override is spelled out, and
880
- `--help` shows it as one:
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
- .env.PORT = ENV.PORT ? "3000"
879
+ ENV.PORT = ENV.PORT ? "3000"
887
880
 
888
881
  $ node server.js
889
882
  ```
890
883
 
891
- `.env-file` and `.add-path` **append**, so a block adds to what it inherited rather than replacing it, and a
892
- block's file can be named by something that block worked out:
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
- if env == "production"
901
- .env-file = ".env.production"
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's `.env`, `.env-file` and `.add-path`, a block's as well as the header's, and what the caller was run
921
- with itself — is where the target it runs starts. That target layers its own settings over it the way it would
922
- over your shell: its own `.env` wins, and its own `.add-path` goes in front. So a helper needs no environment of
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
- .env-file = ".env.production"
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 `.env-file` supplied is still a default when it arrives, so the target's own `.env-file`
942
- replaces it: a `ci` target that loads `.env` and runs `test` does not push the development database over
943
- `test`'s own `.env.test`. `$ run test` is different. A new process can only be handed variables, so there the
944
- caller's `.env-file` values arrive as if you had exported them, and they beat `test`'s own file.
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
- `do … end` is a block with no condition — somewhere for a property to go when it should cover a few commands
947
- and not the whole target:
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
- ```sh
950
- # runfiles/setup.run
951
- # One-time per clone: hooks, then the web dependencies
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
- $ git config core.hooksPath .githooks
950
+ ```sh
951
+ # runfiles/_shared.run
952
+ # Settings every target here inherits
954
953
 
955
- do
956
- .workdir = "web"
954
+ add_path("node_modules/.bin")
955
+ ENV.CARGO_TERM_COLOR = "always"
957
956
 
958
- $ pnpm install
959
- $ pnpm exec playwright install chromium
960
- end
957
+ let image = "ghcr.io/example/builder:v2"
961
958
  ```
962
959
 
963
- That is what replaced writing `cd web &&` on every line, which the shell would have needed once per command.
960
+ ## Properties
964
961
 
965
- `_shared.run` holds properties — and `let` bindings — that every target in its directory inherits. A nested
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
- ```sh
969
- # runfiles/_shared.run
970
- # Settings every target here inherits
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
- .add-path = "node_modules/.bin"
973
- .env.CARGO_TERM_COLOR = "always"
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
- let image = "ghcr.io/example/builder:v2"
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 `.workdir = RUN.cwd` — and every relative path follows it, so
1008
- `read_file("package.json")` and `.env-file = ".env"` read the project's files, not your home's. A `_shared.run`
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
- .workdir = RUN.cwd
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, `.env-file`, `.add-path`,
1037
- `.watch`, and `.workdir` itself — resolves against **the working directory in effect where it is written**. One
1038
- rule, so a path names the same file to the language as it does to the shell on the next line. The working
1039
- directory starts at the parent of `runfiles/`, which is `{{ RUN.parent }}`, and a `.workdir` moves it from its
1040
- own line down to the end of its block. A nested one is relative to the one around it, the way `cd` is:
1041
- `.workdir = "src"` inside a block under `.workdir = "web"` is `web/src`. A path written *above* a `.workdir`
1042
- keeps the directory it was written in, so `.add-path = "web/node_modules/.bin"` above `.workdir = "web"` still
1043
- names the subproject's tools. An absolute path is taken as written, and an absolute `glob` pattern answers with
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.8.2 or older, `install bash` again: the installed script is a copy |
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 a keyutils
1137
- fallback. In CI, pass them as `RUNFILE_PRIVATE_KEYS` (newline-separated) and no credential store is involved.
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 copied into `.env.X = {{ ARG.x }}` from
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
- may sit inside a block; `$`, `exec`, `run`, `retry`, `match` and the rest explain the line form itself.
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
Binary file
Binary file
Binary file
package/bin/linux-x64/run CHANGED
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -13,5 +13,5 @@
13
13
  "license": "MIT",
14
14
  "name": "@runfile/cli",
15
15
  "repository": "https://github.com/JoaaoVerona/runfile",
16
- "version": "1.9.3"
16
+ "version": "2.0.0"
17
17
  }