@fynjs/run 1.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/LICENSE +201 -0
- package/README.md +720 -0
- package/bin/qrun.js +3 -0
- package/bin/xrun.js +42 -0
- package/cli/check-global.js +6 -0
- package/cli/ck.js +6 -0
- package/cli/cli-options.js +98 -0
- package/cli/config.js +8 -0
- package/cli/env.js +33 -0
- package/cli/npm-loader.js +56 -0
- package/cli/parse-cmd-args.js +156 -0
- package/cli/provider-packages.js +73 -0
- package/cli/search-up-task-file.js +92 -0
- package/cli/task-file.js +192 -0
- package/cli/ts-runner.js +51 -0
- package/cli/usage.js +6 -0
- package/cli/wrap-process.js +27 -0
- package/cli/xrun-main.js +425 -0
- package/cli/xrun.js +8 -0
- package/lib/chalk.js +11 -0
- package/lib/cli-context.js +145 -0
- package/lib/defaults.js +32 -0
- package/lib/gen-xqid.js +10 -0
- package/lib/index.js +12 -0
- package/lib/logger.js +105 -0
- package/lib/ns-order.js +76 -0
- package/lib/print-tasks/index.js +105 -0
- package/lib/reporters/console.js +133 -0
- package/lib/stringify.js +16 -0
- package/lib/util/parse-array.js +5 -0
- package/lib/util/update-env.js +16 -0
- package/lib/xqitem.js +70 -0
- package/lib/xqtor.js +814 -0
- package/lib/xqtree.js +41 -0
- package/lib/xrun-instance.js +33 -0
- package/lib/xrun.d.ts +89 -0
- package/lib/xrun.js +344 -0
- package/lib/xtask-spec.js +63 -0
- package/lib/xtasks.js +137 -0
- package/package.json +93 -0
package/README.md
ADDED
|
@@ -0,0 +1,720 @@
|
|
|
1
|
+
[![NPM version][npm-image]][npm-url] [![Build Status][ci-shield]][ci-url]
|
|
2
|
+
|
|
3
|
+
# @fynjs/run
|
|
4
|
+
|
|
5
|
+
`npm run` enhanced - A powerful task runner and build tool for modern JavaScript projects.
|
|
6
|
+
|
|
7
|
+
- **Compatible** with `npm run` for [npm scripts]
|
|
8
|
+
- **Concurrent & Serial** execution of tasks
|
|
9
|
+
- **JavaScript extensibility** with functions and promises
|
|
10
|
+
- **Provider packages** for reusable task libraries
|
|
11
|
+
- **TypeScript support** - node strips types natively; tsx is picked up automatically for syntax it cannot strip
|
|
12
|
+
- **Advanced CLI** with argument parsing and remaining args support
|
|
13
|
+
- **Namespace organization** for better task management
|
|
14
|
+
- and [more](#full-list-of-features)
|
|
15
|
+
|
|
16
|
+
## Running [npm scripts]
|
|
17
|
+
|
|
18
|
+
This module provides a command `xrun` to run all your [npm scripts] in `package.json`.
|
|
19
|
+
|
|
20
|
+
And you can run multiple of them **concurrently** or **serially**.
|
|
21
|
+
|
|
22
|
+
Some examples below:
|
|
23
|
+
|
|
24
|
+
| what you want to do | npm command | `xrun` command |
|
|
25
|
+
| ----------------------------------- | -------------- | ------------------------- |
|
|
26
|
+
| run `test` | `npm run test` | `xrun test` |
|
|
27
|
+
| run `lint` and `test` concurrently | N/A | `xrun lint test` |
|
|
28
|
+
| run `lint` and then `test` serially | N/A | `xrun --serial lint test` |
|
|
29
|
+
|
|
30
|
+
Alias for the options:
|
|
31
|
+
|
|
32
|
+
- `-s`: `--serial`
|
|
33
|
+
|
|
34
|
+
## Running JavaScript tasks
|
|
35
|
+
|
|
36
|
+
You can write your tasks in JavaScript and run them with `xrun`.
|
|
37
|
+
|
|
38
|
+
> This is useful when a shell script is too long to fit in a JSON string, or when it's not easy to do something with shell script.
|
|
39
|
+
|
|
40
|
+
These APIs are provided: `concurrent`, `serial`, `exec`, `env`, and `load`.
|
|
41
|
+
|
|
42
|
+
Put your tasks in a file `xrun-tasks.js` and `xrun` will load it automatically.
|
|
43
|
+
|
|
44
|
+
An example `xrun-tasks.js`:
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
const { load, exec, concurrent, serial } = require("@fynjs/run");
|
|
48
|
+
load({
|
|
49
|
+
//
|
|
50
|
+
// define a task hello, with a string definition
|
|
51
|
+
// because a string is the task's direct value, it will be executed as a shell command.
|
|
52
|
+
//
|
|
53
|
+
hello: "echo hello",
|
|
54
|
+
//
|
|
55
|
+
// define a task world, using a JavaScript function to print something
|
|
56
|
+
//
|
|
57
|
+
world: () => console.log("world"),
|
|
58
|
+
//
|
|
59
|
+
// define a task serialTask, that will execute the three tasks serially, first two are
|
|
60
|
+
// the hello and world tasks defined above, and 3rd one is a shell command defined with exec.
|
|
61
|
+
// because the 3rd one is not a direct value of a task, it has to use exec to define a shell command.
|
|
62
|
+
//
|
|
63
|
+
serialTask: serial("hello", "world", exec("echo hi from exec")),
|
|
64
|
+
//
|
|
65
|
+
// define a task concurrentTask, that will execute the three tasks concurrently
|
|
66
|
+
//
|
|
67
|
+
concurrentTask: concurrent("hello", "world", exec("echo hi from exec")),
|
|
68
|
+
//
|
|
69
|
+
// define a task nesting, that does complex nesting of concurrent/serial constructs
|
|
70
|
+
//
|
|
71
|
+
nesting: concurrent(serial("hello", "world"), serial("serialTask", concurrent("hello", "world")))
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
To run the tasks defined above from the command prompt, below are some examples:
|
|
76
|
+
|
|
77
|
+
| what you want to do | command |
|
|
78
|
+
| ------------------------------------- | --------------------------- |
|
|
79
|
+
| run `hello` | `xrun hello` |
|
|
80
|
+
| run `hello` and `world` concurrently | `xrun hello world` |
|
|
81
|
+
| run `hello` and then `world` serially | `xrun --serial hello world` |
|
|
82
|
+
|
|
83
|
+
### `exec` and shell scripts
|
|
84
|
+
|
|
85
|
+
Use `exec` to invoke a shell command from JavaScript.
|
|
86
|
+
|
|
87
|
+
Here are some examples:
|
|
88
|
+
|
|
89
|
+
| shell script in JSON string | shell script using `exec` in JavaScript | note |
|
|
90
|
+
| --------------------------- | ------------------------------------------------ | ---------------------------- |
|
|
91
|
+
| `echo hello` | `exec("echo hello")` | |
|
|
92
|
+
| `FOO=bar echo hello $FOO` | `exec("FOO=bar echo hello $FOO")` | |
|
|
93
|
+
| `echo hello && echo world` | `exec("echo hello && echo world")` | |
|
|
94
|
+
| `echo hello && echo world` | `serial(exec("echo hello"), exec("echo world"))` | using serial instead of `&&` |
|
|
95
|
+
|
|
96
|
+
- `exec` supports `options` that can set a few things. Some examples below:
|
|
97
|
+
|
|
98
|
+
| what you want to do | shell script using `exec` in JavaScript |
|
|
99
|
+
| ------------------------------------- | ------------------------------------------------------------------ |
|
|
100
|
+
| setting an env variable | `exec("echo hello $FOO", {env: {FOO: "bar"}})` |
|
|
101
|
+
| provide tty to the shell process | `exec("echo hello", {flags: "tty"})` |
|
|
102
|
+
| using spawn with tty, and setting env | `exec("echo hello $FOO", {flags: "tty,spawn", env: {FOO: "bar"}})` |
|
|
103
|
+
|
|
104
|
+
### Function tasks
|
|
105
|
+
|
|
106
|
+
A task in JavaScript can be just a function.
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
load({
|
|
110
|
+
hello: () => console.log("hello")
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
A function task can do a few things:
|
|
115
|
+
|
|
116
|
+
- Return a promise or be an async function, and `xrun` will wait for the Promise.
|
|
117
|
+
- Return a stream and `xrun` will wait for the stream to end.
|
|
118
|
+
- Return another task for `xrun` to execute further.
|
|
119
|
+
- Access parsed options with `context.argOpts`.
|
|
120
|
+
|
|
121
|
+
Example:
|
|
122
|
+
|
|
123
|
+
```js
|
|
124
|
+
load({
|
|
125
|
+
// A function task named hello that access parsed options with `context.argOpts`
|
|
126
|
+
async hello(context) {
|
|
127
|
+
console.log("hello argOpts:", context.argOpts);
|
|
128
|
+
return ["foo"];
|
|
129
|
+
},
|
|
130
|
+
h2: ["hello world"],
|
|
131
|
+
foo: "echo bar"
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Running tasks with `concurrent` and `serial`
|
|
136
|
+
|
|
137
|
+
Use `concurrent` and `serial` to define a task that run multiple other tasks **concurrently** or **serially**.
|
|
138
|
+
|
|
139
|
+
Some examples:
|
|
140
|
+
|
|
141
|
+
- To do the same thing as the shell script `echo hello && echo world`:
|
|
142
|
+
|
|
143
|
+
```js
|
|
144
|
+
serial(exec("echo hello"), exec("echo world"));
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
- or concurrently:
|
|
148
|
+
|
|
149
|
+
```js
|
|
150
|
+
concurrent(exec("echo hello"), exec("echo world"));
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
- You can specify any valid tasks:
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
serial(
|
|
157
|
+
exec("echo hello"),
|
|
158
|
+
() => console.log("world"),
|
|
159
|
+
"name-of-a-task",
|
|
160
|
+
concurrent("task1", "task2")
|
|
161
|
+
);
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Tasks to set `process.env`
|
|
165
|
+
|
|
166
|
+
`env` allows you to create a task to set variables in `process.env`.
|
|
167
|
+
|
|
168
|
+
You use it by passing an object of env vars, like `env({VAR_NAME: "var-value"})`
|
|
169
|
+
|
|
170
|
+
Examples:
|
|
171
|
+
|
|
172
|
+
```js
|
|
173
|
+
load({
|
|
174
|
+
setEnv: serial(env({ FOO: "bar" }), () => console.log(process.env.FOO))
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### And to put it all together
|
|
179
|
+
|
|
180
|
+
A popular CI/CD use case is to start servers and then run tests, which can be achieved using `xrun` JavaScript tasks:
|
|
181
|
+
|
|
182
|
+
```js
|
|
183
|
+
const { concurrent, serial, load, stop } = require("@fynjs/run");
|
|
184
|
+
const waitOn = require("wait-on");
|
|
185
|
+
|
|
186
|
+
const waitUrl = url => waitOn({ resources: [url] });
|
|
187
|
+
|
|
188
|
+
load({
|
|
189
|
+
"start-server-and-test": concurrent(
|
|
190
|
+
// start the servers concurrently
|
|
191
|
+
concurrent("start-mock-server", "start-app-server"),
|
|
192
|
+
serial(
|
|
193
|
+
// wait for servers concurrently, and then run tests
|
|
194
|
+
concurrent("wait-mock-server", "wait-app-server"),
|
|
195
|
+
"run-tests",
|
|
196
|
+
// Finally stop servers and exit.
|
|
197
|
+
// This is only needed because there are long running servers.
|
|
198
|
+
() => stop()
|
|
199
|
+
)
|
|
200
|
+
),
|
|
201
|
+
"start-mock-server": "mock-server",
|
|
202
|
+
"start-app-server": "node lib/server",
|
|
203
|
+
"wait-mock-server": () => waitUrl("http://localhost:8000"),
|
|
204
|
+
"wait-app-server": () => waitUrl("http://localhost:3000"),
|
|
205
|
+
"run-tests": "cypress run --headless -b chrome"
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
> `xrun` adds `node_modules/.bin` to PATH. That's why `npx` is not needed to run commands like `cypress` that's installed in `node_modules`.
|
|
210
|
+
|
|
211
|
+
### Provider Packages
|
|
212
|
+
|
|
213
|
+
`@fynjs/run` supports **provider packages** - reusable task libraries that can be shared across projects. This allows teams to standardize common build tasks and workflows.
|
|
214
|
+
|
|
215
|
+
#### What makes a provider package?
|
|
216
|
+
|
|
217
|
+
A provider package is identified by either:
|
|
218
|
+
|
|
219
|
+
1. Having `xrunProvider` config in its `package.json`
|
|
220
|
+
2. Having `@fynjs/run` as a dependency
|
|
221
|
+
|
|
222
|
+
#### Creating a provider package
|
|
223
|
+
|
|
224
|
+
```js
|
|
225
|
+
// In your provider package's package.json
|
|
226
|
+
{
|
|
227
|
+
"name": "my-build-tasks",
|
|
228
|
+
"xrunProvider": {
|
|
229
|
+
"module": "tasks.js" // optional: specify which module exports loadTasks
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
```js
|
|
235
|
+
// In your provider's tasks.js (or main module)
|
|
236
|
+
module.exports = {
|
|
237
|
+
loadTasks(xrun) {
|
|
238
|
+
// can pass in optional namespace with xrun.load("namespace", {...})
|
|
239
|
+
return xrun.load({
|
|
240
|
+
build: "webpack --mode=production",
|
|
241
|
+
test: "jest",
|
|
242
|
+
lint: "eslint src/",
|
|
243
|
+
ci: ["lint", "test", "build"]
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
#### Using provider packages
|
|
250
|
+
|
|
251
|
+
Provider packages are automatically loaded when:
|
|
252
|
+
|
|
253
|
+
1. You have no tasks loaded (automatic discovery)
|
|
254
|
+
2. You explicitly enable them by setting `loadProviderModules: true` in your `@fynjs/run` config
|
|
255
|
+
|
|
256
|
+
Provider tasks are loaded from:
|
|
257
|
+
|
|
258
|
+
- `dependencies`
|
|
259
|
+
- `devDependencies`
|
|
260
|
+
- `optionalDependencies`
|
|
261
|
+
|
|
262
|
+
Example `package.json`:
|
|
263
|
+
|
|
264
|
+
```json
|
|
265
|
+
{
|
|
266
|
+
"name": "my-app",
|
|
267
|
+
"dependencies": {
|
|
268
|
+
"my-build-tasks": "^1.0.0"
|
|
269
|
+
},
|
|
270
|
+
"@fynjs/run": {
|
|
271
|
+
"loadProviderModules": true
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Now you can run provider tasks directly:
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
xrun build # runs the build task from my-build-tasks
|
|
280
|
+
xrun ci # runs the ci task which executes lint, test, build serially
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Shorthands
|
|
284
|
+
|
|
285
|
+
Not a fan of full API names like `concurrent`, `serial`, `exec`? You can skip them.
|
|
286
|
+
|
|
287
|
+
- `concurrent`: Any array of tasks are concurrent, except when they are specified at the top level.
|
|
288
|
+
- `exec`: Any string starting with `~$` are treated as shell script.
|
|
289
|
+
- `serial`: An array of tasks specified at the top level is executed serially.
|
|
290
|
+
|
|
291
|
+
Example:
|
|
292
|
+
|
|
293
|
+
```js
|
|
294
|
+
load({
|
|
295
|
+
executeSerially: ["task1", "task2"], // top level array serially
|
|
296
|
+
concurrentArray: [["task1", "task2"]], // Any other array (the one within) are concurrent
|
|
297
|
+
topLevelShell: "echo hello", // top level string is a shell script
|
|
298
|
+
shellScripts: [
|
|
299
|
+
"~$echo hello", // any string started with ~$ is shell script
|
|
300
|
+
"~(tty,spawn)$echo hello" // also possible to specify tty and spawn flag between ~ and $
|
|
301
|
+
]
|
|
302
|
+
});
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Full List of Features
|
|
306
|
+
|
|
307
|
+
- **Core Execution Engine**
|
|
308
|
+
|
|
309
|
+
- Serial and concurrent task execution with proper nesting hierarchy
|
|
310
|
+
- Promise, [node.js stream], or callback support for JavaScript tasks
|
|
311
|
+
- Run time flow control - return further tasks to execute from JS task functions
|
|
312
|
+
- Tasks can have a [_finally_](./REFERENCE.md#finally-hook) hook that always runs after task finish or fail
|
|
313
|
+
|
|
314
|
+
- **Developer Experience**
|
|
315
|
+
|
|
316
|
+
- Compatible with and loads npm scripts from `package.json`
|
|
317
|
+
- Auto completion for [bash] and [zsh]
|
|
318
|
+
- TypeScript support with automatic tsx/ts-node loading (tsx preferred)
|
|
319
|
+
- Advanced CLI with comprehensive options (see [CLI reference](./REFERENCE.md#cli-options))
|
|
320
|
+
- Argument parsing with `--` remaining args support
|
|
321
|
+
- Specify complex task execution patterns from command line
|
|
322
|
+
|
|
323
|
+
- **Extensibility & Organization**
|
|
324
|
+
|
|
325
|
+
- **Provider packages** - reusable task libraries for sharing common workflows
|
|
326
|
+
- **[Namespaces](./REFERENCE.md#namespace)** for organizing tasks across modules
|
|
327
|
+
- Define tasks in JavaScript files with full programmatic control
|
|
328
|
+
- Support [flexible function tasks](./REFERENCE.md#function) that can return more tasks to run
|
|
329
|
+
- Custom task execution reporters
|
|
330
|
+
|
|
331
|
+
- **Advanced Features**
|
|
332
|
+
- TTY control for interactive commands
|
|
333
|
+
- Environment variable management with `env()` tasks
|
|
334
|
+
- Shell command execution with `exec()` and spawn options
|
|
335
|
+
- Task dependency resolution and execution planning
|
|
336
|
+
|
|
337
|
+
## Getting Started
|
|
338
|
+
|
|
339
|
+
Still reading? Maybe you want to take it for a test drive?
|
|
340
|
+
|
|
341
|
+
## A Simple Example
|
|
342
|
+
|
|
343
|
+
Here is a simple sample.
|
|
344
|
+
|
|
345
|
+
1. First setup the directory and project:
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
mkdir xrun-test
|
|
349
|
+
cd xrun-test
|
|
350
|
+
npm init --yes
|
|
351
|
+
npm install rimraf @fynjs/run
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
2. Save the following code to `xrun-tasks.js`:
|
|
355
|
+
|
|
356
|
+
```js
|
|
357
|
+
"use strict";
|
|
358
|
+
const { load } = require("@fynjs/run");
|
|
359
|
+
|
|
360
|
+
const tasks = {
|
|
361
|
+
hello: "echo hello world", // a shell command to be exec'ed
|
|
362
|
+
jsFunc() {
|
|
363
|
+
console.log("JS hello world");
|
|
364
|
+
},
|
|
365
|
+
both: ["hello", "jsFunc"] // execute the two tasks serially
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
// Load the tasks into @fynjs/run
|
|
369
|
+
load(tasks);
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
3. And try one of these commands:
|
|
373
|
+
|
|
374
|
+
| what to do | command |
|
|
375
|
+
| ------------------------------------- | ---------------------------- |
|
|
376
|
+
| run the task `hello` | `xrun hello` |
|
|
377
|
+
| run the task `jsFunc` | `xrun jsFunc` |
|
|
378
|
+
| run the task `both` | `xrun both` |
|
|
379
|
+
| run `hello` and `jsFunc` concurrently | `xrun hello jsFunc` |
|
|
380
|
+
| run `hello` and `jsFunc` serially | `xrun --serial hello jsFunc` |
|
|
381
|
+
|
|
382
|
+
## A More Complex Example
|
|
383
|
+
|
|
384
|
+
Here is a more complex example to showcase a few more features:
|
|
385
|
+
|
|
386
|
+
```js
|
|
387
|
+
"use strict";
|
|
388
|
+
|
|
389
|
+
const util = require("util");
|
|
390
|
+
const { exec, concurrent, serial, env, load } = require("@fynjs/run");
|
|
391
|
+
const rimraf = util.promisify(require("rimraf"));
|
|
392
|
+
|
|
393
|
+
const tasks = {
|
|
394
|
+
hello: "echo hello world",
|
|
395
|
+
jsFunc() {
|
|
396
|
+
console.log("JS hello world");
|
|
397
|
+
},
|
|
398
|
+
both: {
|
|
399
|
+
desc: "invoke tasks hello and jsFunc in serial order",
|
|
400
|
+
// only array at top level like this is default to serial, other times
|
|
401
|
+
// they are default to concurrent, or they can be marked explicitly
|
|
402
|
+
// with the serial and concurrent APIs (below).
|
|
403
|
+
task: ["hello", "jsFunc"]
|
|
404
|
+
},
|
|
405
|
+
// invoke tasks hello and jsFunc concurrently as a simple concurrent array
|
|
406
|
+
both2: concurrent("hello", "jsFunc"),
|
|
407
|
+
shell: {
|
|
408
|
+
desc: "Run a shell command with TTY control and set an env",
|
|
409
|
+
task: exec({ cmd: "echo test", flags: "tty", env: { foo: "bar" } })
|
|
410
|
+
},
|
|
411
|
+
babel: exec("babel src -D lib"),
|
|
412
|
+
// serial array of two tasks, first one to set env, second to invoke the babel task.
|
|
413
|
+
compile: serial(env({ BABEL_ENV: "production" }), "babel"),
|
|
414
|
+
// more complex nesting serial/concurrent tasks.
|
|
415
|
+
build: {
|
|
416
|
+
desc: "Run production build",
|
|
417
|
+
task: serial(
|
|
418
|
+
() => rimraf("dist"), // cleanup, (returning a promise will be awaited)
|
|
419
|
+
env({ NODE_ENV: "production" }), // set env
|
|
420
|
+
concurrent("babel", exec("webpack")) // invoke babel task and run webpack concurrently
|
|
421
|
+
)
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
load(tasks);
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
## Install Globally
|
|
429
|
+
|
|
430
|
+
If you'd like to get the command `xrun` globally, you can install this module globally.
|
|
431
|
+
|
|
432
|
+
```bash
|
|
433
|
+
$ npm install -g @fynjs/run
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
However, it will still try to `require` and use the copy from your `node_modules` if you installed it.
|
|
437
|
+
|
|
438
|
+
## Load and Run Tasks Programmatically
|
|
439
|
+
|
|
440
|
+
If you don't want to use the CLI, you can load and invoke tasks in your JavaScript code using the `run` API.
|
|
441
|
+
|
|
442
|
+
Example:
|
|
443
|
+
|
|
444
|
+
```js
|
|
445
|
+
const { run, load, concurrent } = require("@fynjs/run");
|
|
446
|
+
const myTasks = require("./tools/tasks");
|
|
447
|
+
|
|
448
|
+
load(myTasks);
|
|
449
|
+
// assume task1 and task2 are defined, below will run them concurrently
|
|
450
|
+
run(concurrent("task1", "task2"), err => {
|
|
451
|
+
if (err) {
|
|
452
|
+
console.log("run tasks failed", err);
|
|
453
|
+
} else {
|
|
454
|
+
console.log("tasks completed");
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
> Promise version of `run` is `asyncRun`
|
|
460
|
+
|
|
461
|
+
## Task file formats
|
|
462
|
+
|
|
463
|
+
Your task file can be any of these, and `xrun` searches for them in this order:
|
|
464
|
+
|
|
465
|
+
| extension | module type |
|
|
466
|
+
| --- | --- |
|
|
467
|
+
| `xrun-tasks.js` | follows the nearest `package.json` `type` |
|
|
468
|
+
| `xrun-tasks.cjs` | CommonJS |
|
|
469
|
+
| `xrun-tasks.mjs` | ES module |
|
|
470
|
+
| `xrun-tasks.ts` | TypeScript, follows `type` |
|
|
471
|
+
| `xrun-tasks.mts` | TypeScript ES module |
|
|
472
|
+
| `xrun-tasks.cts` | TypeScript CommonJS |
|
|
473
|
+
|
|
474
|
+
A CommonJS task file exports with `module.exports`, an ES module with `export default`. Either can
|
|
475
|
+
export the tasks object directly, or a function that receives the `xrun` instance:
|
|
476
|
+
|
|
477
|
+
```js
|
|
478
|
+
export default xrun => {
|
|
479
|
+
xrun.load({ hello: "echo hello" });
|
|
480
|
+
};
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
### Top-level await
|
|
484
|
+
|
|
485
|
+
An ES module task file may use top-level await:
|
|
486
|
+
|
|
487
|
+
```js
|
|
488
|
+
const config = await loadConfigFromSomewhere();
|
|
489
|
+
|
|
490
|
+
export default xrun => {
|
|
491
|
+
xrun.load({ build: `build --target ${config.target}` });
|
|
492
|
+
};
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
Task files load through `import()`, which is what makes this possible - `require` refuses a module
|
|
496
|
+
graph containing top-level await and always will.
|
|
497
|
+
|
|
498
|
+
## TypeScript
|
|
499
|
+
|
|
500
|
+
Name your task file `xrun-tasks.ts`, `.mts`, or `.cts`.
|
|
501
|
+
|
|
502
|
+
**No TypeScript runtime is needed for ordinary task files.** Node strips type annotations itself,
|
|
503
|
+
unflagged since 22.18, so a task file with types just works.
|
|
504
|
+
|
|
505
|
+
Syntax that must be *transformed* rather than erased - enums, namespaces, parameter properties -
|
|
506
|
+
is beyond what stripping does. For those, install [tsx](https://www.npmjs.com/package/tsx):
|
|
507
|
+
|
|
508
|
+
```bash
|
|
509
|
+
npm install -D tsx typescript
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
`xrun` loads it automatically when it finds a TypeScript task file. Without it, node reports
|
|
513
|
+
`ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX` and `xrun` tells you which limit you hit. Running node with
|
|
514
|
+
`--experimental-transform-types` is the other way out.
|
|
515
|
+
|
|
516
|
+
## Command Line Usage
|
|
517
|
+
|
|
518
|
+
Any task can be invoked with the command `xrun`:
|
|
519
|
+
|
|
520
|
+
```bash
|
|
521
|
+
$ xrun task1 [task1 options] [<task2> ... <taskN>]
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
ie:
|
|
525
|
+
|
|
526
|
+
```bash
|
|
527
|
+
$ xrun build
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
### Passing Arguments to Tasks
|
|
531
|
+
|
|
532
|
+
You can pass arguments after `--` to shell commands. These arguments are automatically appended to the last shell task:
|
|
533
|
+
|
|
534
|
+
```bash
|
|
535
|
+
$ xrun build -- --watch --verbose
|
|
536
|
+
$ xrun test -- --grep "specific test"
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
For JavaScript function tasks, parsed options are available via the `context` param:
|
|
540
|
+
|
|
541
|
+
It's also pass as the `this` context for the function.
|
|
542
|
+
|
|
543
|
+
```js
|
|
544
|
+
load({
|
|
545
|
+
myTask(context) {
|
|
546
|
+
console.log("Parsed options:", context.argOpts);
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### CLI Options
|
|
552
|
+
|
|
553
|
+
Common CLI options include:
|
|
554
|
+
|
|
555
|
+
- `--serial`, `-s` - Execute tasks serially instead of concurrently
|
|
556
|
+
- `--cwd <path>`, `-w` - Set working directory
|
|
557
|
+
- `--list`, `-l` - List available tasks
|
|
558
|
+
- `--npm`, `-n` - Load npm scripts (default: true)
|
|
559
|
+
- `--quiet`, `-q` - Suppress output
|
|
560
|
+
- `--soe <mode>`, `-e` - Stop on error mode: `no`, `soft`, `full`
|
|
561
|
+
|
|
562
|
+
For complete CLI reference:
|
|
563
|
+
|
|
564
|
+
```bash
|
|
565
|
+
$ xrun -h
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
See [CLI Options](./REFERENCE.md#cli-options) for full details.
|
|
569
|
+
|
|
570
|
+
To load [npm scripts] into the `npm` namespace, use the `--npm` option:
|
|
571
|
+
|
|
572
|
+
This is enabled by default. To turn it off use `--no-npm` option.
|
|
573
|
+
|
|
574
|
+
```bash
|
|
575
|
+
$ xrun --npm test
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
You can also specify command line options under `@fynjs/run` in your `package.json`.
|
|
579
|
+
|
|
580
|
+
### Specifying Complex Tasks from command line
|
|
581
|
+
|
|
582
|
+
- You can specify your tasks as an array from the command line. For example, to have `xrun` execute the tasks `[task_a, task_b]` concurrently:
|
|
583
|
+
|
|
584
|
+
```bash
|
|
585
|
+
$ xrun [task_a, task_b]
|
|
586
|
+
$ xrun --concurrent [task_a, task_b]
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
- You can also execute them serially with:
|
|
590
|
+
|
|
591
|
+
```bash
|
|
592
|
+
$ xrun [--serial, task_a, task_b]
|
|
593
|
+
$ xrun --serial [task_a, task_b]
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
- You can execute tasks serially, and then an inner array with concurrent tasks. The following will execute `task_a`, then `task_b`, and finally `task_c1` and `task_c2` concurrently
|
|
597
|
+
|
|
598
|
+
```bash
|
|
599
|
+
$ xrun --serial [task_a, task_b, [task_c1, task_c2]]
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
- You can also make inner arrays serial using `--serial` as the first element. Other shortcuts for "--serial" are: `.` and `-s`.
|
|
603
|
+
|
|
604
|
+
```bash
|
|
605
|
+
$ xrun [task_a, task_b, [--serial, task_c1, task_c2]]
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
- You can pass the whole array in as a single string, which will be parsed as an array with string elements only.
|
|
609
|
+
|
|
610
|
+
```bash
|
|
611
|
+
$ xrun "[task_a, task_b, [task_c1, task_c2]]"
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
## Task Name
|
|
615
|
+
|
|
616
|
+
Task name is any alphanumeric string that does not contain `/`, or starts with `?` or `~$`.
|
|
617
|
+
|
|
618
|
+
Tasks can be invoked from command line:
|
|
619
|
+
|
|
620
|
+
- `xrun foo/task1` indicates to execute `task1` in namespace `foo`
|
|
621
|
+
- `xrun ?task1` or `xrun ?foo/task1` indicates that executing `task1` is optional.
|
|
622
|
+
|
|
623
|
+
`xrun` treats these characters as special:
|
|
624
|
+
|
|
625
|
+
- `/` as namespace separator
|
|
626
|
+
- prefix `?` to let you indicate that the execution of a task is optional so it won't fail if the task is not found.
|
|
627
|
+
- prefix `~$` to indicate the task to be a string as a shell command
|
|
628
|
+
|
|
629
|
+
## Optional Task Execution
|
|
630
|
+
|
|
631
|
+
By prefixing the task name with `?` when invoking, you can indicate the execution of a task as optional so it won't fail in case the task is not found.
|
|
632
|
+
|
|
633
|
+
For example:
|
|
634
|
+
|
|
635
|
+
- `xrun ?foo/task1` or `xrun ?task1` won't fail if `task1` is not found.
|
|
636
|
+
|
|
637
|
+
## Task Definition
|
|
638
|
+
|
|
639
|
+
A task can be `string`, `array`, `function`, or `object`. See [reference](./REFERENCE.md#task-definition) for details.
|
|
640
|
+
|
|
641
|
+
## package.json
|
|
642
|
+
|
|
643
|
+
You can define @fynjs/run tasks and options in your `package.json`.
|
|
644
|
+
|
|
645
|
+
## Tasks
|
|
646
|
+
|
|
647
|
+
You can also define **xrun** tasks without JavaScript capability in your `package.json`.
|
|
648
|
+
|
|
649
|
+
They will be loaded into a namespace `pkg`.
|
|
650
|
+
|
|
651
|
+
For example:
|
|
652
|
+
|
|
653
|
+
```js
|
|
654
|
+
{
|
|
655
|
+
"name": "my-app",
|
|
656
|
+
"@fynjs/run": {
|
|
657
|
+
"tasks": {
|
|
658
|
+
"task1": "echo hello from package.json",
|
|
659
|
+
"task2": "echo hello from package.json",
|
|
660
|
+
"foo": ["task1", "task2"]
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
And you can invoke them with `xrun pkg/foo`, or `xrun foo` if there are no other namespace with a task named `foo`.
|
|
667
|
+
|
|
668
|
+
## Options
|
|
669
|
+
|
|
670
|
+
Command line options can also be specified under `@fynjs/run` or `xrun` inside your `package.json`.
|
|
671
|
+
|
|
672
|
+
For example:
|
|
673
|
+
|
|
674
|
+
```js
|
|
675
|
+
{
|
|
676
|
+
"name": "my-app",
|
|
677
|
+
"@fynjs/run": {
|
|
678
|
+
"npm": true
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
## Async Tasks
|
|
684
|
+
|
|
685
|
+
You can provide a JS function for a task that executes asynchronously. Your function just need to take a callback or return a Promise or a [node.js stream].
|
|
686
|
+
|
|
687
|
+
ie:
|
|
688
|
+
|
|
689
|
+
```js
|
|
690
|
+
const tasks = {
|
|
691
|
+
cb_async: (cb) => {
|
|
692
|
+
setTimeout(cb, 10);
|
|
693
|
+
},
|
|
694
|
+
promise_async: () => {
|
|
695
|
+
return new Promise(resolve => {
|
|
696
|
+
setTimeout(resolve, 10);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
```
|
|
701
|
+
|
|
702
|
+
## Detailed Reference
|
|
703
|
+
|
|
704
|
+
See [reference](./REFERENCE.md) for more detailed information on features such as [load tasks into namespace], and setup [auto complete with namespace] for your shell.
|
|
705
|
+
|
|
706
|
+
## License
|
|
707
|
+
|
|
708
|
+
Licensed under the [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0)
|
|
709
|
+
|
|
710
|
+
[ci-shield]: https://github.com/jchip/fynjs/actions/workflows/ci.yml/badge.svg
|
|
711
|
+
[ci-url]: https://github.com/jchip/fynjs/actions/workflows/ci.yml
|
|
712
|
+
[npm-image]: https://badge.fury.io/js/%40fynjs%2Frun.svg
|
|
713
|
+
[npm-url]: https://npmjs.org/package/@fynjs/run
|
|
714
|
+
[npm scripts]: https://docs.npmjs.com/misc/scripts
|
|
715
|
+
[bash]: https://www.gnu.org/software/bash/
|
|
716
|
+
[zsh]: http://www.zsh.org/
|
|
717
|
+
[load tasks into namespace]: REFERENCE.md#loading-task
|
|
718
|
+
[auto complete with namespace]: REFERENCE.md#auto-complete-with-namespace
|
|
719
|
+
[npm]: https://www.npmjs.com/package/npm
|
|
720
|
+
[node.js stream]: https://nodejs.org/api/stream.html
|