Skip to content
/ nokit Public

A light weight set of handy tools for real world program.

Notifications You must be signed in to change notification settings

ysmood/nokit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Overview

A light weight set of handy tools for real world program.

Reduce the gap between different systems. Such as watch directory changes on a network file system, operate spawn on Windows and Linux, handle async IO api with promise, etc.

Rather than help you decide what to do, it is designed to create possibilities. Even this document is generated by nokit itself.

NPM version Build Status Build status Deps Up to Date

Features

  • All async functions will return promise.
  • All functions are highly lazy designed, minimum boot time.
  • Light weight and self-reference.

Installation

As a lib dependency, install it locally: npm i nokit.

Changelog

Goto changelog

API

Table of Content

CLI

Auto-Runner

noe is a dev tool to run / watch / reload program automatically. Run noe -h to see what you can do with it.

For more help, run: noe -h.

Static File Server

nos is a tool to statically serve a folder. Run nos -h to see what you can do with it.

For more help, run: nos -h.

Temote TTY

nor is a cross platform remote tty tool.

For more help, run: nor -h.

kit

  • Nokit extends all the functions of nofs and yaku/lib/utils. You can use it as same as nofs. For more info, see the doc:

    Offline Documentation

    • example:

      kit.readFile('test.txt', 'utf8').then((str) =>
          console.log(str)
      );
      
      kit.outputFile('a.txt', 'test')
      .then(() => kit.log('done'));
      
      kit.writeJSON('b.json', { a: 10 })
      .then(() => kit.log('done'))
      
      kit.mkdirs('b.json', { a: 10 })
      .then(() => kit.log('done'));
  • The lodash lib.

    • type: { Object }

    • example:

      kit._.map([1, 2, 3]);
  • The browser helper. It helps you to live reload the page and log remotely.

    • static:

    • param: opts { Object }

      The options of the client, defaults:

      {
       host: '', // The host of the event source.
       useJs: false // By default the function will return html string
      }
    • return: { String }

      The code of client helper.

    • example:

      When the client code is loaded on the browser, you can use the nb.log to log anything to server's terminal. The server will auto-format and log the information to the terminal. It's convinient for mobile development when remote debug is not possible.

      // The nb is assigned to the "window" object.
      nb.log({ a: 10 });
      nb.log(10);
      nb.es.addEventListener('fileModified', () =>
       console.log('file changed')
      );
  • Generate styled string for terminal. It's disabled when process.env.NODE_ENV == 'production'.

    • example:

      let br = kit.require('brush');
      kit.log(br.red('error info'));
      
      // Disable color globally.
      br.isEnabled = false;
      
      // To see all the available brushes.
      kit.log(Object.keys(br));
  • A fast file cache helper. It uses hard link to cache files.

    • param: info { Object }

      Not optional.

      {
          // The first item is the key path, others are
          // its dependencies.
          deps: Array,
      
          // The path of the output file.
          // If it's undefined, depsCache will try to get cache.
          dests: Array,
      
          cacheDir: '.nokit'
      }
    • return: { Promise }

      Resolve a info object.

      {
          isNewer: Boolean,
      
          // { path: mtime }
          deps: Object,
      
          // { destPath: cachePath }
          dests: Object,
      
          cacheError: undefined || Error
      }
    • example:

      // Set cache
      kit.depsCache({
       dests: ['index.css'],
       deps: ['index.less', 'b.less', 'c.less']
      });
      
      // Get cache
      // You don't have to sepecify 'b.less', 'c.less'.
      kit.depsCache({ deps: ['index.less'] })
      .then((cache) => {
          if (cache.isNewer) {
              kit.log('cache is newer');
              kit.log(cache.dests);
          }
      });
  • Daemonize a program. Just a shortcut usage of kit.spawn.

    • param: opts { Object }

      Defaults:

      {
       bin: 'node',
       args: ['app.js'],
       stdout: 'stdout.log', // Can also be a fd
       stderr: 'stderr.log'  // Can also be a fd
      }
    • return: { Porcess }

      The daemonized process.

  • A simple decrypt helper. Cross-version of node.

    • param: data { Any }

    • param: password { String | Buffer }

    • param: algorithm { String }

      Default is 'aes128'.

    • return: { Buffer }

  • The warp drives. You must kit.require 'drives' before using it. For more information goto the Drives section.

    • type: { Object }
  • A simple encrypt helper. Cross-version of node.

    • param: data { Any }

    • param: password { String | Buffer }

    • param: algorithm { String }

      Default is 'aes128'.

    • return: { Buffer }

  • A error log shortcut for kit.log(msg, 'error', opts)

    • param: msg { Any }

    • param: opts { Object }

  • Shortcut for logging multiple error infos.

    • param: args { Any }

      ...

    • example:

      kit.errs('test1', 'test2', 'test3');
      // => [2015-02-07 08:31:49] test1 test2 test3
  • A better child_process.exec. Supports multi-line shell script. For supporting old version of node, it will create 3 temp files, the temp files will be removed after the execution.

    • param: cmd { String }

      Shell commands.

    • param: shell { String }

      Shell name. Such as bash, zsh. Optinal.

    • return: { Promise }

      Resolves when the process's stdio is drained. The resolve value is like:

      {
          code: 0,
          signal: null,
          stdout: 'hello world',
          stderr: ''
      }
    • example:

      kit.exec(`
          a='hello world'
          echo $a
      `).then(({code, stdout}) => {
          kit.log code   // output => 0
          kit.log stdout // output => "hello world"
      });
      
      // Bash doesn't support "**" recusive match pattern.
      let p = kit.exec(`
       echo **/*.css
      `, 'zsh');
      
      // Get the child process object.
      p.process.then((proc) =>
       kit.log(proc.pid)
      );
  • Format the parsed comments array to a markdown string.

    • param: comments { Array }

    • param: opts { Object }

      Defaults:

      {
          indent: 0,
          name: ({ name }) => String,
          tag: ({ tagName, name, type }) => String
      }
    • return: { String }

  • See my project nofs.

    Offline Documentation

  • Fuzzy search a string list by a key word.

    • param: keys { String }

      The key word.

    • param: list { Array }

      The list of string to search.

    • param: opts { Object }

      Defaults:

      {
          result: (wrappedList) =>
              wrappedList.minBy('distance').words,
          threshold: (cOffset, keyLen, cIndex) =>
              Infinity,
          notFound: (cOffset, keyLen, cIndex) =>
              Infinity,
          span: (cOffset, keyLen, cIndex) =>
              cOffset,
          found: (cOffset, keyLen, cIndex) =>
              (Math.exp(cOffset + 1) - 1) * (keyLen - cIndex),
          tail: (cOffset, keyLen, cIndex, tailLen) =>
              tailLen
      }
    • return: { String }

      The best matched one. If not found, return undefined.

    • example:

      kit.fuzzySearch('hw', ['test', 'hello world', 'hey world'])
      // output => 'hey world'
      
      // To get a sortable weighted list.
      kit.fuzzySearch('hw', ['test', 'hello world', 'hey world'], {
       result: (wrappedList) => wrappedList.value()
      });
      // output => [
      //  { distance: Infinity }
      //  { words: 'hello world', distance: 1110.069 }
      //  { words: 'hey world', distance: 159.849 }
      // ]
  • Generate a list of module paths from a name and a directory.

    • param: moduleName { String }

      The module name.

    • param: dir { String }

      The root path. Default is current working dir.

    • param: modDir { String }

      Default is 'node_modules'.

    • return: { Array }

      Paths

    • example:

      // Suppose current working directory is '/home/a'
      kit.genModulePaths('test')
      // output => ['/home/a/node_modules/test', '/home/node_modules/test', '/node_modules/test']
  • Indent a text block.

    • param: text { String }

    • param: num { Int }

    • param: char { String }

    • param: reg { RegExp }

      Default is /^/mg.

    • return: { String }

      The indented text block.

    • example:

      // Increase
      kit.indent("one\ntwo", 2)
      // => "  one\n  two"
      
      // Decrease
      kit.indent("--one\n--two", 0, '', /^--/mg)
      // => "one\ntwo"
  • Nokit use it to check the running mode of the app. Overwrite it if you want to control the check logic. By default it returns the rocess.env.NODE_ENV == 'development'.

    • return: { Boolean }
  • Nokit use it to check the running mode of the app. Overwrite it if you want to control the check logic. By default it returns the rocess.env.NODE_ENV == 'production'.

    • return: { Boolean }
  • A fast helper to hash string or binary file. See my jhash project. You must kit.require 'jhash' before using it.

    Offline Documentation

    • example:

      kit.require('jhash');
      kit.jhash.hash('test'); // output => '349o'
      
      jhash.hash(kit.readFileSync('a.jpg'));
      
      // Control the hash char set.
      kit.jhash.setSymbols('abcdef');
      kit.jhash.hash('test'); // output => 'decfddfe'
      
      // Control the max length of the result hash value. Unit is bit.
      jhash.setMaskLen(10);
      jhash.hash('test'); // output => 'ede'
  • A better log for debugging, it uses the kit.xinspect to log.

    Use terminal command like logReg='pattern' node app.js to filter the log info.

    Use logTrace='on' node app.js to force each log end with a stack trace.

    • param: msg { Any }

      Your log message.

    • param: action { String }

      'log', 'error', 'warn'.

    • param: opts { Object }

      Default is same with kit.xinspect, but with some extra options:

      {
       isShowTime: true,
       logReg: process.env.logReg && new RegExp(process.env.logReg),
       logTrace: process.env.logTrace === 'on',
      
       // Custom log method
       log: (str, action) => console[action](str)
      }
    • example:

      kit.log('test');
      // => '[2015-02-07 08:31:49] test'
      
      kit.log('test', { isShowTime: false });
      // => 'test'
      
      kit.log('test', { logReg: /a/ });
      // => ''
      
      kit.log('%s %s %d', ['a', 'b', 10]);
      // => '[2015-02-07 08:31:49] a b 10'
  • Shortcut for logging multiple infos.

    • param: args { Any }

      ...

    • example:

      kit.logs('test1', 'test2', 'test3');
      // => [2015-02-07 08:31:49] test1 test2 test3
  • Monitor an application and automatically restart it when file changed. Even when the monitored app exit with error, the monitor will still wait for your file change to restart the application. Not only nodejs, but also other programs like ruby or python. It will print useful infomation when it application unexceptedly.

    • param: opts { Object }

      Defaults:

      {
       bin: 'node',
       args: ['index.js'],
       prefix: 'string', // see the `kit.spawn` for details
       watchList: [], // By default, the same with the "args".
       isNodeDeps: true,
       opts: {}, // Same as the opts of 'kit.spawn'.
      
       // The option of `kit.parseDependency`
       parseDependency: {},
      
       // A hook for restarting the program, run the function "start" to
       // restart.
       retry: (start) => {},
      
       onStart: =>
           kit.log("Monitor: " + opts.watchList),
       onRestart: (path) =>
           kit.log("Reload app, modified: " + path),
       onWatchFiles: (paths) =>
           kit.log('Watching:' + paths.join(', ')),
       onNormalExit: ({ code, signal }) =>
           kit.log('EXIT' +
               ` code: ${code} signal: ${signal}`),
       onErrorExit: ({ code, signal }) =>
           kit.err('EXIT' +
           ` code: ${code} signal: ${signal}\n` +
           'Process closed. Edit and save the watched file to restart.'),
      }
    • return: { Object }

      Properties:

      {
       // Call it to stop monitor.
       stop: => {},
      
       // Resolve a list of watch handlers.
       watchPromise: Promise
      }
    • example:

      kit.monitorApp({
       bin: 'coffee',
       args: ['main.coffee']
      });
      
      kit.monitorApp({
       bin: 'ruby'
       args: ['app.rb', 'lib/**/*.rb']
       isNodeDeps: false
      });
  • Node version. Such as v0.10.23 is 0.1023, v0.10.1 is 0.1001.

    • return: { Float }
  • A helper for arguments type based function override.

    • param: args { Array | Object }

      The arguments to set.

    • param: defaults { Object }

      The default argument settings. The key value of the setting is the argument name, the value is an object, and the key is the type of the argument, the value is the default value of the argument.

    • return: { Object }

    • example:

      let foo = () => {
          kit.defaultArgs(arguments, {
              name: { String: 'A' },
              brush: { Array: [] },
              family: { String: null },
              isReal: { Boolean: false },
              fn: { Function: => 'callback' }
          });
      };
      
      kit.log(foo('test', false, ['red'], -> 'nothing'));
      // Here the logged value will deeply equal:
      { name: 'test', brush: ['red'], family: null, fn: => 'nothing' }
  • A comments parser for javascript and coffee-script. Used to generate documentation from source code automatically. It will traverse through all the comments of a coffee file.

    • param: code { String }

      Coffee source code.

    • param: opts { Object }

      Parser options:

      {
          commentReg: RegExp,
          splitReg: RegExp,
          tagNameReg: RegExp,
          typeReg: RegExp,
          nameReg: RegExp,
          nameTags: ['param', 'property'],
          descriptionReg: RegExp
      }
    • return: { Array }

      The parsed comments. Each item is something like:

      {
          name: 'parseComment',
          description: 'A comments parser for coffee-script.',
          tags: [
              {
                  tagName: 'param',
                  type: 'string',
                  name: 'code',
                  description: 'The name of the module it belongs to.',
                  index: 256, // The target char index in the file.
                  line: 32 // The line number of the target in the file.
              }
          ]
      }
  • Parse dependency tree by regex. The dependency relationships is not a tree, but a graph. To avoid dependency cycle, this function only return an linear array of the dependencies, from which you won't get the detail relationshops between files.

    • param: entryPaths { String | Array }

      The file to begin with.

    • param: opts { Object }

      Defaults:

      {
       // It will match `require`, `import` statements.
       depReg: RegExp,
      
       // It will handle all the matched paths.
       // Return false value if you don't want this match.
       handle: (path) => path
      }
    • return: { Promise }

      It resolves the dependency path array.

    • example:

      kit.parseDependency('main.', {
       depReg: /require\s*\(?['"](.+)['"]\)?/gm,
       handle: (path) => {
           if (path.match(/^(?:\.|/|[a-z]:)/i)) return path;
       }
      })
      .then((markdownStr) =>
       kit.log(markdownStr)
      );
  • io.js native module path. See nofs for more information.

  • The promise lib. Now, it uses Yaku as ES5 polyfill. In the future, the Yaku will be replaced with native ES6 Promise. Please don't use any API other than the ES6 spec.

    • type: { Object }
  • The proxy module. You must kit.require 'proxy' before using it. For more information goto the Proxy section.

  • Reduce a string via a regex.

    • param: reg { RegExp }

    • param: str { String }

    • param: iter { Function }

      (init, matchGroup) -> init, default is _.iteratee.

    • param: init { Any }

    • return: { Any }

    • example:

      let out = kit.regexReduce(/\w(\d+)/g, 'a1, b10, c3', (ret, ms) => {
       ret.push(ms[1]);
       return ret;
      }, []);
      
      kit.log(out); // => [1, 10, 3]
  • Map a string via a regex.

    • param: reg { RegExp }

    • param: str { String }

    • param: iter { Function }

      (matchGroup) ->, default is _.iteratee.

    • return: { Array }

    • example:

      let out = kit.regexMap(/\w(\d+)/g, 'a1, b10, c3', 1);
      
      kit.log(out) // => [1, 10, 3]
  • An async string replace function.

    • param: str { String }

      The string to replace

    • param: pattern { String | Regex }

    • param: iter { Function }

      It can return a promise

    • return: { Promise }

  • An async string replace function, each replacement process will run in line.

    • param: str { String }

      The string to replace

    • param: pattern { String | Regex }

    • param: iter { Function }

      It can return a promise

    • return: { Promise }

  • Much faster than the native require of node, but you should follow some rules to use it safely. Use it to load nokit's internal module.

    • param: moduleName { String }

      The module path or name.

    • param: dir { String }

      Current absolute file path. Not optional, expect when requiring nokit's internal modules. On most times, just pass __dirname to it is enough.

    • param: loaded { Function }

      Run only the first time after the module loaded.

    • return: { Module }

      The module that you require.

    • example:

      Use it to load nokit's internal module.

      kit.require('jhash');
      // Then you can use the module, or it will be null.
      kit.jhash.hash('test');

      To load a relative path, or you own module, the second parameter 'dir' is required.

      let mod = kit.require('./mod', __dirname);
      
      // Or load your own 'jhash', rather than nokit's.
      let jhash = kit.require('jhash', __dirname);
  • Require an optional package. If not found, it will warn the user to npm install it, and exit the process. When kit.requireOptional.autoInstall is set to true, the package will be auto installed if it's missed.

    • param: name { String }

      Package name

    • param: dir { String }

      Current absolute file path. Not optional. On most times, just pass __dirname to it is enough.

    • param: semver { String }

      Specify what version you need, such as ^0.3.1 or >=1.2.3, ect.

    • return: { Any }

      The required package.

  • A handy extended combination of http.request and https.request.

    • param: opts { Object }

      The same as the http.request, but with some extra options:

      {
       // String or Url Object.
       url: String | Object,
      
       // Other than return `res` with `res.body`,return `body` directly.
       body: true,
      
       // Max times of auto redirect. If 0, no auto redirect.
       redirect: 0,
      
       // Timeout of the socket of the http connection.
       // If timeout happens, the promise will reject.
       // Zero means no timeout.
       timeout: 0,
      
       // The key of headers should be lowercased.
       headers: {},
      
       protocol: 'http:' or 'https:',
      
       agent: null,
      
       // Auto set "transfer-encoding" header to 'chunked' if the `reqData` is
       // stream and the 'Content-Length' header is not set.
       autoTE: true,
      
       // Set null to use buffer, optional.
       // It supports GBK, ShiftJIS etc.
       // For more info, see https://github.com/ashtuchkin/iconv-lite
       resEncoding: 'auto',
      
       // Whether to unzip gzip / deflate.
       autoUnzip: true,
      
       // It's string, object, stream or buffer, it's optional. When it's an object,
       // The request will be 'application/x-www-form-urlencoded'.
       reqData: null,
      
       // auto end the request.
       autoEndReq: true,
      
       // Writable stream.
       resPipe: null,
      
       // Handle resPipe before it's piped.
       // Its returned value will be assigned to `opts.resPipe`. So you can return
       // null to make the request resolve the `body`.
       handleResPipe: (res, resPipe) => resPipe,
      
       /// The progress of the request.
       reqProgress: (complete, total) => {},
      
       // The progress of the response.
       resProgress: (complete, total) => {},
      
       resPipeError: (res) => res.end()
      }

      And if set opts as string, it will be treated as the url.

    • return: { Promise }

      Contains the http response object, it has an extra body property. You can also get the request object by using Promise.req.

    • example:

      let p = kit.request('http://test.com');
      p.req.on('response', (res) =>
       kit.log res.headers['content-length']
      );
      p.then((body) =>
       kit.log(body); // html or buffer
      );
      
      kit.request({
       url: {
           protocol: 'https', hostname: 'test.com',
           port: 8123, path: '/a.mp3?s=1'
       },
       body: false,
       resProgress: (complete, total) =>
           kit.log(`Progress: ${complete} / ${total}`)
      })
      .then((res) => {
       kit.log(res.body.length);
       kit.log(res.headers);
      });
      
      // Send form-data.
      let form = new require('form-data');
      form.append('image', Buffer.alloc(0), {
       filename: 'a.jpg', contentType: 'image/jpg'
      });
      form.append('key', 'value');
      kit.request({
       url: 'a.com',
       method: 'POST',
       headers: form.getHeaders(),
      
       reqData: form
      })
      .then((body) =>
       kit.log(body)
      );
  • The semantic versioner for npm, known as semver. You must kit.require 'semver' before using it.

    • type: { Object }
  • A safer version of child_process.spawn to cross-platform run a process. In some conditions, it may be more convenient to use the kit.exec. It will automatically add node_modules/.bin to the PATH environment variable.

    • param: cmd { String }

      Path or name of an executable program.

    • param: args { Array }

      CLI arguments. If any of the item is an object, it will be converted to string by JSON.stringify.

    • param: opts { Object }

      Process options. Almost the same with the Node.js official documentation. It will inherit the parent's stdio by default. An extra prefix option, if it's enabled, all stdout and stderr will be prefix with the specified string, you can also specify the color like web:red, web:blue, if no color found, a random color will be used.

    • return: { Promise }

      The promise.process is the spawned child process object. Resolves when the process's stdio is drained and the exit code is either 0 or 130. The resolve value is like:

      {
       code: 0,
       signal: null
      }
    • example:

      kit.spawn('git', ['commit', '-m', '42 is the answer to everything'])
      .then(({code}) => kit.log code);
  • The sse module. You must kit.require 'sse' before using it. For more information goto the sse section.

  • Sequencing and executing tasks and dependencies concurrently.

    • param: name { String }

      The task name.

    • param: opts { Object }

      Optional. Defaults:

      {
       deps: String | Array,
       description: String,
       logStart: () => (),
       logEnd: () => (),
      
       // Whether to run dependency in a row.
       isSequential: false
      }
    • param: fn { Function }

      (val) -> Promise | Any The task function. If it is a async task, it should return a promise. It will get its dependency tasks' resolved values.

    • property: run { Function }

      Use it to start tasks. Each task will only run once. (names = 'default', opts) ->. The names can be a string or array. The default opts:

      {
       isSequential: false,
      
       // Will be passed as the first task's argument.
       init: undefined,
      
       // To stop the run currently in process. Set the `$stop`
       // reference to true. It will reject a "runStopped" error.
       warp: { $stop: false }
      }
    • property: list { Object }

      The defined task functions.

    • return: { Promise }

      Resolve with the last task's resolved value. When isSequential == true, it resolves a value, else it resolves an array.

    • example:

      kit.task('default', { deps: 'build' }, () =>
       kit.log('run defaults...')
      );
      
      kit.task('build', { deps: ['clean'] }, (isFull) =>
       isFull ? 'do something' : 'do something else'
      );
      
      kit.task('clean', (opts) =>
       opts.isForce ?
           kit.remove('dist/**', { isForce: true }) :
           kit.remove('dist/**')
      );
      
      kit.task.run()
      .then(() =>
       kit.log('All Done!')
      );
  • Cross-platform kill process tree by root process id.

    • param: pid { Number }

    • param: signal { String | Number }

      Such as 'SIGINT'

    • param: callback { Function }

  • The url module of node. You must kit.require 'url' before using it.

  • Works much like gulp.src, but with Promise instead. The warp control and error handling is more pleasant.

    • param: from { String }

      Glob pattern string.

    • param: opts { Object }

      It extends the options of nofs.glob, but with some extra proptereis. Defaults:

      {
       // The base directory of the pattern.
       baseDir: String
      }
    • return: { Object }

      The returned warp object has these members:

      {
       // The drive can also be a promise that will resolve a drive.
       load: (drive) => fileInfo | null,
      
       run: (path) => Promise
      }

      Each piped drive will recieve a object that extends nofs's fileInfo object:

      {
       // Set the contents and return self.
       set: (String | Buffer) => fileInfo,
      
       // The src file path.
       path: String,
      
       // The dest root path.
       to: String,
      
       baseDir: String,
      
       // The destination path.
       // Alter it if you want to change the output file's location.
       // You can set it to string, warp will auto-convert it to object.
       // It's "valueOf" will return "kit.path.join dir, name + ext".
       dest: { root, dir, base, ext, name },
      
       // The file content.
       contents: String | Buffer,
      
       isDir: Boolean,
      
       stats: fs.Stats,
      
       // Alter it to control the left drives dynamically.
       drives: [Function],
      
       // All the globbed files.
       list: Array,
      
       driveList: Array,
      
       // The opts you passed to "kit.warp", it will be extended.
       opts: Object
      }

      Each drive can have a onEnd: (fileInfo) -> Any | Promise function, which will be called after a file's whole warp is ended.

      The drive can have a isReader property, which will make the drive override the default file reader.

      The drive can have a isWriter property, which will make the drive override the default file writer.

      If a drive overrides another, it can call fileInfo.super() to use it again.

    • example:

      // Define a simple workflow.
      kit.warp('src/**/*.js')
      .load((fileInfo) =>
          fileInfo.set('/* Lisence Info */' + fileInfo.contents)
      )
      .load(jslint())
      .load(minify())
      .run('build/minified');
      
      // Override warp's file reader with a custom one.
      let myReader = kit._.extend((f) =>
          kit.readFile(f.path, 'hex').then(f.path)
      ), {
       // This will tell warp you want use your own reader.
       isReader: true
      });
      
      // Override writer.
      let myWriter = kit._.extend((f) => {
       if (f.dest === 'a.js') return;
      
       // Call the overrided writer.
       f.super();
      }, { isWriter: true, onEnd: () => {
         super();
         kit.log(this.list);
      });
      
      kit.warp('src/**/*.js')
      .load(myWriter)
      .run('dist');
      
      // Use nokit's built-in warp drives.
      let drives = kit.require('drives');
      kit.warp('src/**/*.coffee')
      .load(drives.coffee());
      .run('dist');
  • Same as the unix which command. You must kit.require 'which' before using it.

    • param: name { String }

      The command.

    • return: { Promise }

  • Sync version of which. You must kit.require 'whichSync' before using it.

    • type: { Function }
  • For debugging. Dump a colorful object.

    • param: obj { Object }

      Your target object.

    • param: opts { Object }

      Options. Default:

      { colors: true, depth: 7 }
    • return: { String }

  • Open a thing that your system can recognize. Now only support Windows, OSX or system that installed 'xdg-open'.

    • param: cmds { String | Array }

      The thing you want to open.

    • param: opts { Object }

      The options of the node native child_process.exec.

    • return: { Promise }

      When the child process exists.

    • example:

      Open a webpage with the default browser.

      kit.open('http://ysmood.org');

proxy

  • For test, page injection development. A cross-platform programmable Fiddler alternative. You can even replace express.js with it's flow function.

  • A simple request body middleware. It will append a property reqBody to ctx. It will append a property body to ctx.req.

    • params:

      opts {Object} Defaults:

      {
          limit: Infinity,
          memoryLimit: 100 * 1024 // 100KB
      }
    • return: { Function }

      (ctx) -> Promise

    • example:

      let kit = require('nokit');
      let proxy = kit.require('proxy');
      
      let app = proxy.flow();
      
      app.push(proxy.body());
      
      app.push(($) => {
          kit.logs($.reqBody);
      });
      
      app.listen(8123);
      
  • Http CONNECT method tunneling proxy helper. Most times it is used to proxy https and websocket.

    • param: opts { Object }

      Defaults:

      {
          // If it returns false, the proxy will be ignored.
          filter: (req) => true,
      
          handleReqHeaders: (headers) => headers,
      
          host: null, // Optional. The target host force to.
          port: null, // Optional. The target port force to.
          onError: (err, socket) => {}
      }
    • return: { Function }

      The connect request handler.

    • example:

      let kit = require('nokit');
      let proxy = kit.require('proxy');
      
      let app = proxy.flow();
      
      // Directly connect to the original site.
      app.server.on('connect', kit.proxy.connect());
      
      app.listen(8123);
  • Proxy and replace a single js file with a local one.

    • param: opts { Object }

      {
          url: Regex, // The url pattern to match
          file: String // The local js file path
      }
    • return: { Function }

      noflow middleware

    • example:

      let kit = require('nokit');
      let http = require('http');
      let proxy = kit.require('proxy');
      
      let app = proxy.flow();
      
      app.push(proxy.debugJs({
          url: /main.js$/,
          file: './main.js'
      }));
      
      app.listen(8123);
  • Create a etag middleware.

    • return: { Function }
  • A simple protocol to read, write, chmod, delete file via http. The protocol is very simple

    POST / HTTP/1.1
    file-action: ${action}
    
    ${body}
    

    The action is somethine like { type: 'create', path: '/home/u/a/b.js', mode: 0o777 } The body is the binary of the file content. Both the action and the body are encrypt with the password and algorithm specified in the opts.

    • param: opts { Object }

      defaults

      {
          password: 'nokit',
          algorithm: 'aes128',
          rootAllowed: '/',
          actionKey: 'file-action'
      }
    • return: { Function }

      noflow middleware

  • Make a file create request to proxy.file.

    • param: opts { Object }

      Defaults

      {
         action: 'read',
         url: '127.0.0.1',
         path: String,
         data: Any,
         password: 'nokit',
         algorithm: 'aes128',
         actionKey: 'file-action',
         typeKey: 'file-type'
      }
    • return: { Promise }

  • A minimal middleware composer for the future. https://github.com/ysmood/noflow

  • Convert noflow middleware express middleware.

    • param: fn { Function }

      noflow middleware

    • return: { FUnction }

      express middleware

  • Generate an express like unix path selector. See the example of proxy.flow.

    • param: pattern { String }

    • param: opts { Object }

      Same as the path-to-regexp's options.

    • return: { Function }

      (String) -> Object.

    • example:

      let proxy = kit.require('proxy');
      let match = proxy.match('/items/:id');
      kit.log(match('/items/10')) // output => { id: '10' }
  • Convert a Express-like middleware to proxy.flow middleware.

    • param: h { Function }

      (req, res, next) ->

    • return: { Function }

      (ctx) -> Promise

      let proxy = kit.require('proxy');
      let http = require('http');
      let bodyParser = require('body-parser');
      
      let middlewares = [
          proxy.midToFlow(bodyParser.json()),
      
          (ctx) => ctx.body = ctx.req.body
      ];
      
      http.createServer(proxy.flow(middlewares)).listen(8123);
  • A simple url parser middleware. It will append a url object to ctx

    • param: parseQueryString { boolean }

    • param: slashesDenoteHost { boolean }

    • return: { Function }

      (ctx) -> Promise

    • example:

      let kit = require('nokit');
      let proxy = kit.require('proxy');
      
      let app = proxy.flow();
      
      app.push(proxy.parseUrl(true));
      
      app.push(($) => {
          kit.logs($.reqUrl.path);
      });
      
      app.listen(8123);
      
  • A helper for http server port tunneling.

    • param: opts { Object }

      {
          allowedHosts: [],
          onSocketError: () => {},
          onRelayError: () => {}
      }
    • return: { Function }

      A http connect method helper.

  • A helper for http server port tunneling.

    • param: opts { Object }

      {
          host: '0.0.0.0:9970',
          relayHost: '127.0.0.1:9971',
          hostTo: '127.0.0.1:8080',
          onSocketError: () => {},
          onRelayError: () => {}
      }
    • return: { Promise }

      Resolve a tcp server object.

  • Create a conditional middleware that only works when the pattern matches.

    • param: sel { Object }

      The selector. Members:

      {
       url: String | Regex | Function,
       method: String | Regex | Function,
       headers: Object
      }

      When it's not an object, it will be convert via sel = { url: sel }. The url, method and headers are act as selectors. If current request matches the selector, the middleware will be called with the captured result. If the selector is a function, it should return a non-undefined, non-null value when matches, it will be assigned to the ctx. When the url is a string, if req.url starts with the url, the rest of the string will be captured.

    • param: middleware { Function }

    • return: { Function }

  • Create a http request middleware.

    • param: opts { Object }

      Same as the sse.

    • return: { Function }

      (req, res, next) ->. It has some extra properties:

      {
       ssePrefix: '/nokit-sse',
       logPrefix: '/nokit-log',
       sse: kit.sse,
       watch: (filePath, reqUrl) => {},
       host: '', // The host of the event source.
       useJs: false // By default the browserHelper will be a html string
      }
    • example:

      Visit 'http://127.0.0.1:80123', every 3 sec, the page will be reloaded. If the ./static/default.css is modified, the page a.html will also be reloaded.

      let kit = require('nokit');
      let http = require('http');
      let proxy = kit.require('proxy');
      let handler = proxy.serverHelper();
      
      let app = proxy.flow();
      
      handler.watch('./static/default.css', '/st/default.css');
      
      app.push(handler);
      
      app.push(proxy.select(/a\.html$/, proxy.url({
          handleResBody: (body) => body + handler.browserHelper
      })));
      
      app.listen(8123);
      
      setInterval(() =>
          handler.sse.emit('fileModified', 'changed-file-path.js')
      ), 3000);

      You can also use the nokit.log on the browser to log to the remote server.

      nokit.log({ any: 'thing' });
  • Create a static file middleware for proxy.flow.

    • param: opts { String | Object }

      Same as the send's. It has an extra option { onFile: (path, stats, ctx) => void }.

    • return: { Function }

      The middleware handler of porxy.flow.

      let proxy = kit.require('proxy');
      let http = require('http');
      
      let middlewares = [proxy.select({ url: '/st' }, proxy.static('static'))]
      
      http.createServer(proxy.flow(middlewares)).listen(8123);
  • Send or receive any size of package over a socket. Add a writeFrame method and a frame event to net.Socket object. The writeFrame's signature is same with the net.Socket.write. The frame event is the same with the native stream's data event.

    • param: socket { net.Socket }

      The nodejs native net.Socket.

    • param: opts { Object }

      Defaults

      {
          // The extra first chunk to be used as part of a frame
          head: Buffer
      }
  • Use it to proxy one url to another.

    • param: opts { Object | String }

      Other options, if it is a string, it will be converted to { url: opts }. Default:

      {
          // The target url forced to. Optional.
          // Such as proxy 'http://test.com/a' to 'http://test.com/b',
          // proxy 'http://test.com/a' to 'http://other.com/a',
          // proxy 'http://test.com' to 'other.com'.
          // It can also be an url object. Such as
          // `{ protocol: 'http:', host: 'test.com:8123', pathname: '/a/b', query: 's=1' }`.
          url: null,
      
          // Mutate the url before the proxy take charge of it.
          handleUrl: (url) => url,
      
          agent: customHttpAgent,
      
          // Force the header's host same as the url's.
          isForceHeaderHost: false,
      
          // The request data to use. The return value should be stream, buffer or string.
          handleReqData: (req) => req.body || req
      
          // You can hack the headers before the proxy send it.
          handleReqHeaders: (headers, req) => headers
          handleResHeaders: (headers, req, proxyRes) => headers,
      
          // Same option as the `kit.request`'s `handleResPipe`.
          handleResPipe: (res, stream) => stream,
      
          // Manipulate the response body content of the response here,
          // such as inject script into it. Its return type is same as the `ctx.body`.
          handleResBody: (body, req, proxyRes) => body,
      
          // Only when the `content-type` matches, handleResBody will work
          handleResBodyMIME: /text|json|javascript|css|xml/
      
          resPipeError: (res) => void,
      
          rejectUnauthorized: true,
      
          // It will log some basic error info.
          error: (e, req) => {}
      }
    • return: { Function }

      (req, res) => Promise A middleware.

    • example:

      let kit = require('nokit');
      let proxy = kit.require('proxy');
      let http = require('http');
      
      http.createServer(proxy.flow(
          // Transparent proxy
          proxy.select({ url: '/a' }, proxy.url()),
      
          // Porxy to `a.com`
          proxy.select({ url: '/b' }, proxy.url({ url: 'a.com' })),
      
          // Porxy to a file
          proxy.select({ url: '/c' }, proxy.url({ url: 'c.com/s.js' })),
      
          proxy.select(
              { url: /$/, method: 'GET' },
              proxy.url({
                  url: 'd.com',
                  // Inject script to html page.
                  handleResBody: (body, req, res) => {
                      if (res.headers['content-type'].indexOf('text/html') > -1)
                          return body + '<script>alert("test")</script>';
                      else
                          return body;
                  }
              })
          )
      ).listen(8123);
  • Add a van method to flow context object. It's a helper to set and get the context body.

    • param: ctx { FlowContext }

sse

  • A Server-Sent Event Manager. For more info see Using server-sent events. It is used to implement the live-reload of web assets.

    • param: opts { Object }

      Defaults:

      {
       // The reconnection time to use when attempting to send the event, unit is ms.
       retry: 1000
      }
    • example:

      Your server side code may look like this:

      let http = require('http');
      let kit = require('nokit');
      let sse = kit.require('sse');
      let sseHandler = sse();
      
      sseHandler.onConnect = ({ req }) => {
          console.log('client connected: ', req.url)
      }
      
      http.createServer((req, res) => {
          if (req.url === '/sse')
              sseHandler(req, res);
          else
              res.end();
      }).listen(8080, () =>
          setTimeout(() =>
              sseHandler.emit('test', { test: 'ok' })
          );
      );

      You browser code should be something like this:

      let es = new EventSource('/sse');
      es.addEventListener('test', (e) => {
          let msg = JSON.parse(e.data);
          console.log(msg); // => { test: 'ok' }
      });
  • The sse middleware for http handler.

    • param: req { http.IncomingMessage }

      Also supports Express.js.

    • param: res { http.ServerResponse }

      Also supports Express.js.

  • The sessions of connected clients.

    • type: { Array }
  • Broadcast a event to all clients.

    • param: event { String }

      The event name.

    • param: msg { Object | String }

      The data you want to emit to session.

    • param: [path] { String }

      The namespace of target sessions. If not set, broadcast to all clients.

  • Create a sse session.

    • param: req { http.IncomingMessage }

      Also supports Express.js.

    • param: res { http.ServerResponse }

      Also supports Express.js.

    • return: { SSESession }

  • A session object is something like:

    {
     req,  // The http req object.
     res   // The http res object.
    }
  • Emit message to client.

    • param: event { String }

      The event name.

    • param: msg { Object | String }

      The message to send to the client.

drives

Quick Start

Here it will automatically lint, compile, compress and cache files by their extensions. You can goto Drives section to see what extensions are supported, or write your own.

let kit = require('nokit');
let drives = kit.require('drives');

kit.warp('src/**/*.@(jade|less|coffee|ls)')
    // // To disable cache.
    // .load drives.reader isCache: false
    .load(drives.auto('lint'))
    .load(drives.auto('compile', { '.coffee': { bare: false } }))
    .load(drives.auto('compress'))
    .load(concat('main.js'))
.run('dist/path');

Write your own drives

Nokit has already provided some handy example drives, you can check them in the Drives section. It's fairly easy to write your own.

let kit = require('nokit');
let coffee = require('coffee-script');

// A drive for coffee, a simple curried function.
let compiler = (opts) => function () {
    // Change extension from '.coffee' to '.js'.
    this.dest.ext = '.js';
    this.set(coffee.compile(this.contents, opts));
};

// A drive to prepend lisence to each file.
// Here "fileInfo.set" is the same with the "@set".
let lisencer = (lisence) => function (fileInfo) {
    this.set(lisence + '\n' + this.contents)
}

// A drive to concat all files. It will override the default writer.
let concat = (outputFile) => {
    let all = '';

    // Object.assign
    return kit._.assign(function () {
        all += this.contents;
    }, { isWriter: true, onEnd: function () {
        // This will enable the auto-cache.
        this.deps = kit._.pluck(this.list, 'path');

        this.dest = this.to + '/' + outputFile;
        this.set(all);

        // Call the overrided writer.
        // Call two times and create two output files.
        this.super().then(() => {
            this.dest = this.dest + '.info';
            this.set = '/* info */\n' + all;
            this.super();
        });
    } });
};

kit.warp('src/**/*.coffee')
    .load(compiler(bare: true))
    .load(lisencer('/* MIT lisence */'))
    .load(concat('bundle.js'))
.run('dist')
.then(() => {
    kit.log('Build Done');
});
  • The built-in plguins for warp. It's more like examples to show how to use nokit efficiently.

  • clean-css

    • param: opts { Object }

    • return: { Function }

  • coffee-script compiler

    • param: opts { Object }

      Default is { bare: true }.

    • return: { Function }

      / }), coffee: _.extend(function (opts) { if (opts == null) { opts = {}; } _.defaults(opts, { bare: true });

            const coffee = kit.requireOptional('coffee-script', __dirname, '>=1.8.0');
      
            return function () {
                opts.filename = this.path;
                this.deps = [this.path];
                this.dest.ext = '.js';
                try {
                    this.set(coffee.compile(this.contents + '', opts));
                    return kit.log(br.cyan('coffee: ') + this.path);
                } catch (err) {
                    kit.err(br.red(err.stack));
                    return Promise.reject('coffeescriptCompileError');
                }
            };
        }, {
            compile: ['.coffee']
      
            /**
      

      coffeelint processor

    • param: opts { Object }

      It extends the default config of coffeelint, properties:

      {
       colorize: true,
       reporter: 'default',
      
       // The json of the "coffeelint.json".
       // If it's null, coffeelint will try to find
       // "coffeelint.json" as its content.
       config: null | JSON | JsonFilePath
      }
    • return: { Function }

      / }), coffeelint: _.extend(function (opts) { if (opts == null) { opts = {}; } _.defaults(opts, { colorize: true, reporter: 'default' });

            const coffeelint = kit.requireOptional('coffeelint', __dirname);
      
            if (!opts.config) {
                const configfinder = require('coffeelint/lib/configfinder');
                opts.config = configfinder.getConfig();
            }
      
            if (_.isString(opts.config)) {
                opts.config = kit.readJsonSync(opts.config);
            }
      
            const Reporter = require(`coffeelint/lib/reporters/${opts.reporter}`);
      
            return function () {
                this.deps = [this.path];
                const errorReport = new coffeelint.getErrorReport();
                errorReport.lint(this.path, this.contents, opts.config);
                const reporter = new Reporter(errorReport, opts);
      
                for (let path in errorReport.paths) {
                    const errors = errorReport.paths[path];
                    kit.log(br.cyan('coffeelint: ') + _.trim(reporter.reportPath(path, errors)));
                    if (errors.length > 0) {
                        return Promise.reject(errors[0]);
                    }
                }
            };
        }, {
            lint: ['.coffee']
      
            /**
      

      Parse commment from a js, coffee, or livescript file, and output a markdown string.

    • param: path { String }

    • param: opts { Object }

      Defaults:

      {
       // Output doc path.
       out: 'readme.md',
      
       // jst template path.
       tpl: 'readme.jst.md',
      
       // Init doc info.
       doc: {},
      
       // Header size.
       h: 3,
      
       parseComment: () => {},
       formatComment: () => {}
      }
    • return: { Function }

    • example:

      The nofile of nokit shows how to use it. / }), comment2md(opts) { if (opts == null) { opts = {}; } _.defaults(opts, { out: 'readme.md', tpl: 'readme.jst.md', doc: {}, h: 3, parseComment: {}, formatComment: {} });

            return _.extend(function (file) {
                    const toc = [];
                    opts.formatComment.name = function ({
                        name,
                        line
                    }) {
                        name = name.replace('self.', '');
      
                        const tocName = name.toLowerCase()
                            .replace(/\s/g, '-')
                            .replace(/[^\w-]/g, '');
      
                        toc.push(`  - [${name}](#${tocName})`);
      
                        const link = `${file.path}?source#L${line}`;
                        return `- ${_.repeat('#', opts.h)} **[${name}](${link})**\n\n`;
                    };
      
                    const comments = kit.parseComment(this.contents + '', opts.parseComment);
                    opts.doc[this.path] = kit.formatComment(comments, opts.formatComment);
                    return opts.doc[this.path + '-toc'] = toc.join('\n');
                }
      
                , {
                    isWriter: true,
                    onEnd(file) {
                        if (_.keys(opts.doc).length < this.list.length) {
                            return;
                        }
      
                        this.deps = _.map(this.list, 'path');
                        this.deps.push(opts.tpl);
      
                        this.dest = kit.path.join(this.to, opts.out);
      
                        return kit.readFile(opts.tpl, 'utf8')
                            .then(tpl => file.set(_.template(tpl)({
                                doc: opts.doc
                            })))
                            .then(function () {
                                kit.log(br.cyan('comment2md: ') +
                                    kit.path.join(file.to, opts.out)
                                );
                                return file.super();
                            });
                    }
                }
            );
        },
      
        /**
      

      Auto-compiler file by extension. It will search through kit.drives, and find proper drive to run the task. You can extend kit.drives to let it support more. For example:

      kit.drives.myCompiler = kit._.extend(() => {
          // your compile logic
      }), { compiler: ['.jsx'] })
    • param: action { String }

      By default, it can be 'compile' or 'compress' or 'lint'

    • param: opts { Object }

      {
       // If no compiler match.
       onNotFound: (fileInfo) => {}
      }
    • return: { Function }

  • Change dest path with a filter.

    • param: dir { String }

    • param: filter { Function }

      (fileInfo, dir) -> Boolean

    • return: { Function }

  • a batch file concat helper

    • param: name { String }

      The output file path.

    • param: dir { String }

      Optional. Override the dest of warp's.

    • return: { Function }

  • Suffix file name with the hash value of file content.

    • param: hashMapPath { String }

      The output file name hash map.

    • return: { Function }

  • Lint js via jshint.

    • param: opts { Object }

      Properties:

      {
       global: null,
       config: null | JSON | JsonFilePath
      }
    • return: { Function }

  • Compile less.

    • param: { Object }

    • return: { Function }

      / } ), less: _.extend(function (opts) { if (opts == null) { opts = {}; } const less = kit.requireOptional('less', __dirname, '>=2.5.1');

            return function (file) {
                this.dest.ext = '.css';
                opts.filename = this.path;
                return less.render(this.contents + '', opts)
                    .then(function (output) {
                        file.deps = [file.path].concat(output.imports);
                        file.set(output.css);
                        return kit.log(br.cyan('less: ') + file.path);
                    }, function (err) {
                        if ((err.line == null)) {
                            return Promise.reject(err);
                        }
                        // The error message of less is the worst.
                        err.message = err.filename +
                            `:${err.line}:${err.column}\n` +
                            (err.extract != null ? err.extract.join('\n') : undefined) + '\n--------\n' +
                            err.message;
                        return Promise.reject(err);
                    });
            };
        }, {
            compile: ['.less']
      
            /**
      

      LiveScript compiler.

    • param: opts { Object }

      Default is { bare: true }.

    • return: { Function }

      / }), livescript: _.extend(function (opts) { if (opts == null) { opts = {}; } _.defaults(opts, { bare: true });

            const LiveScript = kit.requireOptional('LiveScript', __dirname, '>=1.2.0');
      
            return function () {
                this.deps = [this.path];
                opts.filename = this.path;
                this.dest.ext = '.js';
                try {
                    this.set(LiveScript.compile(this.contents + '', opts));
                    return kit.log(br.cyan('livescript: ') + this.path);
                } catch (err) {
                    kit.err(br.red(err));
                    return Promise.reject('livescriptCompileError');
                }
            };
        }, {
            compile: ['.ls']
      
            /**
      

      read file and set contents

    • param: opts { Object }

      Defaults:

      {
       isCache: false,
       encoding: 'utf8',
       cacheDir: '.nokit/warp'
      }
    • return: { Function }

      / }), reader(opts) { if (opts == null) { opts = {}; } _.defaults(opts, { isCache: false, encoding: 'utf8', cacheDir: '.nokit/warp' });

            if (jhash == null) {
                jhash = new(kit.require('jhash').constructor);
            }
      
            // Create a unique id for each workflow.
            const hashDrives = function (ds) {
                const str = _.map(ds, d => d.toString()).join();
                return jhash.hash(str, true) + '';
            };
      
            const read = function () {
                return kit.readFile(this.path, opts.encoding)
                    .then(this.set);
            };
      
            return _.extend(function (file) {
                if (!this.list.cacheDir) {
                    this.list.isCache = opts.isCache;
                    this.list.cacheDir = kit.path.join(opts.cacheDir,
                        hashDrives(this.driveList));
                }
      
                if (this.isDir) {
                    return;
                }
                if (opts.isCache) {
                    return kit.depsCache({
                        deps: [this.path],
                        cacheDir: this.list.cacheDir
                    }).then(function (cache) {
                        file.deps = cache.deps;
                        if (cache.isNewer) {
                            kit.log(br.green('reader cache: ') +
                                file.deps.join(br.grey(', '))
                            );
                            file.drives.length = 0;
      
                            return Promise.all(_.map(cache.dests, (cachePath, dest) =>
                                kit.mkdirs(kit.path.dirname(dest))
                                .then(() =>
                                    kit.link(cachePath, dest)
                                    .catch(function (err) {
                                        if (err.code !== 'EEXIST') {
                                            return Promise.reject(err);
                                        }
                                    })
                                )
                            ));
                        } else {
                            return read.call(file);
                        }
                    });
                } else {
                    return read.call(file);
                }
            }, {
                isReader: true
            });
        },
      
        /**
      

      Compile stylus.

    • param: opts { Object }

      It will use stylus.set to iterate opts and set the key-value, is the value is not a function.

      {
       config: (styl) => {}
      }
    • return: { Function }

    • example:

      kit.drives.stylus({
       compress: true,
       config: (styl) =>
           styl.define('jack', 'a persion')
      });

Contribution

Unit Test

npm test or npm run no -- test

Others

Run npm run no -- -h for all command you can use. Such as run npm run no -- build to build this project.

Docs

Edit the templete of the readme at doc/readme.jst.md.

Lisence

MIT

About

A light weight set of handy tools for real world program.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published