3 Star 2 Fork 1

zaaack / foy

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MIT

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) in your project root.

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

foy --init

Then it will put a simple Foyfile.js in 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 description to a single task:

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 to all tasks, please use setGlobalOptions, e.g.

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 are running serially by default, but you can specific them running concurrently.

e.g. Passing running options to dependencies.

task(
  'publish:patch',
  [{
    name: 'test',
    async: true, // run test parallelly
    force: true, // force rerun test whether it is 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, bigger is formmer, then we will run `build` before `test`.
If you have multi async dependencies with same priority, they will be executed 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 dependences:

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

If you have lots of tasks, naming might be a problem, what foy do is to making life easier, but more anxious. So we provide a namespace function to group tasks with namespaces.

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 wrap fs module with promises, so we can use it in async/await smoothly. Foy also implements some useful functions for build scripts which missing in nodejs 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

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' 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'),
  ])
})

Using in CI servers

If you use Foy in CI servers, you might won't want the loading because most CI servers will log the stdout and stderr to files, the loading will be logged as frames. Luckily, Foy has already considered this use case, you can simple disable the loading behavior 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 use before/after/onerror to do something in lifecycles.

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

API documentation

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

License

MIT

MIT License Copyright (c) 2017 zaaack Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

福伊(foy) 是一个轻量级的基于 nodejs 和 promise 的通用 build 工具 展开 收起
TypeScript 等 3 种语言
MIT
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
TypeScript
1
https://gitee.com/zane_young/foy.git
git@gitee.com:zane_young/foy.git
zane_young
foy
foy
master

搜索帮助