{"id":21203075,"url":"https://github.com/faqtor/faqtor","last_synced_at":"2025-07-10T06:33:48.648Z","repository":{"id":44979595,"uuid":"163218855","full_name":"faqtor/faqtor","owner":"faqtor","description":"Promise-based task runner. Easy to use, easy to extend.","archived":false,"fork":false,"pushed_at":"2023-01-03T16:02:43.000Z","size":796,"stargazers_count":74,"open_issues_count":11,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-11-03T06:20:16.124Z","etag":null,"topics":["build","build-automation","make","nodejs","npm","task-runner"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/faqtor.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-12-26T21:24:51.000Z","updated_at":"2023-10-17T08:19:51.000Z","dependencies_parsed_at":"2023-02-01T07:31:36.232Z","dependency_job_id":null,"html_url":"https://github.com/faqtor/faqtor","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faqtor%2Ffaqtor","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faqtor%2Ffaqtor/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faqtor%2Ffaqtor/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/faqtor%2Ffaqtor/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/faqtor","download_url":"https://codeload.github.com/faqtor/faqtor/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225624101,"owners_count":17498353,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["build","build-automation","make","nodejs","npm","task-runner"],"created_at":"2024-11-20T20:20:52.624Z","updated_at":"2024-11-20T20:20:53.742Z","avatar_url":"https://github.com/faqtor.png","language":"TypeScript","readme":"\u003cp align=\"center\"\u003e\n\u003ca href=\"https://travis-ci.org/faqtor/faqtor\"\u003e\u003cimg src=\"https://travis-ci.org/faqtor/faqtor.svg?branch=master\" alt=\"Build Status\"\u003e\u003c/a\u003e\n\u003ca href=\"https://codecov.io/gh/faqtor/faqtor\"\u003e\u003cimg src=\"https://codecov.io/gh/faqtor/faqtor/branch/master/graph/badge.svg\" alt=\"codecov\"/\u003e\u003c/a\u003e\n\u003ca href=\"https://www.npmjs.com/package/faqtor\"\u003e\u003cimg src=\"https://img.shields.io/npm/v/faqtor.svg\" alt=\"npm\"/\u003e\u003c/a\u003e\n\u003ca href=\"https://github.com/faqtor/faqtor\"\u003e\u003cimg src=\"https://img.shields.io/github/languages/top/faqtor/faqtor.svg\" alt=\"GitHub top language\"/\u003e\u003c/a\u003e\n\u003ca href=\"https://www.npmjs.com/package/faqtor\"\u003e\u003cimg src=\"https://img.shields.io/npm/dt/faqtor.svg\" alt=\"npm\"/\u003e\u003c/a\u003e\n\u003ca href=\"https://snyk.io/test/npm/faqtor\"\u003e\u003cimg src=\"https://snyk.io/test/npm/faqtor/badge.svg\" alt=\"Known Vulnerabilities\"/\u003e\u003c/a\u003e\n\u003ca href=\"https://gitter.im/faqtor/questions?utm_source=badge\u0026utm_medium=badge\u0026utm_campaign=pr-badge\u0026utm_content=badge\"\u003e\u003cimg src=\"https://badges.gitter.im/faqtor/questions.svg\" alt=\"Join the chat at https://gitter.im/hyper-oop\"/\u003e\u003c/a\u003e\n\u003c/p\u003e\n\n# faqtor\nPromise-based build automation for the NodeJS ecosystem \n\n## Table of contents\n * [Tutorial](#tutorial)\n   * [Project directory layout](#project-directory-layout)\n   * [Faqtor configuration basics](#faqtor-configuration-basics)\n   * [Real world examples](#real-world-examples)\n * [Example](#example)\n\n\n## Tutorial\n\nBasically, the Faqtor build system consists of library named `faqtor` and CLI tool named `fqr`\n\n```bash\nnpm i -D faqtor fqr\n```\n\nBut for this tutorial it is easier to have `fqr` installed globally:\n\n```bash\nnpm i -g fqr\n```\n\nAlso you may install additional Faqtor plugins in order to use tools like [rollup](https://rollupjs.org) or [browser-sync](https://browsersync.io/). They are called with prefix 'faqtor-of-' usually, like [faqtor-of rollup](https://www.npmjs.com/package/faqtor-of-rollup) or [faqtor-of-browser-sync](https://www.npmjs.com/package/faqtor-of-browser-sync). Just [search for the faqtor](https://www.npmjs.com/search?q=faqtor).\n\n### Project directory layout\n\nThe recommended directory layout for a Faqtor-based project is following:\n\n    .\n    ├──build               # Folder containing all files related to the project building\n    │  ├──fqr.config.js    # Faqtor configuration file containing all information about how to build\n    │  ├──...\n    ├──package.json        # The usual package description file\n    ├──...                 # Other project files\n\nIn this case `fqr` will find `fqr.config.js` in the `build` directory automatically, with no additional configuration. But if you don't plan to have the `build` folder in your project, then the following structure will also work:\n\n    .\n    ├──fqr.config.js\n    ├──package.json\n    ├──...\n\n### Faqtor configuration basics\n\nFile `fqr.config.js` is just JavaScript nodejs module. For example, this file is valid `fqr.config.js`:\n\n```javascript\nmodule.exports = {\n    hello: () =\u003e console.log(\"Hello, World!\"),\n}\n```\n\nThen if you type following bash command (with `fqr` installed globally):\n\n```bash\nfqr hello\n```\n\nyou will see the expected output `Hello, World!`. But usually entries of `module.exports` are more complex objects, called factors. The faqtor library itself provides following functions for producing factors:\n\n- `cmd`\n- `seq`\n- `all`\n- `func`\n\n#### Factor of `cmd`\n\nThe first one, `cmd`, executes binary:\n\n```javascript\nconst { cmd } = require(\"faqtor\");\n\nmodule.exports = {\n    friends: cmd(\"echo 'Hello, friends!'\"),\n    world:   cmd(\"echo 'Hello, World!'\"),\n}\n```\n\nIn this case commands `fqr friends` and `fqr world` will produce more complex outputs, for example:\n\n\u003cpre\u003e\nfqr friends\n\n\u003cb style=\"color:blue\"\u003eTARGET:\u003c/b\u003e   friends\n\u003cb style=\"color:gray\"\u003eCOMMAND:\u003c/b\u003e  /bin/echo  'Hello, friends!'\nHello, friends!\n\u003cb style=\"color:blue\"\u003eSUCCEED:\u003c/b\u003e  friends\n\u003c/pre\u003e\n\nAnouther feature of the `cmd` is that it can look for binaries in the local `node_modules`. For example if you have [rimraf](https://www.npmjs.com/package/rimraf) locally installed in your project then you can execute it:\n\n```javascript\nconst { cmd } = require(\"faqtor\");\n\nmodule.exports = {\n    clean: cmd(\"rimraf *.o\"),\n}\n```\n\nLet's try it, assuming there are some `.o` files:\n\n\u003cpre\u003e\nfqr clean\n\n\u003cb style=\"color:blue\"\u003eTARGET:\u003c/b\u003e   clean\n\u003cb style=\"color:gray\"\u003eCOMMAND:\u003c/b\u003e  /usr/bin/node /home/osman/src/faqtor/readme/node_modules/.bin/rimraf *.o\n\u003cb style=\"color:blue\"\u003eSUCCEED:\u003c/b\u003e  clean\n\u003c/pre\u003e\n\nAs you can see, `cmd` has properly found locally installed `rimraf` and executed it.\n\n#### Factor of `seq`\n\nFactor produced by `seq` can execute several factors one by one. It stops execution if some factor returns error. In other words, `seq` acts much like `\u0026\u0026` operator of bash:\n\n```javascript\nconst { cmd, seq } = require(\"faqtor\");\n\nconst\n    clean = cmd(\"rimraf *.o\"),\n    hello = cmd(\"echo 'Hello, World!'\");  \n\nmodule.exports = {\n    sequence: seq(hello, clean),\n}\n```\n\nTry this configuration:\n\n\u003cpre\u003e\nfqr sequence\n\n\u003cb style=\"color:blue\"\u003eTARGET:\u003c/b\u003e   sequence\n\u003cb style=\"color:gray\"\u003eCOMMAND:\u003c/b\u003e  /bin/echo  'Hello, World!'\nHello, World!\n\u003cb style=\"color:gray\"\u003eCOMMAND:\u003c/b\u003e  /usr/bin/node /home/osman/src/faqtor/readme/node_modules/.bin/rimraf *.o\n\u003cb style=\"color:blue\"\u003eSUCCEED:\u003c/b\u003e  sequence\n\u003c/pre\u003e\n\n#### Factor of `all`\n\nFactor produced by `all` can execute several factors in \"parallel\" using `Promise.all`. See [example](#example) above.\n\n#### Factor of `func`\n\nFinally, `func` can produce factor from user defined function, that may have about the following signature:\n\n```typescript\nfunction MyFactor(argv?: string[]): Promise\u003cError\u003e\n```\n\nLet's create the following configuration:\n\n```javascript\nconst { func } = require(\"faqtor\");\n\nconst myHello = (someone) =\u003e console.log(`Привет, ${someone}!`)\n\nmodule.exports = {\n    hello: func(myHello),\n}\n```\n\nNow try:\n\n\u003cpre\u003e\nfqr \"hello World\"\n\n\u003cb style=\"color:blue\"\u003eTARGET:\u003c/b\u003e   hello\nПривет, World!\n\u003cb style=\"color:blue\"\u003eSUCCEED:\u003c/b\u003e  hello\n\u003c/pre\u003e\n\nThe difference between providing factor object and just function as entry is that factor have some convenient methods like `task`.\n\n#### Method `task`\n\nLet's modify the previous example:\n\n```javascript\nconst { func } = require(\"faqtor\");\n\nconst myHello = (someone) =\u003e console.log(`Привет, ${someone}!`)\n\nmodule.exports = {\n    hello: func(myHello).task(\"greet someone\"),\n}\n```\n\nAs you see we added call of the `task` method with argument `\"greet someone\"`. Now we can see task description in the output:\n\n\u003cpre\u003e\nfqr \"hello World\"\n\n\u003cb style=\"color:blue\"\u003eTARGET:\u003c/b\u003e   hello\n\u003cb style=\"color:green\"\u003eTASK:\u003c/b\u003e     greet someone\nПривет, World!\n\u003cb style=\"color:blue\"\u003eSUCCEED:\u003c/b\u003e  hello\n\u003c/pre\u003e\n\nIt is especially convenient when you run many tasks during build process, and some of them may run silently.\n\n#### Method `factor`\n\nAnother important feature of factor object is the method of the same name, `factor`. It has the following signature:\n\n```typescript\npublic factor(input?: Domain, output?: Domain): IFactor\n```\n\nwhere `Domain` is TypeScript type:\n\n```typescript\nexport type Domain = null | string | string[];\n```\n\n`Domain` argument may contain some [glob](https://www.npmjs.com/package/glob) or array of globs. In this case Faqtor system calculates the maximum of modification times of files matching the glob. Now the given factor will be executed in the case if the time calculated for input is greater then for output. More precisely, Faqtor system checks the following conditions consequently:\n\n- run factor if no input globs\n- return \"nothing to do\" if input has globs but no files\n- run factor if no output globs\n- run factor if has output globs but no files\n- run factor if modification time for input is greater then modification time for output\n- return \"nothing to do\" otherwise\n\nCalling `factor` method with no arguments is meaningful for some factors that have their \"native\" input or output globs. Example of such factor is one produced by [faqtor-of-uglify](https://www.npmjs.com/package/faqtor-of-uglify):\n\n```javascript\nconst uglify = minify(\"index.js\", \"index.min.js\")\n    .factor()\n    .task(\"minifying 'index.js'\");\n```\n\nHere `\"index.js\"` and `\"index.min.js\"` are used by default as input and output `Domain`'s correspondently.\n\n... [_to be continued_](https://medium.com/@bineev/writing-a-plugin-for-the-faqtor-task-runner-4c8e967546b9) ...\n\n### Real world examples\n\nFaqtor build automation system is used in [HyperOOP](https://github.com/HyperOOP) project.\nThe most detailed example of Faqtor/fqr and plugins usage is [source code](https://github.com/HyperOOP/hyperoop-site) of the HyperOOP homepage. Look at [fqr.config.js](https://github.com/HyperOOP/hyperoop-site/blob/master/build/fqr.config.js). Other examples of Faqtor configuration files are [HyperOOP library](https://github.com/HyperOOP/hyperoop/blob/master/build/fqr.config.js) and [HyperOOP Router library](https://github.com/HyperOOP/hyperoop-router/blob/master/build/fqr.config.js) build configuration files. Also all our official plugins have Faqtor configuration files, but they are similar, look at [this one](https://github.com/faqtor/faqtor-of-watch/blob/master/build/fqr.config.js) for example.\n\n\n## Example\n\nYou can install and run this example locally:\n\n```\ngit clone https://github.com/faqtor/example faqtor-example\ncd faqtor-example\nnpm i\nnpm start\n```\n\nThen try to change `src/template.html` and `src/index.js`: all changes will be visible in browser immediately.\n\nLet's look at build configuration (`build/fqr.config.js`):\n\n```javascript\n// Necessary utilities from 'faqtor' library:\nconst { seq, cmd, all } = require(\"faqtor\")\n\n// Factor produced by 'minify' will perform javascript minification:\nconst { minify } = require(\"faqtor-of-uglify\");\n\n// Factor produced by 'render' will run our HTML template:\nconst { render } = require(\"faqtor-of-handlebars\");\n\n// Factor to watch changes in files:\nconst { watch } = require(\"faqtor-of-watch\");\n\n// 'bs' can produce factors for usual browser-sync tasks, like 'reload' for example:\nconst bs = require(\"faqtor-of-browser-sync\").create();\n\n// In this block we create elementary parts of our building process:\nconst\n    // create 'dist' and 'dist/js' folders if they don't exist, using 'mkdirp' command:\n    makeDistFolder = cmd(\"mkdirp dist/js\"),\n    // create development version of 'index.html' using handlebars:\n    devMakeIndexHtml = render(\"src/template.html\", \"src/index.html\", {\n            indexJS: \"./index.js\"\n        }),\n    // create production version of 'index.html' using handlebars:\n    prodMakeIndexHtml = render(\"src/template.html\", \"dist/index.html\", {\n            indexJS: \"js/index.js\"\n        }),\n    // minify 'index.js' for production\n    uglifyIndexJS = minify(\"src/index.js\", \"dist/js/index.js\"),\n    // reload browsers if something on page has changed\n    reloadBrowserPage = bs.reload(\"src/index.*\");\n\nmodule.exports = {\n    // entry 'build' to call from 'package.json/scripts': fqr build\n    // 'seq' is sequence of tasks, analog of bash \u0026\u0026 operator\n    build: seq(makeDistFolder, uglifyIndexJS, prodMakeIndexHtml),\n    // entry 'serve' to call from 'package.json/scripts': fqr serve\n    // watch for changes and reload page if necessary\n    serve: seq(devMakeIndexHtml, all(\n        bs.init({ server: { baseDir: \"src\" } }),\n        watch([devMakeIndexHtml, reloadBrowserPage])\n    )),\n    // entry 'clean' to call from 'package.json/scripts': fqr clean\n    clean: cmd(\"rimraf dist src/index.html\")\n}\n\n```\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffaqtor%2Ffaqtor","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffaqtor%2Ffaqtor","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffaqtor%2Ffaqtor/lists"}