A simple, light-weight and modern task runner for general purpose.
yarn add -D foy # or npm i -D foy
Or Install globally with
yarn add -g foy # or npm i -g foy
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 => {
})
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'`.
})
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)
})
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
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
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)
}
})
})
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')
})
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'),
])
})
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
...
*/
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
// ...
})
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
})
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')
})
})
# 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
https://zaaack.github.io/foy/api
MIT
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。