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

boilup

v0.0.0-alpha.3

Published

Boilerplate code generator

Downloads

1

Readme

Boilup

Boilup is a tool for generating boilerplate code for projects. It allows you to build custom actions consisting of various methods that run in different stages. Each action can have child actions, providing a flexible and extensible system.

Features

  • Generate boilerplate code for projects
  • Build custom actions
  • Support for action stages
  • Extensible and flexible

Installation

Install Boilup using your package manager:

pnpm add boilup

Usage

Boilup works in stages, each stage serving a specific purpose in the boilerplate generation process.

Stages

  1. Loading Actions - Loads the specified actions.

  2. Collecting Data - Used for running terminal prompts for the user or fetching any other information needed for further processing.

  3. Executing Actions - Runs the action, stacking files and their contents to be written.

  4. Writing Files - Writes all stacked files to the disk.

  5. Post-write - Runs additional actions after files are written, such as installing packages, running command line scripts, validating output, or displaying final messages.

Creating Actions

Actions are the core of Boilup's functionality. Each action can be defined with methods that run at different stages. Here's a basic example of defining an action:

import { exists } from 'node:fs'
import { installDependencies } from 'nypm'
import { input } from '@inquirer/prompts'
import type { BoilupCanLoadMethod, BoilupAction, BoilupActionMethod, BoilupCollectDataMethod, BoilupPostWriteMethod } from 'boilup'

const moduleName = 'create-package-json'

export interface PackageJsonData {
  name: string
  version: string
  author: string
}

const canLoad: BoilupCanLoadMethod = async () => {
  if (await exists('./package.json')) {
    return false
  }
}

const collectData: BoilupCollectDataMethod<PackageJsonData> = async () => {
  const name = await input({ message: `Package name` })
  const version = await input({ message: `Version` })
  const author = await input({ message: `Author` })

  return { name, version, author }
}

const action: BoilupActionMethod<PackageJsonData> = async ({ data, files }) => {
  const template = {
    name: data.name,
    version: data.version,
    author: data.author,
    type: 'module',
    dependencies: {
      'typescript': '^5.4.5'
    },
    devDependencies: {
      '@types/node': '^20.14.5'
    }
  }

  const packageJsonString = JSON.stringify(template, null, 2)

  files.write('./package.json', packageJsonString, moduleName)
}

const postWrite: BoilupPostWriteMethod<PackageJsonData> = async () => {
  await installDependencies()
  console.log('Main package.json file successfully created!')
}

export const createPackageJson: BoilupAction<PackageJsonData> = {
  name: moduleName,
  description: 'Generates package.json file with provided data.',
  canLoad,
  collectData,
  action,
  postWrite
}

Running Boilup

To run Boilup with your defined actions:

import { run } from 'boilup'
import createPackageJson from './create-package-json.action.ts'

run(
  [ createPackageJson],
  {
    outDir: './new-project',
    debug: true,
    dryRun: true
  }
);

You can also run all the stages by yourself:

import { createContext, loadActions, collectData, executeActions, write, postWrite, type Context, type ContextOptions, type BoilupAction } from 'boilup'

async function init() {
  const context: Context = createContext({ ... })

  const loadedActions = await loadActions(context, [ ... ])
  
  const data = await collectData(context, loadedActions)

  await executeActions(context, loadedActions, data)
  
  write(context)
  
  await postWrite(context, loadedActions, data)
}

init()

Boilup API Documentation

Boilup Methods

BoilupCanLoadMethod

type BoilupCanLoadMethod = (params: BoilupCanLoadMethodParams) => Promise<boolean | undefined>;

interface BoilupCanLoadMethodParams {
  files: OutputCollection;
  logger: Logger;
  context: Context;
  actions: BoilupAction[];
}

Checks whether an action and its child actions should be loaded. Returns true to load, false to skip, or undefined for default behavior.

BoilupCanCollectDataMethod

type BoilupCanCollectDataMethod = (params: BoilupCanCollectDataMethodParams) => Promise<boolean | undefined>;

interface BoilupCanCollectDataMethodParams {
  files: OutputCollection;
  logger: Logger;
  context: Context;
  upToNowData: CollectedData;
}

Determines if the data collection step should be performed. Returns true to collect data, false to skip, or undefined for default behavior.

BoilupCollectDataMethod

type BoilupCollectDataMethod<A> = (params: BoilupCollectDataMethodParams) => Promise<A>;

interface BoilupCollectDataMethodParams {
  files: OutputCollection;
  logger: Logger;
  context: Context;
  upToNowData: CollectedData;
}

Collects necessary data for future processing. The return value is added to fullData under the action's name.

BoilupCanExecuteActionMethod

type BoilupCanExecuteActionMethod = (params: BoilupCanExecuteMethodParams) => Promise<boolean | undefined>;

interface BoilupCanExecuteMethodParams {
  files: OutputCollection;
  logger: Logger;
  context: Context;
  fullData: CollectedData;
}

Determines if the action should be executed. Returns true to execute, false to skip, or undefined for default behavior.

BoilupActionMethod

type BoilupActionMethod<A> = (params: BoilupActionMethodParams<A>) => Promise<void>;

interface BoilupActionMethodParams<A> {
  files: OutputCollection;
  logger: Logger;
  context: Context;
  fullData: CollectedData;
  data: A;
  actions: BoilupAction[];
}

Main logic of an action, processing the collected data and performing tasks.

BoilupPostWriteMethod

type BoilupPostWriteMethod<A> = (params: BoilupPostWriteMethodParams<A>) => Promise<void>;

interface BoilupPostWriteMethodParams<A> {
  files: OutputCollection;
  logger: Logger;
  context: Context;
  data: A;
  fullData: CollectedData;
}

Additional tasks executed after all files are written.

BoilupAction Interface

interface BoilupAction<A extends Record<string, any> = any> {
  name: string;
  description: string;
  subActions?: BoilupAction[];
  canLoad?: BoilupCanLoadMethod;
  canCollectData?: BoilupCanCollectDataMethod;
  collectData?: BoilupCollectDataMethod<A>;
  canExecuteAction?: BoilupCanExecuteActionMethod;
  action?: BoilupActionMethod<A>;
  postWrite?: BoilupPostWriteMethod<A>;
}

Defines a Boilup action with the following properties:

  • name: string - The name of the action.
  • description: string - A description of the action.
  • subActions: BoilupAction[] - Optional child actions.
  • canLoad: BoilupCanLoadMethod - Optional method to check if the action should be loaded.
  • canCollectData: BoilupCanCollectDataMethod - Optional method to check if data should be collected.
  • collectData: BoilupCollectDataMethod - Optional method to collect necessary data.
  • canExecuteAction: BoilupCanExecuteActionMethod - Optional method to check if the action should be executed.
  • action: BoilupActionMethod - Optional method for the main logic of the action.
  • postWrite: BoilupPostWriteMethod - Optional method for tasks after all files are written.

Writing Files in Boilup Actions

In all methods of an action, you have access to the current list of files in the context. This allows you to manage files that are about to be created or modified.

Adding New Files

The files.write method accessible from the context doesn't write the file to the disk immediately. Instead, it stacks the new content for the file path, enabling you to modify files later or debug the file changes before they are written.

Example

async function action(currentData: A, fullData: CollectedData, actions: BoilupAction[], context: Context) {
  const { files } = context;

  const time = await fetch('http://worldtimeapi.org/api/timezone/Europe/Warsaw');
  
  files.write('./created-at.txt', time.datetime);
}

It's a good practice to provide the origin of the change, usually the name of the action, but you can specify a custom name.

Example

files.write('./README.md', contents, 'readme-md-generator');

Editing Stacked Files

You can edit a file that has already been created using the write method.

Example

async function action(currentData: A, fullData: CollectedData, actions: BoilupAction[], context: Context) {
  const { files } = context;

  const [versionPath, versionContents] = files.get((path) => path.includes('version.json'));
  
  if (versionContents) {
    const parsedVersionContents = JSON.parse(versionContents);
    parsedVersionContents.version = 'v.2.0.0';
    files.write(versionPath, JSON.stringify(parsedVersionContents), 'version-bumper');
  }
}

Debugging Changes

You can also track the changes made to a file.

Example

async function action(currentData: A, fullData: CollectedData, actions: BoilupAction[], context: Context) {
  const { files } = context;

  const changes = files.getChanges('./package.json');
  
  // Compare the last and penultimate changes
  changes.diff(-1, -2); // Prints colored diff in the console
}

Or you can check which action caused the last change.

Example

async function action(currentData: A, fullData: CollectedData, actions: BoilupAction[], context: Context) {
  const { files } = context;

  const packageJsonChanges = files.getChanges('./package.json');

  const change = packageJsonChanges.get(-1);

  console.log('Change was performed by', change.origin, 'at', (new Date(change.timestamp)).toLocaleTimeString());
}