• Stars
    star
    257
  • Rank 152,990 (Top 4 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created over 5 years ago
  • Updated 4 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A simple, light-weight, type-friendly and modern task runner for general purpose.

Foy

Build Status npm npm install size

A simple, light-weight and modern task runner for general purpose.

Contents

Features

  • Promise-based tasks and built-in utilities.
  • shelljs-like commands
  • Easy to learn, stop spending hours for build tools.
  • Small install size
    • foy: install size
    • gulp: install size
    • grunt: install size

GIF

Install

yarn add -D foy # or npm i -D foy

Or install globally with

yarn add -g foy # or npm i -g foy

Write a Foyfile

You need to add a Foyfile.js(or Foyfile.ts with ts-node installed) to your project root.

Also, you can simply generate a Foyfile.js via:

foy --init

which will create a simple Foyfile.js in the current folder:

// Foyfile.js
const { task } = require('foy')

task('build', async ctx => {
  await ctx.exec('tsc')
})

You can also generate a Foyfile.ts via

foy --init ts

Then we can run foy build to execute the build task.

foy build

You can also add some options and a description to your tasks:

import { task, desc, option, strict } from 'foy'

desc('Build ts files with tsc')
option('-w, --watch', 'watch file changes')
strict() // This will throw an error if you passed some options that doesn't defined via `option()`
task('build', async ctx => {
  await ctx.exec(`tsc ${ctx.options.watch ? '-w' : ''}`)
})
foy build -w

Warning! If you want to set flags like strict for all tasks, please use setGlobalOptions:

import { setGlobalOptions } from 'foy'

setGlobalOptions({ strict: true }) // all tasks' options will be strict.

option('-aa') // strict via default
task('dev', async ctx => {

})
option('-bb') // strict via default
task('build', async ctx => {

})

Using with built-in promised-based API

import { fs, task } from 'foy'

task('some task', async ctx => {
  await fs.rmrf('/some/dir/or/file') // Remove directory or file
  await fs.copy('/src', '/dist') // Copy folder or file
  let json = await fs.readJson('./xx.json')
  await ctx
    .env('NODE_ENV', 'production')
    .cd('./src')
    .exec('some command') // Execute an command
  let { stdout } = await ctx.exec('ls', { stdio: 'pipe' }) // Get the stdout, default is empty because it's redirected to current process via `stdio: 'inherit'`.
})

Using with other packages

import { task, logger } from 'foy'
import * as axios from 'axios'

task('build', async ctx => {
  let res = await axios.get('https://your.server/data.json')
  logger.info(res.data)
})

Using dependencies

import { task } from 'foy'
import * as axios from 'axios'

task('test', async ctx => {
  await ctx.exec('mocha')
})

task('build', async ctx => {
  let res = await axios.get('https://your.server/data.json')
  console.log(res.data)
  await ctx.exec('build my awesome project')
})
task(
  'publish:patch',
  ['test', 'build'], // Run test and build before publish
  async ctx => {
    await ctx.exec('npm version patch')
    await ctx.exec('npm publish')
  }
)

Dependencies run serially by default but you can specify when a task should be run concurrently.

Example: Passing running options to dependencies:

task(
  'publish:patch',
  [{
    name: 'test',
    async: true, // run test parallelly
    force: true, // force rerun test whether it has been executed before or not.
  }, {
    name: 'build',
    async: true,
    force: true,
  },],
  async ctx => {
    await ctx.exec('npm version patch')
    await ctx.exec('npm publish')
  }
)

/* Sugar version */
task(
  'publish:patch',
  [ 'test'.async().force(),
    'build'.async().force() ],
  async ctx => {
    await ctx.exec('npm version patch')
    await ctx.exec('npm publish')
  }
)

/*
Priority for async tasks

Default is 0, higher values will be run earlier; so, in this next example, `build` will be run before `test`.
(Note: If you have multiple async dependencies with same priority, they will be executed in parallel.)
*/
task(
  'publish:patch',
  [ 'test'.async(0).force(),
    'build'.async(1).force() ],
  async ctx => {
    await ctx.exec('npm version patch')
    await ctx.exec('npm publish')
  }
)

You can also pass options to dependencies:

task('task1', async ctx => {
  console.log(ctx.options) // "{ forceRebuild: true, lazyOptions: 1 }"
  console.log(ctx.global.options) // options from command line "{ a: 1 }"
})


task('task2', [{
  name: 'task1',
  options: {
    forceRebuild: true,
  },
  // Some options that rely on ctx or asynchronization,
  // it will be merged to options.
  resolveOptions: async ctx => {
    return { lazyOptions: 1 }
  }
}])

// foy task2 -a 1

Using namespaces

To avoid name collisions, Foy provides namespaces to group tasks via the namespace function:

import { task, namespace } from 'foy'

namespace('client', ns => {
  before(() => {
    logger.info('before')
  })
  after(() => {
    logger.info('after')
  })
  onerror(() => {
    logger.info('onerror')
  })
  task('start', async ctx => { /* ... */ }) // client:start
  task('build', async ctx => { /* ... */ }) // client:build
  task('watch', async ctx => { /* ... */ }) // client:watch
  namespace('proj1', ns => { // nested namespace
    onerror(() => {
      logger.info('onerror', ns)
    })
    task('start', async ctx => { /* ... */ }) // client:proj1:start

  })
})

namespace('server', ns => {
  task('build', async ctx => { /* ... */ }) // server:build
  task('start', async ctx => { /* ... */ }) // server:start
  task('watch', async ctx => { /* ... */ }) // server:watch
})

task('start', ['client:start'.async(), 'server:start'.async()]) // start

// foy start
// foy client:build

Useful utils

fs

Foy 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.

import { fs } from 'foy'


task('build', async ctx => {
  let f = await fs.readFileSync('./assets/someFile')

  // copy file or directory
  await fs.copy('./fromPath', './toPath')

  // watch a directory
  await fs.watchDir('./src', (event, filename) => {
    logger.info(event, filename)
  })

  // make directory with parent directories
  await fs.mkdirp('./some/directory/with/parents/not/exists')

  // write file will auto create missing parent directories
  await fs.outputFile('./some/file/with/parents/not/exists', 'file data')

  // write json file will auto create missing parent directories
  await fs.outputJson('./some/file/with/parents/not/exists', {text: 'json data'})
  let file = await fs.readJson('./some/jsonFile')

  // iterate directory tree
  await fs.iter('./src', async (path, stat) => {
    if (stat.isDirectory()) {
      logger.info('directory:', path)
      // skip scan node_modules
      if (path.endsWith('node_modules')) {
        return true
      }
    } else if (stat.isFile()) {
      logger.warn('file:', path)
    }
  })
})

logger

Foy includes a light-weight built-in logger

import { logger } from 'foy'

task('build', async ctx => {

  logger.debug('debug', { aa: 1})
  logger.info('info')
  logger.warn('warn')
  logger.error('error')

})

exec command

A simple wrapper for sindresorhus's lovely module execa

import { logger } from 'foy'

task('build', async ctx => {
  await ctx.exec('tsc')

  // run multiple commands synchronously
  await ctx.exec([
    'tsc --outDir ./lib',
    'tsc --module es6 --outDir ./es',
  ])

  // run multiple commands concurrently
  await Promise.all([
    ctx.exec('eslint'),
    ctx.exec('tsc'),
    ctx.exec('typedoc'),
  ])
  // restart process when file changes
  ctx.monitor('./src', 'node ./dist')
  ctx.monitor('./src', ['rm -rf dist', 'tsc', 'node dist'])
  ctx.monitor('./src', async () => {
    await ctx.run('build:server')
    await ctx.exec('node ./dist') // auth detect long-running process when using ctx.exec
  })
  ctx.monitor('./src', async (p) => {
    // manually point out the process need to be killed when restart
    p.current = require('child_process').exec('node dist')
  })
})

Using in CI servers

If you use Foy in CI servers, you won't want the loading 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:

import { task, setGlobalOptions } from 'foy'

setGlobalOptions({ loading: false }) // disable loading animations

task('test', async cyx => { /* ... */ })
/*
$ foy test
DependencyGraph for task [test]:
─ test

Task: test
...
*/

Using lifecycle hooks

You can add lifecycle hooks via the before, after, and onerror functions.

import { before, after, onerror } from 'foy'
before(() => { // do something before all tasks tree start
  // ...
})
after(() => { // do something after all tasks tree finished
  // ...
})
onerror((err) => { // do something when error happens
  // ...
})

run task in task

task('task1', async ctx => { /* ... */ })
task('task2', async ctx => {
  // do things before task1

  // run task1 manually, so we can
  // do things before or after it
  await ctx.run('task1')

  // do things after task1
})

Watch and build

task('build', async ctx => { /* build your project */ })
task('run', async ctx => { /* start your project */ })

let p = null
task('watch', async ctx => {
  ctx.fs.watchDir('./src', async (evt, file) => {
    await ctx.run('build')
    p && !p.killed && p.kill()
    p = await ctx.run('run')
  })
})

Using with custom compiler

# Write Foyfile in ts, enabled by default
foy -r ts-node/register -c ./some/Foyfile.ts build

# Write Foyfile in coffee
foy -r coffeescript/register -c ./some/Foyfile.coffee build

zsh/bash auto completion (New!!!)

Add foy auto completion in zsh/bash:

# for bash
foy --completion-profile >> ~/.bashrc

# for zsh
foy --completion-profile >> ~/.zshrc

API documentation

https://zaaack.github.io/foy/api

License

MIT

More Repositories

1

vscode-markdown-editor

A vscode extension to make your vscode become a full-featured WYSIWYG markdown editor
TypeScript
337
star
2

ELaunch

A launcher based on Electron and inspired by UAfred,now only support linux and macos
JavaScript
233
star
3

node-systray

A cross-platform systray library for nodejs.
TypeScript
82
star
4

koa-joi-swagger

An opinionated koa validation & swagger library, letting you write one Joi schema for both validation & generating swagger ui.
JavaScript
74
star
5

keyv-file

File storage adapter for Keyv, using json to serialize data.
TypeScript
66
star
6

inker

Measure & copy CSS from your designs of Sketch, Gravit Designer, Adobe XD, Vectr, etc. Pro version: https://github.com/inker8/
TypeScript
58
star
7

fable-validation

An isomorphic validation library for Fable/F#, inspired by elm-validate
F#
49
star
8

htmls-webpack-plugin

A simple, flexible and fast html webpack plugin support multiple htmls
TypeScript
20
star
9

koa-dec-router

An ES6 decorator + class based router, support inherit, override, priority, auto load controllers, etc.
JavaScript
19
star
10

nstate

A simple but powerful react state management library with low mind burden
TypeScript
14
star
11

systray-portable

A portable version of go systray, using stdin/stdout to communicate with other language
Go
14
star
12

mongo-tx

A flexible & extensible mongodb transaction library for nodejs.
JavaScript
11
star
13

aria2c-gui

An aria2 gui by systray + web, written in Rust.
JavaScript
9
star
14

immuter

**DEPRECATED** for https://github.com/hydux/hydux-mutator
JavaScript
9
star
15

android_treeview

an android treeview widget,it's not a extension of listview,it extends a linearlayout and you can put any view in it with any event listener
8
star
16

debug-rs

A debug crate for rust inspired by nodejs debug module.
Rust
6
star
17

react-starter

My react-starter 2017
JavaScript
4
star
18

aria2c-node-gui

An aria2c gui by node-systray
JavaScript
3
star
19

ebook_spider

支持微信公众号的爬虫
TypeScript
3
star
20

FablePlus

F#
2
star
21

elmish-react-demo

A playground project
JavaScript
2
star
22

minify-cssinjs-loader

A light-weight and fast css minifier for css-in-js
TypeScript
2
star
23

position-caculator

https://zaaack.github.io/position-caculator
TypeScript
2
star
24

statical-ghost

another static blog generator using ghost theme
JavaScript
2
star
25

rust-reason-graphql-todo

An example project by rust & reasonml
Rust
1
star
26

svg-measure

This repository has been moved to https://github.com/zaaack/inker
1
star
27

reason-github-trending

A github trending app by ReasonML
OCaml
1
star
28

forex-alert

TypeScript
1
star
29

fable-office-ui-fabric-react

Fable binding for office-ui-fabric-react
F#
1
star
30

chrome-alarm

TypeScript
1
star
31

tsx2wxml

typescript tsx 转 微信小程序 wxml
TypeScript
1
star
32

vscode-fsharp-format-provider

FSharp format provider for vscode using fantomas.
F#
1
star
33

jquery-treeview-addon

An addon of jzaefferer / jquery-treeview. now support checkbox and radio in html and create dynamic tree by using jsonArray. it's not perfect, but everyone can perfect it.
JavaScript
1
star
34

word2html

word 文档导出html, fork from https://www.52pojie.cn/thread-1202100-1-1.html
JavaScript
1
star