Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
/foyPublic

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

License

NotificationsYou must be signed in to change notification settings

zaaack/foy

Repository files navigation

publishnpmnpminstall size

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

Contents

Features

  • Promise-based tasks and built-in utilities.
  • <a href="https://github.com/shelljs/shelljs">shelljs</a>-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 withtsx or@swc-node/register orts-node installed) to your project root.

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

foy --init

which will create a simpleFoyfile.js in the current folder:

// Foyfile.jsconst{ task}=require('foy')task('build',asyncctx=>{awaitctx.exec('tsc')})

You can also generate aFoyfile.ts via

foy --init ts

Then we can runfoy build to execute thebuild 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',asyncctx=>{awaitctx.exec(`tsc${ctx.options.watch ?'-w' :''}`)})

And, if using TypeScript, add types to your options through thetask generic:

import{task,desc,option,strict}from'foy'typeBuildOptions={watch:boolean}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<BuildOptions>('build',asyncctx=>{// ctx.options now has type BuildOptions instead of unknownawaitctx.exec(`tsc${ctx.options.watch ?'-w' :''}`)})
foy build -w

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

import{setGlobalOptions}from'foy'setGlobalOptions({strict:true})// all tasks' options will be strict.option('-aa')// strict via defaulttask('dev',asyncctx=>{})option('-bb')// strict via defaulttask('build',asyncctx=>{})

Using with built-in promised-based API

import{fs,task}from'foy'task('some task',asyncctx=>{awaitfs.rmrf('/some/dir/or/file')// Remove directory or fileawaitfs.copy('/src','/dist')// Copy folder or fileletjson=awaitfs.readJson('./xx.json')awaitctx.env('NODE_ENV','production').env('NODE_ENV=production').cd('./src').exec('some command')// Execute an commandlet{ stdout}=awaitctx.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*asaxiosfrom'axios'task('build',asyncctx=>{letres=awaitaxios.get('https://your.server/data.json')logger.info(res.data)})

Using dependencies

import{task}from'foy'import*asaxiosfrom'axios'task('test',asyncctx=>{awaitctx.exec('mocha')})task('build',asyncctx=>{letres=awaitaxios.get('https://your.server/data.json')console.log(res.data)awaitctx.exec('build my awesome project')})task('publish:patch',['test','build'],// Run test and build before publishasyncctx=>{awaitctx.exec('npm version patch')awaitctx.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 parallellyforce:true,// force rerun test whether it has been executed before or not.},{name:'build',async:true,force:true,},],asyncctx=>{awaitctx.exec('npm version patch')awaitctx.exec('npm publish')})/* Sugar version */task('publish:patch',['test'.async().force(),'build'.async().force()],asyncctx=>{awaitctx.exec('npm version patch')awaitctx.exec('npm publish')})/*Priority for async tasksDefault 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()],asyncctx=>{awaitctx.exec('npm version patch')awaitctx.exec('npm publish')})

You can also pass options to dependencies:

task('task1',asyncctx=>{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:asyncctx=>{return{lazyOptions:1}}}])// foy task2 -a 1

Using namespaces

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

import{task,namespace}from'foy'namespace('client',ns=>{before(()=>{logger.info('before')})after(()=>{logger.info('after')})onerror(()=>{logger.info('onerror')})task('start',asyncctx=>{/* ... */})// client:starttask('build',asyncctx=>{/* ... */})// client:buildtask('watch',asyncctx=>{/* ... */})// client:watchnamespace('proj1',ns=>{// nested namespaceonerror(()=>{logger.info('onerror',ns)})task('start',asyncctx=>{/* ... */})// client:proj1:start})})namespace('server',ns=>{task('build',asyncctx=>{/* ... */})// server:buildtask('start',asyncctx=>{/* ... */})// server:starttask('watch',asyncctx=>{/* ... */})// server:watch})task('start',['client:start'.async(),'server:start'.async()])// start// foy start// foy client:build

Useful utils

fs

Foy wraps the NodeJS'sfs (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',asyncctx=>{letf=awaitfs.readFileSync('./assets/someFile')// copy file or directoryawaitfs.copy('./fromPath','./toPath')// watch a directoryawaitfs.watchDir('./src',(event,filename)=>{logger.info(event,filename)})// make directory with parent directoriesawaitfs.mkdirp('./some/directory/with/parents/not/exists')// write file will auto create missing parent directoriesawaitfs.outputFile('./some/file/with/parents/not/exists','file data')// write json file will auto create missing parent directoriesawaitfs.outputJson('./some/file/with/parents/not/exists',{text:'json data'})letfile=awaitfs.readJson('./some/jsonFile')// iterate directory treeawaitfs.iter('./src',async(path,stat)=>{if(stat.isDirectory()){logger.info('directory:',path)// skip scan node_modulesif(path.endsWith('node_modules')){returntrue}}elseif(stat.isFile()){logger.warn('file:',path)}})})

logger

Foy includes a light-weight built-in logger

import{logger}from'foy'task('build',asyncctx=>{logger.debug('debug',{aa:1})logger.info('info')logger.warn('warn')logger.error('error')})

exec command

A simple wrapper for sindresorhus's lovely moduleexeca

import{logger}from'foy'task('build',asyncctx=>{awaitctx.exec('tsc')// run multiple commands synchronouslyawaitctx.exec(['tsc --outDir ./lib','tsc --module es6 --outDir ./es',])// run multiple commands concurrentlyawaitPromise.all([ctx.exec('eslint'),ctx.exec('tsc'),ctx.exec('typedoc'),])// restart process when file changesctx.monitor('./src','node ./dist')ctx.monitor('./src',['rm -rf dist','tsc','node dist'])ctx.monitor('./src',async()=>{awaitctx.run('build:server')awaitctx.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 restartp.current=require('child_process').exec('node dist')})})

Using in CI servers

If you use Foy in CI servers, you won't want thecli 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,spinner,setGlobalOptions}from'foy'setGlobalOptions({spinner:true})// enable loading animations, default is falsespinner(false)// disable spinner for current tasktask('test',asyncctx=>{/* ... */})/*$ foy testDependencyGraph for task [test]:─ testTask: test...*/

Using lifecycle hooks

You can add lifecycle hooks via thebefore,after, andonerror 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',asyncctx=>{/* ... */})task('task2',asyncctx=>{// do things before task1// run task1 manually, so we can// do things before or after itawaitctx.run('task1')// do things after task1})

Watch and build

task('build',asyncctx=>{/* build your project */})letp=nulltask('watch',asyncctx=>{ctx.monitor('./src',async()=>{ctx.exec('node ./src/server.ts')})})

Using with custom compiler

# Write Foyfile in ts, enabled by defaultfoy -r ts-node/register -c ./some/Foyfile.ts build# Write Foyfile in coffeefoy -r coffeescript/register -c ./some/Foyfile.coffee build

zsh/bash auto completion (New!!!)

Add foy auto completion in zsh/bash:

# for bashfoy --completion-profile>>~/.bashrc# for zshfoy --completion-profile>>~/.zshrc

API documentation

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

License

MIT


[8]ページ先頭

©2009-2025 Movatter.jp