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 🙏

© 2024 – Pkg Stats / Ryan Hefner

cloe

v0.1.3

Published

framework for building developer tools on the cli and in editors

Downloads

56

Readme

A framework for building developer tools on the cli and in editors.

It's not quite ready for use yet, and will likely change a lot between now and then, but maybe this will get you excited!

Install

I recommend installing cloe in each project where you have some custom code for it, and a global install can be used for running cloe in arbitrary places.

npm install --save-dev cloe
npm install --global cloe

# or
yarn add --dev cloe
yarn global add cloe

CLI

The basic interface to cloe is on the command line. We'll be using some simple commands, but there's much more to cloe, so continue reading! First, let's create a file.

$ echo "Hello, world!" > test.txt

Then we'll invoke a "cloe behavior" on the file.

$ cloe -f test.txt readFile
Hello, world!

Here, the -f flag tells cloe which file(s) to execute the command on. Then we have a positional argument for the command to run, in this case, it's "readFile".

Some commands take arguments. To specify them, we use square brackets. Make sure to leave a space around the brackets.

$ cloe [ cloe.echo "Anybody there?" ]
Anybody there?

Concepts

At the core of cloe you'll find "behaviors". Many are defined for you, and you can define your own. Most importantly, you can override behaviors per-project.

Let's look at a simplified version of readFile, which we used earlier. Built-in behaviors are prefixed with 'cloe.', but you can define your own with any name you like.

The file needs to be named readFile.js, and reside in a directory cloe knows to look in. In this case, the file can be found in src/behaviors/readFile.js.

import * as fs from 'fs';
import promisify from 'util.promisify';

const readFile = promisify(fs.readFile);

export default async function cloeReadFile(ctx) {
  // Get the file we're currently running on
  const filePath = ctx.filePath;

  // Attempt to read the file
  try {
    content = await readFile(filePath, encoding);
  } catch (e) {
    // Handling errors is good, as we can communicate more clearly what went wrong.
    if (e.code === 'EISDIR') {
      throw ctx.error(`Tried to read a directory`, { reason: 'is_directory' });
    }
    throw e;
  }

  return content;
}

// Reads the file, prints it to screen
export async function main(ctx) {
  const content = await ctx.exec(`readFile`);
  console.log(content);
}

There are two functions here. Think of them like a library and and a program respectively. The cloeReadFile function can be run by other behaviors, allowing you to compose them to accomplish any task. The main function is invoked when we, the user, directly invoke the behavior readFile, e.g. via the CLI.

There's quite a lot going on in this code, but we're still covering the core concepts! More on that and this 'ctx' thing later.

Files

Many default implementations of behaviors exist in the cloe repo, but these implementations might not fit your requirements or project. Because behaviors are kept small, relying on other behaviors when possible, and avoiding duplication, you can pick specific things to override in your project. You can also define new behaviors in your project, or across all projects on your computer.

Let's take a look at another behavior called "resolveImport". The implementation doesn't matter, but the usage string in the source says "Usage: resolveImport './foo.js'". We can attempt to run it from the CLI. For this example, assume we have src/app/foo.js and it contains import illusive from '~src/utils/illusive';. Let's run a behavior that should (cross your fingers) give us the absoltue path to this illusive.js file.

$ cloe -f src/app/foo.js [ resolveImport '~src/utils/illusive.js' ]
resolveImport: Failed to resolve module
 - resolveImport(
    "~src/utils/illusive.js",
   )

Hey, wait a minute! What is this '~' thing? Well, cloe doesn't know either. The default implementation of resolveImport uses the node.js resolve algorithm, but it seems we're using a custom way of resolving modules; perhaps a babel plugin or webpack loader (yes, I actually do this in some projects, it's really great).

We'd still like to use the rest of cloe, we don't want to fork the whole thing, and we want everything that tries to resolve a JS import to use our custom implementation - cloe core files, our own files, maybe some things we got from npm, etc.

Easy enough; we'll copy over the file from cloe, and make the changes. To do this, we'll create a new directory in our project to keep our cloe files.

mkdir -p .cloe

Then we can take the default implementation, and put it in ./.cloe/resolveImport.js. I've added a small fix to handle our custom imports.

import relative from 'require-relative';

/*
  Usage: resolveImport './foo.js'

  Returns: null
*/
export default async function cloeJsResolveImport(ctx, importPath) {
  let filePath = ctx.filePath;

  if (!filePath) throw ctx.error(`Must be run on a file`, { reason: 'no_ctx_file' });

  // THIS IS OUR CHANGE
  if (filePath[0] === '~') {
    filePath = ctx.cwd + '/' + filePath.slice(1);
  }
  // END OF CHANGE

  const fromFile = filePath.replace(/\/[^\/]+$/, '');
  try {
    return relative.resolve(importPath, fromFile);
  } catch (e) {
    if (/Cannot find module/.test(e.message)) {
      throw ctx.error(`Failed to resolve module`, { reason: 'not_found' });
    }
    throw e;
  }
}

Now any time resolveImport is invoked in this project - directly or indirectly - it'll use our custom implementation.

Let's give it a whirl.

$ cloe -f src/app/foo.js [ resolveImport '~src/utils/illusive.js' ]
/home/me/projects/example/src/utils/illusive.js

Success! This is all the documentation there is so far. Feel free to dive into the source, and file a github issue if you need help. All feedback is very much appreciated.