UNPKG

11.6 kBMarkdownView Raw
1# Foy
2
3[![Build Status](https://travis-ci.org/zaaack/foy.svg?branch=master)](https://travis-ci.org/zaaack/foy) [![npm](https://img.shields.io/npm/v/foy.svg)](https://www.npmjs.com/package/foy) [![npm](https://img.shields.io/npm/dm/foy.svg)](https://www.npmjs.com/package/foy) [![install size](https://packagephobia.now.sh/badge?p=foy)](https://packagephobia.now.sh/result?p=foy)
4
5A simple, light-weight and modern task runner for general purpose.
6
7## Contents
8
9- [Foy](#foy)
10 - [Contents](#contents)
11 - [Features](#features)
12 - [Install](#install)
13 - [Write a Foyfile](#write-a-foyfile)
14 - [Using with built-in promised-based API](#using-with-built-in-promised-based-api)
15 - [Using with other packages](#using-with-other-packages)
16 - [Using dependencies](#using-dependencies)
17 - [Using namespaces](#using-namespaces)
18 - [Useful utils](#useful-utils)
19 - [fs](#fs)
20 - [logger](#logger)
21 - [exec command](#exec-command)
22 - [Using in CI servers](#using-in-ci-servers)
23 - [Using lifecycle hooks](#using-lifecycle-hooks)
24 - [run task in task](#run-task-in-task)
25 - [Watch and build](#watch-and-build)
26 - [Using with custom compiler](#using-with-custom-compiler)
27 - [API documentation](#api-documentation)
28 - [License](#license)
29
30## Features
31
32- Promise-based tasks and built-in utilities.
33- <a href="https://github.com/shelljs/shelljs" target="_blank">shelljs</a>-like commands
34- Easy to learn, stop spending hours for build tools.
35- Small install size
36 - foy: [![install size](https://packagephobia.now.sh/badge?p=foy)](https://packagephobia.now.sh/result?p=foy)
37 - gulp: [![install size](https://packagephobia.now.sh/badge?p=gulp)](https://packagephobia.now.sh/result?p=gulp)
38 - grunt: [![install size](https://packagephobia.now.sh/badge?p=grunt)](https://packagephobia.now.sh/result?p=grunt)
39
40![GIF](https://github.com/zaaack/foy/blob/master/docs/capture.gif?raw=true)
41
42## Install
43
44```sh
45yarn add -D foy # or npm i -D foy
46```
47
48Or install globally with
49
50```sh
51yarn add -g foy # or npm i -g foy
52```
53
54## Write a Foyfile
55
56You need to add a Foyfile.js(or Foyfile.ts with [ts-node](https://github.com/TypeStrong/ts-node) installed) to your project root.
57
58Also, you can simply generate a Foyfile.js via:
59
60```sh
61foy --init
62```
63
64which will create a simple `Foyfile.js` in the current folder:
65
66```js
67// Foyfile.js
68const { task } = require('foy')
69
70task('build', async ctx => {
71 await ctx.exec('tsc')
72})
73```
74
75You can also generate a `Foyfile.ts` via
76
77```sh
78foy --init ts
79```
80
81Then we can run `foy build` to execute the `build` task.
82
83```sh
84foy build
85```
86
87You can also add some options and a description to your tasks:
88
89```ts
90import { task, desc, option, strict } from 'foy'
91
92desc('Build ts files with tsc')
93option('-w, --watch', 'watch file changes')
94strict() // This will throw an error if you passed some options that doesn't defined via `option()`
95task('build', async ctx => {
96 await ctx.exec(`tsc ${ctx.options.watch ? '-w' : ''}`)
97})
98```
99
100```sh
101foy build -w
102```
103
104Warning! If you want to set flags like strict for all tasks, please use `setGlobalOptions`:
105
106```ts
107import { setGlobalOptions } from 'foy'
108
109setGlobalOptions({ strict: true }) // all tasks' options will be strict.
110
111option('-aa') // strict via default
112task('dev', async ctx => {
113
114})
115option('-bb') // strict via default
116task('build', async ctx => {
117
118})
119
120```
121
122## Using with built-in promised-based API
123
124```ts
125import { fs, task } from 'foy'
126
127task('some task', async ctx => {
128 await fs.rmrf('/some/dir/or/file') // Remove directory or file
129 await fs.copy('/src', '/dist') // Copy folder or file
130 let json = await fs.readJson('./xx.json')
131 await ctx
132 .env('NODE_ENV', 'production')
133 .cd('./src')
134 .exec('some command') // Execute an command
135 let { stdout } = await ctx.exec('ls', { stdio: 'pipe' }) // Get the stdout, default is empty because it's redirected to current process via `stdio: 'inherit'`.
136})
137```
138
139## Using with other packages
140
141```ts
142import { task, logger } from 'foy'
143import * as axios from 'axios'
144
145task('build', async ctx => {
146 let res = await axios.get('https://your.server/data.json')
147 logger.info(res.data)
148})
149```
150
151## Using dependencies
152
153
154```ts
155
156import { task } from 'foy'
157import * as axios from 'axios'
158
159task('test', async ctx => {
160 await ctx.exec('mocha')
161})
162
163task('build', async ctx => {
164 let res = await axios.get('https://your.server/data.json')
165 console.log(res.data)
166 await ctx.exec('build my awesome project')
167})
168task(
169 'publish:patch',
170 ['test', 'build'], // Run test and build before publish
171 async ctx => {
172 await ctx.exec('npm version patch')
173 await ctx.exec('npm publish')
174 }
175)
176```
177
178Dependencies run serially by default but you can specify when a task should be run concurrently.
179
180Example: Passing running options to dependencies:
181
182```ts
183task(
184 'publish:patch',
185 [{
186 name: 'test',
187 async: true, // run test parallelly
188 force: true, // force rerun test whether it has been executed before or not.
189 }, {
190 name: 'build',
191 async: true,
192 force: true,
193 },],
194 async ctx => {
195 await ctx.exec('npm version patch')
196 await ctx.exec('npm publish')
197 }
198)
199
200/* Sugar version */
201task(
202 'publish:patch',
203 [ 'test'.async().force(),
204 'build'.async().force() ],
205 async ctx => {
206 await ctx.exec('npm version patch')
207 await ctx.exec('npm publish')
208 }
209)
210
211/*
212Priority for async tasks
213
214Default is 0, higher values will be run earlier; so, in this next example, `build` will be run before `test`.
215(Note: If you have multiple async dependencies with same priority, they will be executed in parallel.)
216*/
217task(
218 'publish:patch',
219 [ 'test'.async(0).force(),
220 'build'.async(1).force() ],
221 async ctx => {
222 await ctx.exec('npm version patch')
223 await ctx.exec('npm publish')
224 }
225)
226```
227
228You can also pass options to dependencies:
229
230```ts
231task('task1', async ctx => {
232 console.log(ctx.options) // "{ forceRebuild: true, lazyOptions: 1 }"
233 console.log(ctx.global.options) // options from command line "{ a: 1 }"
234})
235
236
237task('task2', [{
238 name: 'task1',
239 options: {
240 forceRebuild: true,
241 },
242 // Some options that rely on ctx or asynchronization,
243 // it will be merged to options.
244 resolveOptions: async ctx => {
245 return { lazyOptions: 1 }
246 }
247}])
248
249// foy task2 -a 1
250```
251
252## Using namespaces
253
254To avoid name collisions, Foy provides namespaces to group tasks via the `namespace` function:
255
256```ts
257import { task, namespace } from 'foy'
258
259namespace('client', ns => {
260 before(() => {
261 logger.info('before')
262 })
263 after(() => {
264 logger.info('after')
265 })
266 onerror(() => {
267 logger.info('onerror')
268 })
269 task('start', async ctx => { /* ... */ }) // client:start
270 task('build', async ctx => { /* ... */ }) // client:build
271 task('watch', async ctx => { /* ... */ }) // client:watch
272 namespace('proj1', ns => { // nested namespace
273 onerror(() => {
274 logger.info('onerror', ns)
275 })
276 task('start', async ctx => { /* ... */ }) // client:proj1:start
277
278 })
279})
280
281namespace('server', ns => {
282 task('build', async ctx => { /* ... */ }) // server:build
283 task('start', async ctx => { /* ... */ }) // server:start
284 task('watch', async ctx => { /* ... */ }) // server:watch
285})
286
287task('start', ['client:start'.async(), 'server:start'.async()]) // start
288
289// foy start
290// foy client:build
291```
292
293
294## Useful utils
295
296### fs
297
298Foy wraps the NodeJS's `fs` (file system) module with a promise-based API, so you can easily use async/await patterns, if you prefer. Foy also implements some useful utility functions for build scripts not present in NodeJS's built-in modules.
299
300```ts
301import { fs } from 'foy'
302
303
304task('build', async ctx => {
305 let f = await fs.readFileSync('./assets/someFile')
306
307 // copy file or directory
308 await fs.copy('./fromPath', './toPath')
309
310 // watch a directory
311 await fs.watchDir('./src', (event, filename) => {
312 logger.info(event, filename)
313 })
314
315 // make directory with parent directories
316 await fs.mkdirp('./some/directory/with/parents/not/exists')
317
318 // write file will auto create missing parent directories
319 await fs.outputFile('./some/file/with/parents/not/exists', 'file data')
320
321 // write json file will auto create missing parent directories
322 await fs.outputJson('./some/file/with/parents/not/exists', {text: 'json data'})
323 let file = await fs.readJson('./some/jsonFile')
324
325 // iterate directory tree
326 await fs.iter('./src', async (path, stat) => {
327 if (stat.isDirectory()) {
328 logger.info('directory:', path)
329 // skip scan node_modules
330 if (path.endsWith('node_modules')) {
331 return true
332 }
333 } else if (stat.isFile()) {
334 logger.warn('file:', path)
335 }
336 })
337})
338```
339
340### logger
341
342Foy includes a light-weight built-in logger
343
344```ts
345import { logger } from 'foy'
346
347task('build', async ctx => {
348
349 logger.debug('debug', { aa: 1})
350 logger.info('info')
351 logger.warn('warn')
352 logger.error('error')
353
354})
355
356```
357
358### exec command
359
360A simple wrapper for sindresorhus's lovely module
361[execa](https://github.com/sindresorhus/execa)
362
363```ts
364import { logger } from 'foy'
365
366task('build', async ctx => {
367 await ctx.exec('tsc')
368
369 // run multiple commands synchronously
370 await ctx.exec([
371 'tsc --outDir ./lib',
372 'tsc --module es6 --outDir ./es',
373 ])
374
375 // run multiple commands concurrently
376 await Promise.all([
377 ctx.exec('eslint'),
378 ctx.exec('tsc'),
379 ctx.exec('typedoc'),
380 ])
381 // restart process when file changes
382 ctx.monitor('./src', 'node ./dist')
383 ctx.monitor('./src', ['rm -rf dist', 'tsc', 'node dist'])
384 ctx.monitor('./src', async () => {
385 await ctx.run('build:server')
386 await ctx.exec('node ./dist') // auth detect long-running process when using ctx.exec
387 })
388 ctx.monitor('./src', async (p) => {
389 // manually point out the process need to be killed when restart
390 p.current = require('child_process').exec('node dist')
391 })
392})
393
394
395```
396
397## Using in CI servers
398
399If you use Foy in CI servers, you won't want the [loading spinners](https://github.com/sindresorhus/cli-spinners) as most CI servers will log stdout and stderr in discreet frames not meant for continuous streaming animations. Luckily, Foy has already considered this! You can simply disable the loading animation like this:
400
401```ts
402import { task, setGlobalOptions } from 'foy'
403
404setGlobalOptions({ loading: false }) // disable loading animations
405
406task('test', async cyx => { /* ... */ })
407/*
408$ foy test
409DependencyGraph for task [test]:
410─ test
411
412Task: test
413...
414*/
415```
416
417## Using lifecycle hooks
418
419You can add lifecycle hooks via the `before`, `after`, and `onerror` functions.
420
421```ts
422import { before, after, onerror } from 'foy'
423before(() => { // do something before all tasks tree start
424 // ...
425})
426after(() => { // do something after all tasks tree finished
427 // ...
428})
429onerror((err) => { // do something when error happens
430 // ...
431})
432```
433
434## run task in task
435
436```ts
437
438task('task1', async ctx => { /* ... */ })
439task('task2', async ctx => {
440 // do things before task1
441
442 // run task1 manually, so we can
443 // do things before or after it
444 await ctx.run('task1')
445
446 // do things after task1
447})
448
449```
450
451## Watch and build
452
453```ts
454
455task('build', async ctx => { /* build your project */ })
456task('run', async ctx => { /* start your project */ })
457
458let p = null
459task('watch', async ctx => {
460 ctx.fs.watchDir('./src', async (evt, file) => {
461 await ctx.run('build')
462 p && !p.killed && p.kill()
463 p = await ctx.run('run')
464 })
465})
466```
467
468## Using with custom compiler
469
470```sh
471
472# Write Foyfile in ts, enabled by default
473foy -r ts-node/register -c ./some/Foyfile.ts build
474
475# Write Foyfile in coffee
476foy -r coffeescript/register -c ./some/Foyfile.coffee build
477
478```
479
480## API documentation
481
482<https://zaaack.github.io/foy/api>
483
484## License
485
486MIT