npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@recli/cli

v1.0.23

Published

Tool to create tasks in node js

Readme

⌘ ReCli generator

Powerful but simple cli boilerplate generator

  • Clear: Human readable config file do what you mean
  • Useful: Can insert code in already existing modules
  • Flexible: Can transform, reuse and extend answers on the fly

Table of Contents

Motivation

Reduce file creation routine from dev process for one side. From other is to increase the code quality by automating new code injection. It allows you to have strict code agreements cross over their project, and decrease the onboarding process.

Install

$ npm install --save @recli/cli

or if you use yarn

$ yarn add @recli/cli

Running from command line

You can run ReCli directly from the CLI (if it's globally available in your PATH, e.g. by yarn global add @recli/cli or npm install @recli/cli --global) with a variety of useful options.

Here's how to run ReCli and have debug output:

recli --test

Create generator

After install you can start using it

// using ES6 modules
import { cliOf } from '@recli/cli'

// using CommonJS modules
const { cliOf } = require("@recli/cli");

Usage

On init ou must pass module to second argument.

For work with user inputs used Inquirer.js. You can freely use it api his API as is.

cliOf('Create something', module) // global node.js module
  .ask({
    name: 'variableName',
    message: 'How we name it?',
    type: 'input'
  })

API

Full API for generators are here:

const { cliOf, useImport, usePath, useCustom, file, childProcess } = require("@recli/cli");

cliOf('Create reducer', module)
  .ask({
    name: 'reducerName',
    message: 'Reducer name',
    type: 'input'
  })
  .setKey('key')
  .ask({
    name: 'model',
    message: 'Reducer data model',
    type: 'input'
  })
  .setAnswers((answers) => {
    // extend answers object with new data
    return {
      reducerName: answers.reducerName,
      model: answers.model,
      upperCaseReducerName: answers.reducerName.toUpperCase(),
      otherVariable: 'My name is John Cena',
    }
  });
  .move(['./reducer.template.js'], '../../fake/destination')
  .rename('../../fake/destination/reducer.template.js', (answers) => {
    return `${answers.reducerName}.js`;
  })
  .useHooks('../../fake/destination/store.js', (answers) => [
    useImport(`./${answers.file2}`, answers.file2),
    usePath(`./${answers.file2}`),
    useCustom({regex, content}),
  ])
  .ask({
    message: 'Witch styles do you use?',
    type: 'list',
    name: 'style',
    choices: [
      {name: 'style.template.scss', value: 'Scss'},
      {name: 'style.template.less', value: 'Less'},
    ],
  })
  .call((answers) => {
    // do any wiered stuff you need,
    // use setAnswers other vice to store result
    return axios.get(`./ping-hook/?${answers.style}`)
  })
  .check((answers, goTo) => {
    if (answers.continue) {
      goTo('begining')
    }
  })
  .move((answers) => [
    {from: './' + answers.style, to: 'style/' + answers.style}
  ], '../../fake/destination')
  //---
  .ask({
    name: 'nestedGenerator',
    message: 'Pick one of nesteds...',
    type: 'list',
    choices: () => file.getAvailableGenerators('examples/generators/**/index.js'),
  })
  .useGenerator((answers) => require(answers.nestedGenerator))
  //---
  .ask({
    name: 'pickTask',
    message: 'Which task does we run?',
    type: 'list',
    choices: () => {
      return [
        'npm run build',
        'tsc --emitDeclarationOnly'
      ];
    }
  })
  .call(async (answers) => {
    await childProcess.spawn(answers.pickTask);
  })

Notes all callback functions can be async or return promise, to apply pause on the task.

Formatters & validators

Case conversion:

var { to } = require('@recli/cli')

to.camel('what_the_heck')      // "whatTheHeck"
to.capital('what the heck')    // "What The Heck"
to.constant('whatTheHeck')     // "WHAT_THE_HECK"
to.dot('whatTheHeck')          // "what.the.heck"
to.inverse('whaT tHe HeCK')    // "WHAt ThE HeCK"
to.lower('whatTheHeck')        // "what the heck"
to.pascal('what.the.heck')     // "WhatTheHeck"
to.sentence('WHAT THE HECK.')  // "What the heck."
to.slug('whatTheHeck')         // "what-the-heck"
to.snake('whatTheHeck')        // "what_the_heck"
to.space('what.the.heck')      // "what the heck"
to.title('what the heck')      // "What the Heck"
to.upper('whatTheHeck')        // "WHAT THE HECK"

Validators:

var { validation } = require('@recli/cli')

cliOf('validation', module)
  // adding question
  .ask({
    name: 'libraryName',
    message: 'Please enter the name of module you want to import.',
    type: 'input',
    validate: validation.validate([
      validation.isNoWhitespace,
      validation.isDashFormat,
      validation.isFirstLetterUpper,
      validation.isFirstLetterLower,
      validation.isFileIsNew('directory/**/components'),
      validation.isCamelFormat
    ]),
    default: 'lodash'
  })

⚛️ Hooks

The core feature all around is code injectors to existed files. We call it hooks, to be on hype.

It works like so. Let's imagine you have a file called router.js. After new route generation you want to append new route here.

So, let's add some hook to the router.js,

import React from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";

import { HomeRoute } from './home-route';
/* recli:use-import */

function AppRouter() {
  return (
    <Router>
        <Route path="/about/" component={HomeRoute} />
        {/* recli:use-route */}
      </div>
    </Router>
  );
}

export default AppRouter;

So step after generation we expect to have like this

import React from "react";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";

import { HomeRoute } from './home-route';
import { NewRoute } from './new-route';
/* recli:use-import */

function AppRouter() {
  return (
    <Router>
        <Route path="/about/" component={HomeRoute} />
        <Route path="/new-route/" component={NewRoute} />
        {/* recli:use-route */}
      </div>
    </Router>
  );
}

export default AppRouter;

To make it done we have hooks:

  • useImport -> useImport({ NewRoute }, ./new-route) -> import { NewRoute } from './new-route';
  • usePath -> usePath(./new-route) -> './new-route',
  • useModuleName -> useModuleName(NodeModule) -> NodeModule,
  • useCustom -> useCustom({ regex, content }) -> content

they are applied to file by

  cliOf('My awesome task')
    ...
    .useHooks('path', (answers) => [
      useImport(`{${answers.camelCaseName}}`, `./${answers.name}.js`),
      usePath(`./${answers.name}.js`),
      useCustom({
        regex: /(\s*)(\/\*.*recli:use-module-name.*\*\/)/,
        content: `$1${moduleName}$1$2,`,
      }),
    ])

🚀 Setup

npm install @recli/cli
// or
yarn add @recli/cli

it's possible to use it by global setup

npm install @recli/cli -g
// or
yarn add @recli/cli -g

Placing generators right way

The default agreements you should have generators folder at project root with your own generators are inside folders.

Other words, your generators should match the path: generators/**/index*

To override default behavior is simple (following same markup: https://storybook.js.org/docs/guides/guide-react/)

To do that, create a file at .recli/config.js with the following content:

const { configure } = require("@recli/cli");

function loadStories() {
  require("../generators/**/index*");
  // You can require as many stories as you need.
}

configure(loadStories, module);

But the full file path will resolved to node.js module and will execute it.

Let's say you wan't to have generators like stand alone module, to share it cross over the projects you have. Let's say it have name: @recli/xxx-generators

The code markup can looks like:

  • create a file at index.js with the following content:

    const { configure } = require("recli");
    
    function loadStories() {
      require("./generators/**/*.gen.js");
    }
    
    configure(loadStories, module);
  • make sure you have package.json main section like that:

    "main": "index.js",
  • the @recli/cli will try to find the generators in that node module by using the path you prodive at index.js

Tech notes

You can't use modern syntax different to Node.js you have installed. Cause we doesn't use babel, webpack inside.

@recli/cli is written by using TS. So you will receive the extra IDE help by using TS for generators. But, you have to compile them. It should be simple for stand alone set of generators.


MIT