@runfile/cli 0.12.1 → 0.13.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/bin/darwin-arm64/README.md +86 -5
- package/bin/darwin-arm64/run +0 -0
- package/bin/darwin-x64/README.md +86 -5
- package/bin/darwin-x64/run +0 -0
- package/bin/linux-arm64/README.md +86 -5
- package/bin/linux-arm64/run +0 -0
- package/bin/linux-x64/README.md +86 -5
- 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
|
@@ -111,16 +111,16 @@ Sorted from where Runfile is most differentiated to where it lags behind.
|
|
|
111
111
|
| Argument substitution with chained fallbacks | ✅ | ❌ | ✅ | ❌ |
|
|
112
112
|
| JSON Schema autocomplete in editors | ✅ | ❌ | ❌ | ✅ |
|
|
113
113
|
| Watch mode, built-in | ✅ | ❌ | ❌ | ✅ |
|
|
114
|
+
| Output grouping / prefixing in parallel mode | ✅ | ❌ | ❌ | ✅ |
|
|
114
115
|
| Native Windows (no WSL / msys2 needed) | ✅ | ❌ | ✅ | ✅ |
|
|
115
|
-
| Parallel execution | ✅ | ✅ | ❌ | ✅ |
|
|
116
116
|
| Shell completions | ✅ | ❌ | ✅ | ✅ |
|
|
117
117
|
| Private / internal targets (`_name`) | ✅ | ❌ | ✅ | ✅ |
|
|
118
|
+
| Built-in string functions (upper, replace, trim, regex, base64, …) | ✅ | ❌ | ✅ | ✅ |
|
|
119
|
+
| Parallel execution | ✅ | ✅ | ❌ | ✅ |
|
|
118
120
|
| Single static binary | ✅ | ✅ | ✅ | ✅ |
|
|
119
121
|
| Pattern rules (`%.o: %.c`) | ❌ | ✅ | ❌ | ❌ |
|
|
120
|
-
| Output grouping / prefixing in parallel mode | ❌ | ❌ | ❌ | ✅ |
|
|
121
122
|
| Preconditions / status checks | ❌ | ❌ | ❌ | ✅ |
|
|
122
123
|
| Incremental builds (sources / timestamps / checksums) | ❌ | ✅ | ❌ | ✅ |
|
|
123
|
-
| Built-in string functions (upper, replace, trim, …) | ❌ | ❌ | ✅ | ✅ |
|
|
124
124
|
|
|
125
125
|
---
|
|
126
126
|
|
|
@@ -244,8 +244,13 @@ and work as full substitution bodies *or* as chain segments:
|
|
|
244
244
|
// build : concat, join
|
|
245
245
|
// split : nth, first, last, count_parts
|
|
246
246
|
// encoding : base64_encode, base64_decode
|
|
247
|
+
// hashing : sha256, md5
|
|
248
|
+
// files : read_file, file_exists
|
|
249
|
+
// json : json_get, json_set
|
|
250
|
+
// error : try
|
|
247
251
|
// shell : shell_quote
|
|
248
252
|
// variables : define
|
|
253
|
+
// cwd : set_cwd
|
|
249
254
|
"echo deploying-{{ to_upper(ARGS.env) }}",
|
|
250
255
|
"curl -H \"X-Auth: {{ base64_encode(ENV.TOKEN) }}\" ...",
|
|
251
256
|
"echo {{ concat('hello-', ARGS.name, '-2026') }}",
|
|
@@ -269,6 +274,28 @@ and work as full substitution bodies *or* as chain segments:
|
|
|
269
274
|
// `shell_quote` picks the right quoting for the active shell:
|
|
270
275
|
"some-tool --json {{ shell_quote(base64_decode(ENV.SECRET_BASE64)) }}",
|
|
271
276
|
|
|
277
|
+
// Cache keys / content fingerprints. `sha256` for security-sensitive use,
|
|
278
|
+
// `md5` for cheap fingerprinting (NOT cryptographically secure).
|
|
279
|
+
"echo cache-key={{ sha256(read_file('package-lock.json')) }}",
|
|
280
|
+
|
|
281
|
+
// Branch on file presence without forking a shell. `file_exists` returns
|
|
282
|
+
// the literal "true" / "false", so it doubles as a DSL truthy value.
|
|
283
|
+
// "if": "{{ file_exists('.env.local') }}"
|
|
284
|
+
|
|
285
|
+
// Read a file inline — relative paths anchor to the Runfile directory.
|
|
286
|
+
// Use `try(...)` to recover from missing files.
|
|
287
|
+
"echo version={{ try(read_file('VERSION')) ? '0.0.0-dev' }}",
|
|
288
|
+
|
|
289
|
+
// Pull values out of arbitrary JSON without `jq` (works on every shell):
|
|
290
|
+
"echo db_host={{ json_get(read_file('config.json'), 'database.host') }}",
|
|
291
|
+
|
|
292
|
+
// Modify a JSON document in place (returns the new compact JSON):
|
|
293
|
+
"echo {{ json_set('{\"port\":3000}', 'env', 'production') }}",
|
|
294
|
+
|
|
295
|
+
// `try(expr)` swallows inner errors. Standalone returns "" on failure;
|
|
296
|
+
// chained, the next segment runs as a fallback.
|
|
297
|
+
"echo {{ try(base64_decode(ARGS.maybe_b64)) ? ARGS.maybe_b64 }}",
|
|
298
|
+
|
|
272
299
|
// Nested:
|
|
273
300
|
"echo {{ to_upper(to_lower(ARGS.x)) }}",
|
|
274
301
|
|
|
@@ -280,6 +307,13 @@ and work as full substitution bodies *or* as chain segments:
|
|
|
280
307
|
"{{ VARS.sdk }}/bin/build",
|
|
281
308
|
"echo using sdk at {{ VARS.sdk }}",
|
|
282
309
|
|
|
310
|
+
// `set_cwd(path)` switches the cwd subsequent commands spawn in — like
|
|
311
|
+
// shell `cd`, but works on every shell / OS without forking. Relative
|
|
312
|
+
// paths chain (matching `cd a; cd b` → `a/b`); absolute paths replace.
|
|
313
|
+
"{{ set_cwd(ARGS.subproject ? 'packages/api') }}",
|
|
314
|
+
"npm install",
|
|
315
|
+
"npm run build",
|
|
316
|
+
|
|
283
317
|
// Single-quoted strings interpolate nested {{ }} substitutions:
|
|
284
318
|
"{{ define(cmd, 'docker compose -f {{ VARS.compose }} pull') }}",
|
|
285
319
|
"{{ VARS.cmd }}"
|
|
@@ -291,6 +325,22 @@ to only whitespace (i.e. one consisting solely of a `{{ define(...) }}` call) is
|
|
|
291
325
|
being dispatched to the shell. `define`s in a parent target are visible to `@target` children. The `name` MUST
|
|
292
326
|
be a bareword identifier matching `[A-Za-z_][A-Za-z0-9_-]*` — quotes are NOT allowed on the name.
|
|
293
327
|
|
|
328
|
+
`set_cwd(path)` is the cwd analog of `define`: returns an empty string and changes the cwd that subsequent shell
|
|
329
|
+
commands in the *current target* spawn in. Behaves like shell `cd`, but works uniformly across every shell / OS
|
|
330
|
+
(no forked process, no `cd` binary needed). Resolution rules:
|
|
331
|
+
|
|
332
|
+
- **Absolute path** → fully replaces the current override (matches `cd /abs`).
|
|
333
|
+
- **Relative path** → joins onto the existing override, or onto the target's `workingDirectory` if no override
|
|
334
|
+
has been set yet. So `set_cwd('a'); set_cwd('b')` lands subsequent commands in `<workingDirectory>/a/b`,
|
|
335
|
+
matching how `cd a; cd b` chains in a shell.
|
|
336
|
+
|
|
337
|
+
`set_cwd` is **per-target**: each `@target` invocation starts with a clean override (the dispatched target sees
|
|
338
|
+
its own `workingDirectory`, not the caller's `set_cwd` state). Inside `parallel: true` targets each leaf
|
|
339
|
+
captures the override at the moment of its substitution, so siblings don't race on the spawn cwd. Like
|
|
340
|
+
`define`, the side effect is skipped on the redacted-logging pass so log lines don't double-apply it. With
|
|
341
|
+
`sameShell: true`, only the *final* `set_cwd` value applies (everything joins into one shell invocation) — use
|
|
342
|
+
shell `cd` directly between leaves there if you need intermediate cwd changes.
|
|
343
|
+
|
|
294
344
|
Function args are separated by `, ` (comma + exactly one space).
|
|
295
345
|
|
|
296
346
|
**Quote semantics inside `{{ ... }}`:**
|
|
@@ -319,13 +369,18 @@ evaluated by Runfile itself, so the logic works the same on every shell and plat
|
|
|
319
369
|
"cases": {
|
|
320
370
|
"1": "flutter emulators --launch Tier_1_Android_9",
|
|
321
371
|
"2": "flutter emulators --launch Tier_2_Android_11",
|
|
322
|
-
"3": "flutter emulators --launch Tier_3_Android_14"
|
|
372
|
+
"3": "flutter emulators --launch Tier_3_Android_14",
|
|
373
|
+
// Case keys wrapped in `/.../` are treated as regex patterns.
|
|
374
|
+
// Literal cases always win over a regex that would also match.
|
|
375
|
+
"/^v\\d+$/": "echo version-tag",
|
|
376
|
+
"/^pr-\\d+$/": "echo preview-build"
|
|
323
377
|
} }, // unknown values error out, listing the valid cases
|
|
324
378
|
|
|
325
379
|
{ "for": "service",
|
|
326
380
|
"in": ["api", "web", "worker"],
|
|
327
381
|
"parallel": true,
|
|
328
|
-
|
|
382
|
+
// VARS.<name>_index exposes the 0-based iteration counter.
|
|
383
|
+
"do": ["echo [{{ VARS.service_index }}] docker build -t {{ VARS.service }} services/{{ VARS.service }}"] },
|
|
329
384
|
|
|
330
385
|
{ "for": "file",
|
|
331
386
|
"shell": "git diff --name-only HEAD~1",
|
|
@@ -387,6 +442,27 @@ Wrap any commands in a `when:` block to run them only after a failure, or always
|
|
|
387
442
|
]
|
|
388
443
|
```
|
|
389
444
|
|
|
445
|
+
#### Single-shell mode with `sameShell`
|
|
446
|
+
|
|
447
|
+
By default each step runs in its own shell process, so `cd`, exported variables, and other shell state don't carry
|
|
448
|
+
over between steps. Set `"sameShell": true` to join every step into a single shell invocation — state changes
|
|
449
|
+
persist for free.
|
|
450
|
+
|
|
451
|
+
```jsonc
|
|
452
|
+
{
|
|
453
|
+
"deploy": {
|
|
454
|
+
"sameShell": true,
|
|
455
|
+
"commands": ["cd ci-scripts/", "./ci-deploy.sh"]
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
Steps are joined with `&&` so the run stops at the first failure (or `;` / `&` when `ignoreErrors: true`). `if`,
|
|
461
|
+
`for`, and `match` blocks are evaluated by Runfile and their chosen branches flow into the same joined invocation.
|
|
462
|
+
`@target` calls inside the body are rejected — they have their own shell context and can't share state with the
|
|
463
|
+
parent. `sameShell` also composes with `detach: true` (joined command spawns as one detached process) and is
|
|
464
|
+
available on `globals` for project-wide defaults.
|
|
465
|
+
|
|
390
466
|
#### Watch mode, built in
|
|
391
467
|
|
|
392
468
|
Add a `watch` array to any target. No flags, no extra tooling.
|
|
@@ -405,6 +481,11 @@ $ run :generate zed-tasks
|
|
|
405
481
|
$ run :generate jetbrains-run-configurations
|
|
406
482
|
```
|
|
407
483
|
|
|
484
|
+
Skip a target from the generated configs by setting `metadata.excludeFromGenerateCommand: true`. The
|
|
485
|
+
`metadata` block on `globals` and on each target is **fully open** — any property of any JSON type
|
|
486
|
+
(strings, numbers, booleans, arrays, deeply-nested objects) is accepted and round-trips untouched, so
|
|
487
|
+
editor extensions and CI scripts can stash arbitrary tooling-specific fields here.
|
|
488
|
+
|
|
408
489
|
#### Migrate in seconds
|
|
409
490
|
|
|
410
491
|
```bash
|
package/bin/darwin-arm64/run
CHANGED
|
Binary file
|
package/bin/darwin-x64/README.md
CHANGED
|
@@ -111,16 +111,16 @@ Sorted from where Runfile is most differentiated to where it lags behind.
|
|
|
111
111
|
| Argument substitution with chained fallbacks | ✅ | ❌ | ✅ | ❌ |
|
|
112
112
|
| JSON Schema autocomplete in editors | ✅ | ❌ | ❌ | ✅ |
|
|
113
113
|
| Watch mode, built-in | ✅ | ❌ | ❌ | ✅ |
|
|
114
|
+
| Output grouping / prefixing in parallel mode | ✅ | ❌ | ❌ | ✅ |
|
|
114
115
|
| Native Windows (no WSL / msys2 needed) | ✅ | ❌ | ✅ | ✅ |
|
|
115
|
-
| Parallel execution | ✅ | ✅ | ❌ | ✅ |
|
|
116
116
|
| Shell completions | ✅ | ❌ | ✅ | ✅ |
|
|
117
117
|
| Private / internal targets (`_name`) | ✅ | ❌ | ✅ | ✅ |
|
|
118
|
+
| Built-in string functions (upper, replace, trim, regex, base64, …) | ✅ | ❌ | ✅ | ✅ |
|
|
119
|
+
| Parallel execution | ✅ | ✅ | ❌ | ✅ |
|
|
118
120
|
| Single static binary | ✅ | ✅ | ✅ | ✅ |
|
|
119
121
|
| Pattern rules (`%.o: %.c`) | ❌ | ✅ | ❌ | ❌ |
|
|
120
|
-
| Output grouping / prefixing in parallel mode | ❌ | ❌ | ❌ | ✅ |
|
|
121
122
|
| Preconditions / status checks | ❌ | ❌ | ❌ | ✅ |
|
|
122
123
|
| Incremental builds (sources / timestamps / checksums) | ❌ | ✅ | ❌ | ✅ |
|
|
123
|
-
| Built-in string functions (upper, replace, trim, …) | ❌ | ❌ | ✅ | ✅ |
|
|
124
124
|
|
|
125
125
|
---
|
|
126
126
|
|
|
@@ -244,8 +244,13 @@ and work as full substitution bodies *or* as chain segments:
|
|
|
244
244
|
// build : concat, join
|
|
245
245
|
// split : nth, first, last, count_parts
|
|
246
246
|
// encoding : base64_encode, base64_decode
|
|
247
|
+
// hashing : sha256, md5
|
|
248
|
+
// files : read_file, file_exists
|
|
249
|
+
// json : json_get, json_set
|
|
250
|
+
// error : try
|
|
247
251
|
// shell : shell_quote
|
|
248
252
|
// variables : define
|
|
253
|
+
// cwd : set_cwd
|
|
249
254
|
"echo deploying-{{ to_upper(ARGS.env) }}",
|
|
250
255
|
"curl -H \"X-Auth: {{ base64_encode(ENV.TOKEN) }}\" ...",
|
|
251
256
|
"echo {{ concat('hello-', ARGS.name, '-2026') }}",
|
|
@@ -269,6 +274,28 @@ and work as full substitution bodies *or* as chain segments:
|
|
|
269
274
|
// `shell_quote` picks the right quoting for the active shell:
|
|
270
275
|
"some-tool --json {{ shell_quote(base64_decode(ENV.SECRET_BASE64)) }}",
|
|
271
276
|
|
|
277
|
+
// Cache keys / content fingerprints. `sha256` for security-sensitive use,
|
|
278
|
+
// `md5` for cheap fingerprinting (NOT cryptographically secure).
|
|
279
|
+
"echo cache-key={{ sha256(read_file('package-lock.json')) }}",
|
|
280
|
+
|
|
281
|
+
// Branch on file presence without forking a shell. `file_exists` returns
|
|
282
|
+
// the literal "true" / "false", so it doubles as a DSL truthy value.
|
|
283
|
+
// "if": "{{ file_exists('.env.local') }}"
|
|
284
|
+
|
|
285
|
+
// Read a file inline — relative paths anchor to the Runfile directory.
|
|
286
|
+
// Use `try(...)` to recover from missing files.
|
|
287
|
+
"echo version={{ try(read_file('VERSION')) ? '0.0.0-dev' }}",
|
|
288
|
+
|
|
289
|
+
// Pull values out of arbitrary JSON without `jq` (works on every shell):
|
|
290
|
+
"echo db_host={{ json_get(read_file('config.json'), 'database.host') }}",
|
|
291
|
+
|
|
292
|
+
// Modify a JSON document in place (returns the new compact JSON):
|
|
293
|
+
"echo {{ json_set('{\"port\":3000}', 'env', 'production') }}",
|
|
294
|
+
|
|
295
|
+
// `try(expr)` swallows inner errors. Standalone returns "" on failure;
|
|
296
|
+
// chained, the next segment runs as a fallback.
|
|
297
|
+
"echo {{ try(base64_decode(ARGS.maybe_b64)) ? ARGS.maybe_b64 }}",
|
|
298
|
+
|
|
272
299
|
// Nested:
|
|
273
300
|
"echo {{ to_upper(to_lower(ARGS.x)) }}",
|
|
274
301
|
|
|
@@ -280,6 +307,13 @@ and work as full substitution bodies *or* as chain segments:
|
|
|
280
307
|
"{{ VARS.sdk }}/bin/build",
|
|
281
308
|
"echo using sdk at {{ VARS.sdk }}",
|
|
282
309
|
|
|
310
|
+
// `set_cwd(path)` switches the cwd subsequent commands spawn in — like
|
|
311
|
+
// shell `cd`, but works on every shell / OS without forking. Relative
|
|
312
|
+
// paths chain (matching `cd a; cd b` → `a/b`); absolute paths replace.
|
|
313
|
+
"{{ set_cwd(ARGS.subproject ? 'packages/api') }}",
|
|
314
|
+
"npm install",
|
|
315
|
+
"npm run build",
|
|
316
|
+
|
|
283
317
|
// Single-quoted strings interpolate nested {{ }} substitutions:
|
|
284
318
|
"{{ define(cmd, 'docker compose -f {{ VARS.compose }} pull') }}",
|
|
285
319
|
"{{ VARS.cmd }}"
|
|
@@ -291,6 +325,22 @@ to only whitespace (i.e. one consisting solely of a `{{ define(...) }}` call) is
|
|
|
291
325
|
being dispatched to the shell. `define`s in a parent target are visible to `@target` children. The `name` MUST
|
|
292
326
|
be a bareword identifier matching `[A-Za-z_][A-Za-z0-9_-]*` — quotes are NOT allowed on the name.
|
|
293
327
|
|
|
328
|
+
`set_cwd(path)` is the cwd analog of `define`: returns an empty string and changes the cwd that subsequent shell
|
|
329
|
+
commands in the *current target* spawn in. Behaves like shell `cd`, but works uniformly across every shell / OS
|
|
330
|
+
(no forked process, no `cd` binary needed). Resolution rules:
|
|
331
|
+
|
|
332
|
+
- **Absolute path** → fully replaces the current override (matches `cd /abs`).
|
|
333
|
+
- **Relative path** → joins onto the existing override, or onto the target's `workingDirectory` if no override
|
|
334
|
+
has been set yet. So `set_cwd('a'); set_cwd('b')` lands subsequent commands in `<workingDirectory>/a/b`,
|
|
335
|
+
matching how `cd a; cd b` chains in a shell.
|
|
336
|
+
|
|
337
|
+
`set_cwd` is **per-target**: each `@target` invocation starts with a clean override (the dispatched target sees
|
|
338
|
+
its own `workingDirectory`, not the caller's `set_cwd` state). Inside `parallel: true` targets each leaf
|
|
339
|
+
captures the override at the moment of its substitution, so siblings don't race on the spawn cwd. Like
|
|
340
|
+
`define`, the side effect is skipped on the redacted-logging pass so log lines don't double-apply it. With
|
|
341
|
+
`sameShell: true`, only the *final* `set_cwd` value applies (everything joins into one shell invocation) — use
|
|
342
|
+
shell `cd` directly between leaves there if you need intermediate cwd changes.
|
|
343
|
+
|
|
294
344
|
Function args are separated by `, ` (comma + exactly one space).
|
|
295
345
|
|
|
296
346
|
**Quote semantics inside `{{ ... }}`:**
|
|
@@ -319,13 +369,18 @@ evaluated by Runfile itself, so the logic works the same on every shell and plat
|
|
|
319
369
|
"cases": {
|
|
320
370
|
"1": "flutter emulators --launch Tier_1_Android_9",
|
|
321
371
|
"2": "flutter emulators --launch Tier_2_Android_11",
|
|
322
|
-
"3": "flutter emulators --launch Tier_3_Android_14"
|
|
372
|
+
"3": "flutter emulators --launch Tier_3_Android_14",
|
|
373
|
+
// Case keys wrapped in `/.../` are treated as regex patterns.
|
|
374
|
+
// Literal cases always win over a regex that would also match.
|
|
375
|
+
"/^v\\d+$/": "echo version-tag",
|
|
376
|
+
"/^pr-\\d+$/": "echo preview-build"
|
|
323
377
|
} }, // unknown values error out, listing the valid cases
|
|
324
378
|
|
|
325
379
|
{ "for": "service",
|
|
326
380
|
"in": ["api", "web", "worker"],
|
|
327
381
|
"parallel": true,
|
|
328
|
-
|
|
382
|
+
// VARS.<name>_index exposes the 0-based iteration counter.
|
|
383
|
+
"do": ["echo [{{ VARS.service_index }}] docker build -t {{ VARS.service }} services/{{ VARS.service }}"] },
|
|
329
384
|
|
|
330
385
|
{ "for": "file",
|
|
331
386
|
"shell": "git diff --name-only HEAD~1",
|
|
@@ -387,6 +442,27 @@ Wrap any commands in a `when:` block to run them only after a failure, or always
|
|
|
387
442
|
]
|
|
388
443
|
```
|
|
389
444
|
|
|
445
|
+
#### Single-shell mode with `sameShell`
|
|
446
|
+
|
|
447
|
+
By default each step runs in its own shell process, so `cd`, exported variables, and other shell state don't carry
|
|
448
|
+
over between steps. Set `"sameShell": true` to join every step into a single shell invocation — state changes
|
|
449
|
+
persist for free.
|
|
450
|
+
|
|
451
|
+
```jsonc
|
|
452
|
+
{
|
|
453
|
+
"deploy": {
|
|
454
|
+
"sameShell": true,
|
|
455
|
+
"commands": ["cd ci-scripts/", "./ci-deploy.sh"]
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
Steps are joined with `&&` so the run stops at the first failure (or `;` / `&` when `ignoreErrors: true`). `if`,
|
|
461
|
+
`for`, and `match` blocks are evaluated by Runfile and their chosen branches flow into the same joined invocation.
|
|
462
|
+
`@target` calls inside the body are rejected — they have their own shell context and can't share state with the
|
|
463
|
+
parent. `sameShell` also composes with `detach: true` (joined command spawns as one detached process) and is
|
|
464
|
+
available on `globals` for project-wide defaults.
|
|
465
|
+
|
|
390
466
|
#### Watch mode, built in
|
|
391
467
|
|
|
392
468
|
Add a `watch` array to any target. No flags, no extra tooling.
|
|
@@ -405,6 +481,11 @@ $ run :generate zed-tasks
|
|
|
405
481
|
$ run :generate jetbrains-run-configurations
|
|
406
482
|
```
|
|
407
483
|
|
|
484
|
+
Skip a target from the generated configs by setting `metadata.excludeFromGenerateCommand: true`. The
|
|
485
|
+
`metadata` block on `globals` and on each target is **fully open** — any property of any JSON type
|
|
486
|
+
(strings, numbers, booleans, arrays, deeply-nested objects) is accepted and round-trips untouched, so
|
|
487
|
+
editor extensions and CI scripts can stash arbitrary tooling-specific fields here.
|
|
488
|
+
|
|
408
489
|
#### Migrate in seconds
|
|
409
490
|
|
|
410
491
|
```bash
|
package/bin/darwin-x64/run
CHANGED
|
Binary file
|
|
@@ -111,16 +111,16 @@ Sorted from where Runfile is most differentiated to where it lags behind.
|
|
|
111
111
|
| Argument substitution with chained fallbacks | ✅ | ❌ | ✅ | ❌ |
|
|
112
112
|
| JSON Schema autocomplete in editors | ✅ | ❌ | ❌ | ✅ |
|
|
113
113
|
| Watch mode, built-in | ✅ | ❌ | ❌ | ✅ |
|
|
114
|
+
| Output grouping / prefixing in parallel mode | ✅ | ❌ | ❌ | ✅ |
|
|
114
115
|
| Native Windows (no WSL / msys2 needed) | ✅ | ❌ | ✅ | ✅ |
|
|
115
|
-
| Parallel execution | ✅ | ✅ | ❌ | ✅ |
|
|
116
116
|
| Shell completions | ✅ | ❌ | ✅ | ✅ |
|
|
117
117
|
| Private / internal targets (`_name`) | ✅ | ❌ | ✅ | ✅ |
|
|
118
|
+
| Built-in string functions (upper, replace, trim, regex, base64, …) | ✅ | ❌ | ✅ | ✅ |
|
|
119
|
+
| Parallel execution | ✅ | ✅ | ❌ | ✅ |
|
|
118
120
|
| Single static binary | ✅ | ✅ | ✅ | ✅ |
|
|
119
121
|
| Pattern rules (`%.o: %.c`) | ❌ | ✅ | ❌ | ❌ |
|
|
120
|
-
| Output grouping / prefixing in parallel mode | ❌ | ❌ | ❌ | ✅ |
|
|
121
122
|
| Preconditions / status checks | ❌ | ❌ | ❌ | ✅ |
|
|
122
123
|
| Incremental builds (sources / timestamps / checksums) | ❌ | ✅ | ❌ | ✅ |
|
|
123
|
-
| Built-in string functions (upper, replace, trim, …) | ❌ | ❌ | ✅ | ✅ |
|
|
124
124
|
|
|
125
125
|
---
|
|
126
126
|
|
|
@@ -244,8 +244,13 @@ and work as full substitution bodies *or* as chain segments:
|
|
|
244
244
|
// build : concat, join
|
|
245
245
|
// split : nth, first, last, count_parts
|
|
246
246
|
// encoding : base64_encode, base64_decode
|
|
247
|
+
// hashing : sha256, md5
|
|
248
|
+
// files : read_file, file_exists
|
|
249
|
+
// json : json_get, json_set
|
|
250
|
+
// error : try
|
|
247
251
|
// shell : shell_quote
|
|
248
252
|
// variables : define
|
|
253
|
+
// cwd : set_cwd
|
|
249
254
|
"echo deploying-{{ to_upper(ARGS.env) }}",
|
|
250
255
|
"curl -H \"X-Auth: {{ base64_encode(ENV.TOKEN) }}\" ...",
|
|
251
256
|
"echo {{ concat('hello-', ARGS.name, '-2026') }}",
|
|
@@ -269,6 +274,28 @@ and work as full substitution bodies *or* as chain segments:
|
|
|
269
274
|
// `shell_quote` picks the right quoting for the active shell:
|
|
270
275
|
"some-tool --json {{ shell_quote(base64_decode(ENV.SECRET_BASE64)) }}",
|
|
271
276
|
|
|
277
|
+
// Cache keys / content fingerprints. `sha256` for security-sensitive use,
|
|
278
|
+
// `md5` for cheap fingerprinting (NOT cryptographically secure).
|
|
279
|
+
"echo cache-key={{ sha256(read_file('package-lock.json')) }}",
|
|
280
|
+
|
|
281
|
+
// Branch on file presence without forking a shell. `file_exists` returns
|
|
282
|
+
// the literal "true" / "false", so it doubles as a DSL truthy value.
|
|
283
|
+
// "if": "{{ file_exists('.env.local') }}"
|
|
284
|
+
|
|
285
|
+
// Read a file inline — relative paths anchor to the Runfile directory.
|
|
286
|
+
// Use `try(...)` to recover from missing files.
|
|
287
|
+
"echo version={{ try(read_file('VERSION')) ? '0.0.0-dev' }}",
|
|
288
|
+
|
|
289
|
+
// Pull values out of arbitrary JSON without `jq` (works on every shell):
|
|
290
|
+
"echo db_host={{ json_get(read_file('config.json'), 'database.host') }}",
|
|
291
|
+
|
|
292
|
+
// Modify a JSON document in place (returns the new compact JSON):
|
|
293
|
+
"echo {{ json_set('{\"port\":3000}', 'env', 'production') }}",
|
|
294
|
+
|
|
295
|
+
// `try(expr)` swallows inner errors. Standalone returns "" on failure;
|
|
296
|
+
// chained, the next segment runs as a fallback.
|
|
297
|
+
"echo {{ try(base64_decode(ARGS.maybe_b64)) ? ARGS.maybe_b64 }}",
|
|
298
|
+
|
|
272
299
|
// Nested:
|
|
273
300
|
"echo {{ to_upper(to_lower(ARGS.x)) }}",
|
|
274
301
|
|
|
@@ -280,6 +307,13 @@ and work as full substitution bodies *or* as chain segments:
|
|
|
280
307
|
"{{ VARS.sdk }}/bin/build",
|
|
281
308
|
"echo using sdk at {{ VARS.sdk }}",
|
|
282
309
|
|
|
310
|
+
// `set_cwd(path)` switches the cwd subsequent commands spawn in — like
|
|
311
|
+
// shell `cd`, but works on every shell / OS without forking. Relative
|
|
312
|
+
// paths chain (matching `cd a; cd b` → `a/b`); absolute paths replace.
|
|
313
|
+
"{{ set_cwd(ARGS.subproject ? 'packages/api') }}",
|
|
314
|
+
"npm install",
|
|
315
|
+
"npm run build",
|
|
316
|
+
|
|
283
317
|
// Single-quoted strings interpolate nested {{ }} substitutions:
|
|
284
318
|
"{{ define(cmd, 'docker compose -f {{ VARS.compose }} pull') }}",
|
|
285
319
|
"{{ VARS.cmd }}"
|
|
@@ -291,6 +325,22 @@ to only whitespace (i.e. one consisting solely of a `{{ define(...) }}` call) is
|
|
|
291
325
|
being dispatched to the shell. `define`s in a parent target are visible to `@target` children. The `name` MUST
|
|
292
326
|
be a bareword identifier matching `[A-Za-z_][A-Za-z0-9_-]*` — quotes are NOT allowed on the name.
|
|
293
327
|
|
|
328
|
+
`set_cwd(path)` is the cwd analog of `define`: returns an empty string and changes the cwd that subsequent shell
|
|
329
|
+
commands in the *current target* spawn in. Behaves like shell `cd`, but works uniformly across every shell / OS
|
|
330
|
+
(no forked process, no `cd` binary needed). Resolution rules:
|
|
331
|
+
|
|
332
|
+
- **Absolute path** → fully replaces the current override (matches `cd /abs`).
|
|
333
|
+
- **Relative path** → joins onto the existing override, or onto the target's `workingDirectory` if no override
|
|
334
|
+
has been set yet. So `set_cwd('a'); set_cwd('b')` lands subsequent commands in `<workingDirectory>/a/b`,
|
|
335
|
+
matching how `cd a; cd b` chains in a shell.
|
|
336
|
+
|
|
337
|
+
`set_cwd` is **per-target**: each `@target` invocation starts with a clean override (the dispatched target sees
|
|
338
|
+
its own `workingDirectory`, not the caller's `set_cwd` state). Inside `parallel: true` targets each leaf
|
|
339
|
+
captures the override at the moment of its substitution, so siblings don't race on the spawn cwd. Like
|
|
340
|
+
`define`, the side effect is skipped on the redacted-logging pass so log lines don't double-apply it. With
|
|
341
|
+
`sameShell: true`, only the *final* `set_cwd` value applies (everything joins into one shell invocation) — use
|
|
342
|
+
shell `cd` directly between leaves there if you need intermediate cwd changes.
|
|
343
|
+
|
|
294
344
|
Function args are separated by `, ` (comma + exactly one space).
|
|
295
345
|
|
|
296
346
|
**Quote semantics inside `{{ ... }}`:**
|
|
@@ -319,13 +369,18 @@ evaluated by Runfile itself, so the logic works the same on every shell and plat
|
|
|
319
369
|
"cases": {
|
|
320
370
|
"1": "flutter emulators --launch Tier_1_Android_9",
|
|
321
371
|
"2": "flutter emulators --launch Tier_2_Android_11",
|
|
322
|
-
"3": "flutter emulators --launch Tier_3_Android_14"
|
|
372
|
+
"3": "flutter emulators --launch Tier_3_Android_14",
|
|
373
|
+
// Case keys wrapped in `/.../` are treated as regex patterns.
|
|
374
|
+
// Literal cases always win over a regex that would also match.
|
|
375
|
+
"/^v\\d+$/": "echo version-tag",
|
|
376
|
+
"/^pr-\\d+$/": "echo preview-build"
|
|
323
377
|
} }, // unknown values error out, listing the valid cases
|
|
324
378
|
|
|
325
379
|
{ "for": "service",
|
|
326
380
|
"in": ["api", "web", "worker"],
|
|
327
381
|
"parallel": true,
|
|
328
|
-
|
|
382
|
+
// VARS.<name>_index exposes the 0-based iteration counter.
|
|
383
|
+
"do": ["echo [{{ VARS.service_index }}] docker build -t {{ VARS.service }} services/{{ VARS.service }}"] },
|
|
329
384
|
|
|
330
385
|
{ "for": "file",
|
|
331
386
|
"shell": "git diff --name-only HEAD~1",
|
|
@@ -387,6 +442,27 @@ Wrap any commands in a `when:` block to run them only after a failure, or always
|
|
|
387
442
|
]
|
|
388
443
|
```
|
|
389
444
|
|
|
445
|
+
#### Single-shell mode with `sameShell`
|
|
446
|
+
|
|
447
|
+
By default each step runs in its own shell process, so `cd`, exported variables, and other shell state don't carry
|
|
448
|
+
over between steps. Set `"sameShell": true` to join every step into a single shell invocation — state changes
|
|
449
|
+
persist for free.
|
|
450
|
+
|
|
451
|
+
```jsonc
|
|
452
|
+
{
|
|
453
|
+
"deploy": {
|
|
454
|
+
"sameShell": true,
|
|
455
|
+
"commands": ["cd ci-scripts/", "./ci-deploy.sh"]
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
Steps are joined with `&&` so the run stops at the first failure (or `;` / `&` when `ignoreErrors: true`). `if`,
|
|
461
|
+
`for`, and `match` blocks are evaluated by Runfile and their chosen branches flow into the same joined invocation.
|
|
462
|
+
`@target` calls inside the body are rejected — they have their own shell context and can't share state with the
|
|
463
|
+
parent. `sameShell` also composes with `detach: true` (joined command spawns as one detached process) and is
|
|
464
|
+
available on `globals` for project-wide defaults.
|
|
465
|
+
|
|
390
466
|
#### Watch mode, built in
|
|
391
467
|
|
|
392
468
|
Add a `watch` array to any target. No flags, no extra tooling.
|
|
@@ -405,6 +481,11 @@ $ run :generate zed-tasks
|
|
|
405
481
|
$ run :generate jetbrains-run-configurations
|
|
406
482
|
```
|
|
407
483
|
|
|
484
|
+
Skip a target from the generated configs by setting `metadata.excludeFromGenerateCommand: true`. The
|
|
485
|
+
`metadata` block on `globals` and on each target is **fully open** — any property of any JSON type
|
|
486
|
+
(strings, numbers, booleans, arrays, deeply-nested objects) is accepted and round-trips untouched, so
|
|
487
|
+
editor extensions and CI scripts can stash arbitrary tooling-specific fields here.
|
|
488
|
+
|
|
408
489
|
#### Migrate in seconds
|
|
409
490
|
|
|
410
491
|
```bash
|
package/bin/linux-arm64/run
CHANGED
|
Binary file
|
package/bin/linux-x64/README.md
CHANGED
|
@@ -111,16 +111,16 @@ Sorted from where Runfile is most differentiated to where it lags behind.
|
|
|
111
111
|
| Argument substitution with chained fallbacks | ✅ | ❌ | ✅ | ❌ |
|
|
112
112
|
| JSON Schema autocomplete in editors | ✅ | ❌ | ❌ | ✅ |
|
|
113
113
|
| Watch mode, built-in | ✅ | ❌ | ❌ | ✅ |
|
|
114
|
+
| Output grouping / prefixing in parallel mode | ✅ | ❌ | ❌ | ✅ |
|
|
114
115
|
| Native Windows (no WSL / msys2 needed) | ✅ | ❌ | ✅ | ✅ |
|
|
115
|
-
| Parallel execution | ✅ | ✅ | ❌ | ✅ |
|
|
116
116
|
| Shell completions | ✅ | ❌ | ✅ | ✅ |
|
|
117
117
|
| Private / internal targets (`_name`) | ✅ | ❌ | ✅ | ✅ |
|
|
118
|
+
| Built-in string functions (upper, replace, trim, regex, base64, …) | ✅ | ❌ | ✅ | ✅ |
|
|
119
|
+
| Parallel execution | ✅ | ✅ | ❌ | ✅ |
|
|
118
120
|
| Single static binary | ✅ | ✅ | ✅ | ✅ |
|
|
119
121
|
| Pattern rules (`%.o: %.c`) | ❌ | ✅ | ❌ | ❌ |
|
|
120
|
-
| Output grouping / prefixing in parallel mode | ❌ | ❌ | ❌ | ✅ |
|
|
121
122
|
| Preconditions / status checks | ❌ | ❌ | ❌ | ✅ |
|
|
122
123
|
| Incremental builds (sources / timestamps / checksums) | ❌ | ✅ | ❌ | ✅ |
|
|
123
|
-
| Built-in string functions (upper, replace, trim, …) | ❌ | ❌ | ✅ | ✅ |
|
|
124
124
|
|
|
125
125
|
---
|
|
126
126
|
|
|
@@ -244,8 +244,13 @@ and work as full substitution bodies *or* as chain segments:
|
|
|
244
244
|
// build : concat, join
|
|
245
245
|
// split : nth, first, last, count_parts
|
|
246
246
|
// encoding : base64_encode, base64_decode
|
|
247
|
+
// hashing : sha256, md5
|
|
248
|
+
// files : read_file, file_exists
|
|
249
|
+
// json : json_get, json_set
|
|
250
|
+
// error : try
|
|
247
251
|
// shell : shell_quote
|
|
248
252
|
// variables : define
|
|
253
|
+
// cwd : set_cwd
|
|
249
254
|
"echo deploying-{{ to_upper(ARGS.env) }}",
|
|
250
255
|
"curl -H \"X-Auth: {{ base64_encode(ENV.TOKEN) }}\" ...",
|
|
251
256
|
"echo {{ concat('hello-', ARGS.name, '-2026') }}",
|
|
@@ -269,6 +274,28 @@ and work as full substitution bodies *or* as chain segments:
|
|
|
269
274
|
// `shell_quote` picks the right quoting for the active shell:
|
|
270
275
|
"some-tool --json {{ shell_quote(base64_decode(ENV.SECRET_BASE64)) }}",
|
|
271
276
|
|
|
277
|
+
// Cache keys / content fingerprints. `sha256` for security-sensitive use,
|
|
278
|
+
// `md5` for cheap fingerprinting (NOT cryptographically secure).
|
|
279
|
+
"echo cache-key={{ sha256(read_file('package-lock.json')) }}",
|
|
280
|
+
|
|
281
|
+
// Branch on file presence without forking a shell. `file_exists` returns
|
|
282
|
+
// the literal "true" / "false", so it doubles as a DSL truthy value.
|
|
283
|
+
// "if": "{{ file_exists('.env.local') }}"
|
|
284
|
+
|
|
285
|
+
// Read a file inline — relative paths anchor to the Runfile directory.
|
|
286
|
+
// Use `try(...)` to recover from missing files.
|
|
287
|
+
"echo version={{ try(read_file('VERSION')) ? '0.0.0-dev' }}",
|
|
288
|
+
|
|
289
|
+
// Pull values out of arbitrary JSON without `jq` (works on every shell):
|
|
290
|
+
"echo db_host={{ json_get(read_file('config.json'), 'database.host') }}",
|
|
291
|
+
|
|
292
|
+
// Modify a JSON document in place (returns the new compact JSON):
|
|
293
|
+
"echo {{ json_set('{\"port\":3000}', 'env', 'production') }}",
|
|
294
|
+
|
|
295
|
+
// `try(expr)` swallows inner errors. Standalone returns "" on failure;
|
|
296
|
+
// chained, the next segment runs as a fallback.
|
|
297
|
+
"echo {{ try(base64_decode(ARGS.maybe_b64)) ? ARGS.maybe_b64 }}",
|
|
298
|
+
|
|
272
299
|
// Nested:
|
|
273
300
|
"echo {{ to_upper(to_lower(ARGS.x)) }}",
|
|
274
301
|
|
|
@@ -280,6 +307,13 @@ and work as full substitution bodies *or* as chain segments:
|
|
|
280
307
|
"{{ VARS.sdk }}/bin/build",
|
|
281
308
|
"echo using sdk at {{ VARS.sdk }}",
|
|
282
309
|
|
|
310
|
+
// `set_cwd(path)` switches the cwd subsequent commands spawn in — like
|
|
311
|
+
// shell `cd`, but works on every shell / OS without forking. Relative
|
|
312
|
+
// paths chain (matching `cd a; cd b` → `a/b`); absolute paths replace.
|
|
313
|
+
"{{ set_cwd(ARGS.subproject ? 'packages/api') }}",
|
|
314
|
+
"npm install",
|
|
315
|
+
"npm run build",
|
|
316
|
+
|
|
283
317
|
// Single-quoted strings interpolate nested {{ }} substitutions:
|
|
284
318
|
"{{ define(cmd, 'docker compose -f {{ VARS.compose }} pull') }}",
|
|
285
319
|
"{{ VARS.cmd }}"
|
|
@@ -291,6 +325,22 @@ to only whitespace (i.e. one consisting solely of a `{{ define(...) }}` call) is
|
|
|
291
325
|
being dispatched to the shell. `define`s in a parent target are visible to `@target` children. The `name` MUST
|
|
292
326
|
be a bareword identifier matching `[A-Za-z_][A-Za-z0-9_-]*` — quotes are NOT allowed on the name.
|
|
293
327
|
|
|
328
|
+
`set_cwd(path)` is the cwd analog of `define`: returns an empty string and changes the cwd that subsequent shell
|
|
329
|
+
commands in the *current target* spawn in. Behaves like shell `cd`, but works uniformly across every shell / OS
|
|
330
|
+
(no forked process, no `cd` binary needed). Resolution rules:
|
|
331
|
+
|
|
332
|
+
- **Absolute path** → fully replaces the current override (matches `cd /abs`).
|
|
333
|
+
- **Relative path** → joins onto the existing override, or onto the target's `workingDirectory` if no override
|
|
334
|
+
has been set yet. So `set_cwd('a'); set_cwd('b')` lands subsequent commands in `<workingDirectory>/a/b`,
|
|
335
|
+
matching how `cd a; cd b` chains in a shell.
|
|
336
|
+
|
|
337
|
+
`set_cwd` is **per-target**: each `@target` invocation starts with a clean override (the dispatched target sees
|
|
338
|
+
its own `workingDirectory`, not the caller's `set_cwd` state). Inside `parallel: true` targets each leaf
|
|
339
|
+
captures the override at the moment of its substitution, so siblings don't race on the spawn cwd. Like
|
|
340
|
+
`define`, the side effect is skipped on the redacted-logging pass so log lines don't double-apply it. With
|
|
341
|
+
`sameShell: true`, only the *final* `set_cwd` value applies (everything joins into one shell invocation) — use
|
|
342
|
+
shell `cd` directly between leaves there if you need intermediate cwd changes.
|
|
343
|
+
|
|
294
344
|
Function args are separated by `, ` (comma + exactly one space).
|
|
295
345
|
|
|
296
346
|
**Quote semantics inside `{{ ... }}`:**
|
|
@@ -319,13 +369,18 @@ evaluated by Runfile itself, so the logic works the same on every shell and plat
|
|
|
319
369
|
"cases": {
|
|
320
370
|
"1": "flutter emulators --launch Tier_1_Android_9",
|
|
321
371
|
"2": "flutter emulators --launch Tier_2_Android_11",
|
|
322
|
-
"3": "flutter emulators --launch Tier_3_Android_14"
|
|
372
|
+
"3": "flutter emulators --launch Tier_3_Android_14",
|
|
373
|
+
// Case keys wrapped in `/.../` are treated as regex patterns.
|
|
374
|
+
// Literal cases always win over a regex that would also match.
|
|
375
|
+
"/^v\\d+$/": "echo version-tag",
|
|
376
|
+
"/^pr-\\d+$/": "echo preview-build"
|
|
323
377
|
} }, // unknown values error out, listing the valid cases
|
|
324
378
|
|
|
325
379
|
{ "for": "service",
|
|
326
380
|
"in": ["api", "web", "worker"],
|
|
327
381
|
"parallel": true,
|
|
328
|
-
|
|
382
|
+
// VARS.<name>_index exposes the 0-based iteration counter.
|
|
383
|
+
"do": ["echo [{{ VARS.service_index }}] docker build -t {{ VARS.service }} services/{{ VARS.service }}"] },
|
|
329
384
|
|
|
330
385
|
{ "for": "file",
|
|
331
386
|
"shell": "git diff --name-only HEAD~1",
|
|
@@ -387,6 +442,27 @@ Wrap any commands in a `when:` block to run them only after a failure, or always
|
|
|
387
442
|
]
|
|
388
443
|
```
|
|
389
444
|
|
|
445
|
+
#### Single-shell mode with `sameShell`
|
|
446
|
+
|
|
447
|
+
By default each step runs in its own shell process, so `cd`, exported variables, and other shell state don't carry
|
|
448
|
+
over between steps. Set `"sameShell": true` to join every step into a single shell invocation — state changes
|
|
449
|
+
persist for free.
|
|
450
|
+
|
|
451
|
+
```jsonc
|
|
452
|
+
{
|
|
453
|
+
"deploy": {
|
|
454
|
+
"sameShell": true,
|
|
455
|
+
"commands": ["cd ci-scripts/", "./ci-deploy.sh"]
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
Steps are joined with `&&` so the run stops at the first failure (or `;` / `&` when `ignoreErrors: true`). `if`,
|
|
461
|
+
`for`, and `match` blocks are evaluated by Runfile and their chosen branches flow into the same joined invocation.
|
|
462
|
+
`@target` calls inside the body are rejected — they have their own shell context and can't share state with the
|
|
463
|
+
parent. `sameShell` also composes with `detach: true` (joined command spawns as one detached process) and is
|
|
464
|
+
available on `globals` for project-wide defaults.
|
|
465
|
+
|
|
390
466
|
#### Watch mode, built in
|
|
391
467
|
|
|
392
468
|
Add a `watch` array to any target. No flags, no extra tooling.
|
|
@@ -405,6 +481,11 @@ $ run :generate zed-tasks
|
|
|
405
481
|
$ run :generate jetbrains-run-configurations
|
|
406
482
|
```
|
|
407
483
|
|
|
484
|
+
Skip a target from the generated configs by setting `metadata.excludeFromGenerateCommand: true`. The
|
|
485
|
+
`metadata` block on `globals` and on each target is **fully open** — any property of any JSON type
|
|
486
|
+
(strings, numbers, booleans, arrays, deeply-nested objects) is accepted and round-trips untouched, so
|
|
487
|
+
editor extensions and CI scripts can stash arbitrary tooling-specific fields here.
|
|
488
|
+
|
|
408
489
|
#### Migrate in seconds
|
|
409
490
|
|
|
410
491
|
```bash
|
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