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

clonit

v0.2.0

Published

Scaffolding without the fuss.

Readme

clonit

Scaffolding without template languages

npm version npm downloads

Maintain your scaffolding templates as real, working projects. No more {{mustache}} variables breaking your code.

The Problem

Traditional scaffolding tools require you to maintain templates filled with placeholder syntax:

// package.json.mustache
{
  "name": "{{projectName}}",
  "version": "{{version}}",
  {{#useTypeScript}}
  "devDependencies": {
    "typescript": "^5.0.0"
  }
  {{/useTypeScript}}
}

This approach creates cascading complexity:

🚫 Broken Developer Experience

  • Your IDE can't validate the JSON
  • Linters and formatters fail
  • Syntax highlighting breaks
  • No autocomplete or IntelliSense

🚫 Fragmented Template Structure

_templates/
  component/
    new/
      index.ejs.t         # Generator metadata
      component.ejs.t     # Template with EJS syntax
      test.ejs.t          # Another template file
      prompt.js           # Separate prompt logic

Multiple file types, each with different syntax and rules.

🚫 Template Logic Complexity

---
to: src/components/<%= name %>/index.tsx
---
<%_ if (withStyles) { _%>
import styles from './<%= name %>.module.css'
<%_ } _%>

export const <%= Name %> = () => {
  return (
    <div<% if (withStyles) { %> className={styles.container}<% } %>>
      <%= name %>
    </div>
  )
}

Template tags scattered throughout your code, making it unreadable and unmaintainable.

🚫 Testing Impossibility

  • Can't run or test templates directly
  • No way to validate output before generation
  • Debugging requires trial and error

The Solution

Clonit takes a fundamentally different approach: your templates are real, working projects.

✅ Real Projects as Templates

// package.json - not a template, a real file!
{
  "name": "my-template",
  "version": "1.0.0",
  "devDependencies": {
    "typescript": "^5.0.0"
  }
}
  • Full IDE support: linting, formatting, autocomplete
  • Run npm install and npm test on your template
  • Commit and version control like any other project

✅ Transformations as Code

import { create, fromFS } from 'clonit'

const ctx = await create(fromFS('./template'))

// Transform with familiar JavaScript APIs
await ctx.updateJson('package.json', pkg => ({
  ...pkg,
  name: projectName
}))

// Conditional logic is just JavaScript
if (!useTypeScript) {
  await ctx.delete('tsconfig.json')
  await ctx.updateJson('package.json', pkg => {
    delete pkg.devDependencies.typescript
    return pkg
  })
}

await ctx.out('./my-new-project')

✅ Single Source of Truth

my-template/
  ├── src/
  │   └── index.ts        # Real TypeScript file
  ├── package.json        # Real package.json
  ├── tsconfig.json       # Real config
  └── README.md           # Real documentation

No special directories, no metadata files, no separate prompt logic. Just a regular project.

✅ Maintainability Built-in

  • Test your template: Run the actual project
  • Debug transformations: Use console.log and debugger
  • Refactor safely: Your IDE understands everything
  • Share templates: Just share a GitHub repo or npm package

Features

  • No template language - Keep your templates as real, working projects
  • 🧩 Extensible sources - Use templates from filesystem, git, npm, or anywhere
  • 🛡️ Safe by default - All transformations happen in a temp directory
  • 🔧 Simple API - Intuitive methods for file manipulation
  • 📦 Build your own - Perfect for creating custom create-* packages

Installation

npm install clonit

Quick Start

import { create, fromFS } from 'clonit'

// 1. Create context from a template source
const ctx = await create(fromFS('./my-template'))

// 2. Transform files
await ctx.rename('_gitignore', '.gitignore')
await ctx.updateJson('package.json', pkg => ({
  ...pkg,
  name: 'my-project'
}))

// 3. Write to destination
await ctx.out('./my-project')

Core Concepts

Source Functions

Clonit separates template sources from transformation logic:

// From filesystem
const ctx = await create(fromFS('./templates/react-app'))

// From git repository
const ctx = await create(
  fromGit('https://github.com/vitejs/vite', {
    sparse: ['packages/create-vite/template-react-ts']
  })
)

Transform then Output

All transformations happen in an isolated temp directory:

const ctx = await create(fromFS('./template'))

// Safe transformations
await ctx.rename('README.template.md', 'README.md')
await ctx.update('README.md', content => 
  content.replace('# Template', `# ${projectName}`)
)

// Nothing is written until you call out()
await ctx.out('./my-project')

API Reference

create(source, options?)

Creates a new ClonitContext from a source.

const ctx = await create(fromFS('./template'), {
  keepTemp: false,  // Keep temp directory after out()
  overwrite: false, // Overwrite existing target directory
  dryRun: false    // Simulate operations without writing
})

The source parameter can be:

  • A source function from fromFS() or fromGit()
  • A custom async function that populates the temp directory
  • A string path (for backward compatibility, converted to fromFS())

Source Functions

fromFS(path, options?)

Create a source from filesystem.

fromFS('./template', {
  ignore: ['node_modules', '.git', '*.log']
})

fromGit(repo, options?)

Create a source from git repository.

fromGit('https://github.com/user/repo', {
  branch: 'main',
  tag: 'v1.0.0',
  commit: 'abc123',
  depth: 1,
  sparse: ['packages/template']
})

ClonitContext Methods

File Operations

// Read file content
const content = await ctx.read('README.md')

// Rename files (common for dotfiles)
await ctx.rename('_gitignore', '.gitignore')
await ctx.rename('_env.example', '.env')

// Delete unwanted files conditionally
if (!options.includeTests) {
  await ctx.delete('__tests__')
  await ctx.delete('jest.config.js')
}

// Create files when needed
await ctx.create('src/config.js', `export const API_URL = '${apiUrl}'`)

Content Transformation

// Update text file
await ctx.update('README.md', content => {
  return content.replace(/oldValue/g, 'newValue')
})

// Update JSON file
await ctx.updateJson('package.json', pkg => ({
  ...pkg,
  name: 'new-name',
  version: '1.0.0'
}))

Output

// Write transformed files to target directory
await ctx.out('./my-project')

Building a create-* CLI

Here's a complete example of building your own scaffolding CLI:

#!/usr/bin/env node
import { create, fromFS, fromGit } from 'clonit'
import { select, text } from '@clack/prompts'
import path from 'path'

const templates = {
  minimal: () => fromFS(path.join(__dirname, 'templates/minimal')),
  react: () => fromGit('https://github.com/vitejs/vite', {
    sparse: ['packages/create-vite/template-react-ts']
  })
}

const projectName = await text({
  message: 'Project name?',
  defaultValue: 'my-app'
})

const template = await select({
  message: 'Choose a template',
  options: [
    { value: 'minimal', label: 'Minimal starter' },
    { value: 'react', label: 'React + TypeScript' }
  ]
})

// Create and transform
const ctx = await create(templates[template]())

await ctx.rename('_gitignore', '.gitignore')
await ctx.updateJson('package.json', pkg => ({
  ...pkg,
  name: projectName
}))

// Output
await ctx.out(`./${projectName}`)

console.log(`✨ Created ${projectName}!`)

Publish this as create-my-app and users can run:

npm create my-app
yarn create my-app
pnpm create my-app

Recipes

Conditional Files

if (!options.includeTests) {
  await ctx.delete('__tests__')
  await ctx.delete('jest.config.js')
}

Multiple Package.json Updates

await ctx.updateJson('package.json', pkg => ({
  ...pkg,
  name: projectName,
  description: options.description,
  author: options.author,
  repository: options.git ? { url: options.git } : undefined,
  keywords: options.keywords?.split(',').map(k => k.trim())
}))

Replace Placeholders

// Use simple placeholders in your template files
await ctx.update('README.md', content => 
  content
    .replace(/PROJECT_NAME/g, projectName)
    .replace(/PROJECT_DESCRIPTION/g, description)
    .replace(/AUTHOR_NAME/g, authorName)
)

// Multiple replacements with a map
const replacements = {
  PROJECT_NAME: projectName,
  CURRENT_YEAR: new Date().getFullYear(),
  LICENSE_TYPE: options.license || 'MIT'
}

await ctx.update('LICENSE', content => {
  Object.entries(replacements).forEach(([key, value]) => {
    content = content.replace(new RegExp(key, 'g'), value)
  })
  return content
})

Template-specific Transformations

// React component template
if (componentType === 'functional') {
  await ctx.delete('src/ClassComponent.tsx')
  await ctx.rename('src/FunctionalComponent.tsx', `src/${componentName}.tsx`)
  await ctx.update(`src/${componentName}.tsx`, content => 
    content.replace(/TemplateComponent/g, componentName)
  )
}

// Configure based on options
if (options.useStyledComponents) {
  await ctx.updateJson('package.json', pkg => ({
    ...pkg,
    dependencies: {
      ...pkg.dependencies,
      'styled-components': '^6.0.0'
    }
  }))
}

Comparison with Other Tools

| Feature | clonit | plop/hygen | yeoman | |---------|--------|------------|---------| | Template Language | ❌ None | ✅ Handlebars | ✅ EJS | | Template Testing | ✅ Yes | ❌ No | ❌ No | | IDE Support | ✅ Full | ❌ Limited | ❌ Limited | | Git/Remote Sources | ✅ Yes | ❌ No | ❌ No | | Transform Safety | ✅ Temp dir | ❌ In-place | ❌ In-place |

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT