# template-scaffolder

> multi-file scaffolding with velocity template for any type of project

Latest version **2.3.16** (published 2025-10-17) · MIT license · 0 weekly downloads

## Install

```sh
npm install template-scaffolder
pnpm add template-scaffolder
yarn add template-scaffolder
bun add template-scaffolder
```

Provides the command `scaffolder`.

## Health

**Score 55/100 (C)** — status: stable.

Positive: has types; esm support; no vulnerabilities.

Warnings: low downloads.

## Facts

| | |
|---|---|
| Version | 2.3.16 |
| Published | 2025-10-17 |
| First published | 2022-09-19 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM |
| Node | >=18.0.0 |
| Dependencies | 5 |
| Unpacked size | 580.5 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 1 |
| Author | Shea Martin |
| Maintainers | shea.martin |
| Keywords | typescript, javascript, command-line, cli, devops, command, development, devtools, scaffolding, templates, automation |

## Links

- npm: https://www.npmjs.com/package/template-scaffolder
- Repository: https://github.com/sheam/template-scaffolder
- Homepage: https://github.com/sheam/template-scaffolder#readme
- Issues: https://github.com/sheam/template-scaffolder/issues
- npm.io page: https://npm.io/package/template-scaffolder

## Dependencies (5)

- [glob](https://npm.io/package/glob.md) ^11.0.0
- [velocityjs](https://npm.io/package/velocityjs.md) ^2.0.6
- [@inquirer/prompts](https://npm.io/package/@inquirer/prompts.md) ^7.8.4
- [config-file-ts-async](https://npm.io/package/config-file-ts-async.md) ^1.0.7
- [node-cli-arg-by-config](https://npm.io/package/node-cli-arg-by-config.md) ^0.11.0

## Alternatives

- [@salesforce/cli](https://npm.io/package/@salesforce/cli.md) — 389.7K weekly downloads
- [@mintlify/cli](https://npm.io/package/@mintlify/cli.md) — 208.9K weekly downloads
- [@grafana/e2e-selectors](https://npm.io/package/@grafana/e2e-selectors.md) — 128.7K weekly downloads
- [mintlify](https://npm.io/package/mintlify.md) — 112.0K weekly downloads
- [@intlayer/cli](https://npm.io/package/@intlayer/cli.md) — 22.8K weekly downloads

## Recent versions

- 2.3.16 (latest) — 2025-10-17
- 2.3.15 — 2025-08-29
- 2.3.14 — 2025-02-21
- 2.3.13 — 2025-02-21
- 2.3.12 — 2025-02-21
- 2.3.11 — 2025-02-20
- 2.3.10 — 2025-02-20
- 2.3.9 — 2024-12-09
- 2.3.8 — 2024-10-10
- 2.3.7 — 2024-10-09
- 2.3.6 — 2024-10-09
- 2.3.5 — 2024-10-09
- 2.3.4 — 2024-10-08
- 2.3.3 — 2024-10-08
- 2.3.2 — 2024-10-08
- … 30 more at https://npm.io/package/template-scaffolder/versions

## README

# Introduction

Scaffolding templates for your project.
Make sure your team is using the same patterns by using scaffolding templates.
Quickly create multiple files based on simple templates.
Access to environment variables, users prompts, and scripting.

If you feel like it...

[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/sheamus)

# Release Notes

## Version 2

- support for typescript configuration files (`scaffolding.config.ts`)
- parallel file processing with the `--parallel` option. Testing shows up to 10x faster.
- re-write of the prompt system, as the package we were using for this changed dramatically,
  so the following question types are deprecated: `list` is now `select` and `fuzzypath` is now just `path`.

# Getting Started

1. `npm install template-scaffolder`
2. Create a `scaffolding` directory in the root of your project.
3. Create a sub-folder in the in your _scaffolding_ folder for your (first) template.
4. In the template folder add a `scaffolding.config.mjs` file with
   a simple default configuration of `export default {}` as the contents.
5. Add 1 or more files to the _template_ folder.
6. (optional) Add a script to your `package.json` file to run the scaffolder:
   `"scaffold": "npx scaffolder" `
7. Fine tune your configuration, templates, and npm scripts by reading below.

# Table of Contents

- [Configuration files](#configuration-files)
- [Command Line Options](#command-line)
- [Creating a Template](#making-a-template)
- [Sample Project](#sample-project)

# Configuration Files

## tsconfig.json

You can have a `tsconfig.json` file in your scaffolding directory to control how your
scaffolding configuration files are compiled.

```json
{
  "compilerOptions": {
    "strict": true, // can be true or false
    "module": "CommonJS", // "NodeNext" is also supported
    "target": "ESNext" // this option can not be changed, it helps with your linting if present.
    // all other options are ignored, and only relevant to your IDE.
  }
}
```

**note**: you will have to remove comments because this is a JSON file.

## Scaffolding configuration files

Each template must have exactly one `scaffolding.config` file with one of the following
extensions: `.js, .mjs, .ts, .mts`
in the root. The default export of this file must be an `IConfigFile` (see below for schema).

The typescript schema for this file is:

```typescript
import { TemplateVariables } from './index';

export interface IConfigFile<TInput extends object> {
  name?: string;
  description?: string;
  version?: string;
  variables?:
    | TemplateVariables
    | ((
        instanceName: string,
        initialInputs: TInput
      ) => Promise<TemplateVariables>);
  prompts?:
    | Question<TInput>[]
    | ((
        instanceName: string
      ) => Promise<Question<TInput>[]> | Question<TInput>[]);
  stripLines?: PatternList;
  // eslint-disable-next-line @typescript-eslint/ban-types
  macros?:
    | MacroObject
    | ((
        instanceName: string,
        initialInputs: TInput,
        variables: TemplateVariables
      ) => Promise<MacroObject>);
  destinations?: Array<string> | string;
  createNameDir?: boolean;
  srcRoot?: string;
  afterFileCreated?: (
    createdFilePath: string,
    dryRun: boolean,
    variablesHash: TemplateVariables
  ) => string[];
}
```

**Note:** reference to _'instance name'_ is referring to the _name_ that the user
enters when running the scaffolder. This is not the same as the name of the template.

The defaults will work for most people, so you can have a template
with the following default config:

`scaffolding.config.js`;

```javascript
export default {
  name: 'js-template',
};
```

**OR**

```typescript
'use strict';

import { IConfigFile } from 'template-scaffolder';

export default {
  name: 'ts-template',
} as IConfigFile;
```

Because this is a regular javascript file, you have access to
`process.env`, and any other javascript functions.

## Configuration options

All fields are optional.

- [variables](#variables)
- [destinations](#destinations)
- [createNameDir](#createNameDir)
- [srcRoot](#srcRoot)
- [stripLines](#stripLines)
- [afterFileCreated](#afterFileCreated)
- [prompts](#prompts)
- [macros](#macros)
- [overwrite](#overwrite)

### variables

A hash of keys (variable names) and values that you would like to
use in your template files.

Simple example:

```javascript
export default {
  variables: {
    CREATED_BY: process.env.USERNAME,
    CREATED_ON: new Date().toString(),
    SOMETHING: 'this is really something',
  },
};
```

This can also be a function that returns the object giving
access to the instance name which the user has supplied.
In the function you will have access to the `name` that the user
has provided, as well as values from prompts in the `input` object parameter.

```javascript
export default {
  prompts: [{ name: 'FAV_COLOUR', message: 'Enter favourite color' }],
  variables: (name, inputs) => ({
    COMPONENT_NAME: name,
    TEST_ID: name.replace(/([a-z])([A-Z])/, '$1-$2').toLowerCase(),
    FAV_SHIRT_COLOUR: inputs.FAV_COLOUR,
  }),
};
```

#### Built in Variables

The following variables are available without defining them.
All built in variables except NAME can be overwritten using the _variables_ section.

- NAME - the values for _name_ as entered on command line or through prompts.
- TEMPLATE_NAME - the name of the template as it appears in the `name` fields of the config file.
- TEMPLATE_VERSION - the version of the template of the as it appears in the `version` fields of the config file.
- CURRENT_FILE_PATH - the path of the template file.
- USERNAME - name of the user running the scaffolder.
  (Uses git, and then falls back to the `USERNAME` environment variable).

### destinations

The root folder where generated files will be created.

If no value is supplied, the user will be prompted to select a directory under
`srcRoot`.

The value can be a list of strings, or a single string:

If a list of paths/string is supplied, the user will be prompted to select
one of the options. They will
be allowed to select **Other**, and enter their own directory.

If a single string is supplied, this value will be used for the destination,
and the user will not be prompted for a value.

### createNameDir

_(defaults to `true`)_

By default, the scaffolder will create a directory at the root of the
destination folder named by the value you enter for the `NAME` variable.
The template files will be placed in this created directory.
If you do not want this to happen, set this value to `false`.

### srcRoot

_(defaults to `./src` or `process.cwd()`)_

When the user is being prompted to enter a destination directory,
only directories under the **srcRoot** directory will be available.
By default, the `src` directory in the root of your project will be used
if one exists. If you do not have a `src` directory, then the current
working directory will be used.

Use this value if you need something other than the defaults.

### stripLines

Use for removing lines from the template before processing.
A list of strings or regex patterns used to test each line.
Lines that match any pattern will be removed before processing variables and macros.
If a pattern is a string, the test will determine if the line starts with that string (ignoring whitespace).
For regex patterns, `test()` will be called on the line to determine a match.

### afterFileCreated

If you need some special processing after a file has been scaffolded, you can
use this _async_ function.
It will execute after each file is created.

You can optionally return a list of 1 or more commands to run as well.

An example would be adding the created file to _git_,
running a formatter on the file,
or adding the new file to your IDE's project file, etc.

In the case of a _dry run_, the file will not actually be created,
so you may want to guard against this in your function if it expects it to exist.

The commands that run will not be interactive and should not
expect user input.

```javascript
export default {
  afterFileCreated: async (path, dryRun, variables) => {
    console.log(`${variables.NAME} adding ${path} to git`);
    if (dryRun) return null;
    return [`git add ${path}`, `npx prettier --write ${path}`];
  },
};
```

You can return null if all the processing you require occurs in your function.

### prompts

If your template requires variable values to be entered by the user,
you may prompt the user. The prompts field is an array of
Question objects, or a function that returns an list of
Question objects, as defined by the Inquirer user prompter.
The function form is handy if you need access to the instance name.

You can read more about what a `DistinctQuestion` is by looking at the
(3rd party) **inquirer** documentation [here](https://github.com/SBoudrias/Inquirer.js#questions).

Answers to prompts will be placed available in templates as variables
where the `name` property will be the variable name,
and the user response will be the value.

For a simple question, you will just need two values:

```javascript
export default {
  prompts: [
    {
      name: 'MYVAR',
      message: 'Enter a value for My Var:',
    },
  ],
};
```

There are many types of questions you can prompt the user for:

#### All questions

All questions have the following fields:

```typescript
interface IQuestionBase {
  type?:
    | 'fuzzypath'
    | 'path'
    | 'select'
    | 'list'
    | 'search'
    | 'confirm'
    | 'separator'
    | 'number'
    | 'input'
    | undefined;

  /**
   * The name of the field to store the resulting answer in.
   */
  name: string;

  /**
   * The message prompting the user.
   */
  message: string;

  /**
   * The default value for the answer.
   */
  default?: string | number | boolean;

  /**
   * True if the user must enter a value for the question.
   */
  required?: boolean;

  /**
   * Can we skip answering this question?
   * You can examine the previous answers and determine if you would like to answer this questions.
   * If the question is skipped, the answer value will be undefined.
   * @param previousAnswers
   * @return false if the question should be skipped.
   */
  when?: <TAnswerObject extends object>(
    previousAnswers: TAnswerObject
  ) => boolean;
}
```

#### Simple Input

For getting raw text from the user.

```typescript
interface IInputQuestion extends IQuestionBase {
  type?: 'input';
  default?: string;
}
```

#### Words Input

For a list of words from the user.
Defaults to using whitespace to separator the words,
but you can supply a different separator, such as a comma.

```typescript
interface IInputQuestion extends IQuestionBase {
  type?: 'words';
  separator?: ' ';
  default?: string[];
}
```

#### Confirmation questions

```typescript
interface IConfirmQuestion extends IQuestionBase {
  type: 'confirm';
  default?: boolean;
}
```

#### Number questions

```typescript
interface INumberQuestion extends IQuestionBase {
  type: 'number';
  default?: number;
}
```

#### Select questions

select from a list

```typescript
interface ISelectQuestion extends IQuestionBase {
  type: 'select' | 'list';
  choices: IChoice[];
  default?: string;
}
```

#### Search questions

search for a option to select, or through a set of given choices

```typescript
export interface ISearchQuestion extends IQuestionBase {
  type: 'search';
  source?: (input: string | undefined) => Promise<IChoice[]>;
  choices?: IChoice[];
  default?: string;
}
```

#### Select a file or directory

A file selector with fuzzy search

```typescript
export interface IPathSelectQuestion extends IQuestionBase {
  type: 'fuzzypath' | 'path';
  itemType?: 'file' | 'directory';
  allowManualInput?: boolean;
  rootPath?: string;
  maxDepth?: number;
  excludePath?: (pathInfo: IPathInfo) => boolean;
  default?: string;
}
```

#### Supporting types

```typescript
export interface IChoice {
  value: string;
  name?: string;
  description?: string;
  disabled?: boolean;
}
```

### macros

An object containing 1 or more functions that return a string.
These functions can be called as macros from your templates.
They can take arguments as well.
Config:

```javascript
export default {
  macros: {
    repeat: str => `${str}-${str}`,
    truncate: (str, len) => (str || '').substring(0, len),
  },
};
```

`macros` can also be a function which return an object of macros.

```javascript
export default {
  macros: (name, inputs, variables) => ({
    repeat: str => `${str}-${str}`,
    truncate: (str, len) => (str || '').substring(0, len),
    addName: str => `${str}${name}`,
  }),
};
```

In an HTML file template:

```html
<p>
  This is a paragraph. #repeat('this is a paragraph that is long', 11) Name 2x:
  #repeat(${NAME})
</p>
```

If template is using with a value for NAME of 'TheName', the result would be:

```html
<p>This is a paragraph. this is a p Name 2x: TheName-TheName</p>
```

**Note:** macros do not work when transforming file paths.

# Command Line

The structure of running the **scaffolder** is as follows:

```bash
scaffolder [destinationDirectory] [--template=<templateName>] [--name=<NAME>] [--dryRun]
```

## Arguments

All arguments to the command line are optional.
If you do not supply the arguments on the command line,
you will be prompted for their values.

### Destination Directory

Directory which is the root of where generated files will be placed.
If you specify this value on the command line, _it must be the first argument_.
If it is not specified, the user will be prompted input a value.

### template

Name of the template to use for generating files. This will be the name of a
sub-folder in the **scaffolding** directory.
If it is not specified, the user will be prompted input a value.

### name

Value for the instance NAME variable.
If it is not specified, the user will be prompted input a value.

### dryRun

If this flag is specified, no files or directories will be created.
The contents of what would have been written will be dumped to the console.

### overwrite

By default, the scaffolder will skip the output file if it already exists.
This flag will cause the file to be overwritten.

### parallel

- Read config files in parallel for better startup times.
- Process templates in parallel for much faster processing times.

### rerun

Run scaffolder with the exact same command line arguments and answers to questions as the previous run.
This can be very useful for debugging your templates.

# Making a Template

1. Create a folder in the **scaffolding** directory.
2. Create a `scaffolding.config.mjs` file, and configure it as above.
3. Add files and directories that you will want generated
   when executing the template.

## Parts of the Template

### Directories

The _scaffolder_ will generate directories to match what is in the template dir.
The name of the directories can contain variable names that will be substituted.

**e.g.,**

- a template file with the path `component/${SUB_COMPONENT}/index.tsx`
- Enter 'MySubComponent' when prompted for SUB_COMPONENT
- Enter 'MyComponent' when prompted for NAME
- Generated file will have path: `MyComponent/MySubComponent/index.tsx`

### Template Files

File names may contain variable names that will be substituted.

**e.g.,**

- a template file with the path `component/${NAME}.tsx`
- Enter 'MyComponent' when prompted for NAME
- Generated file will have path: `src/MyComponent/MyComponent.tsx`

### File Contents

Files can be of any type, and have any content.
Variable substitution, and calling macros is all done via the
[Apache 'Velocity Template Language'](https://velocity.apache.org/)
(VTL) syntax.

#### VTL Tips

- In for a simple variable replacement use `${NAME}` or `$NAME`.
- Use backslash `\ ` to escape the transformation:
  `\${NAME}` will not get transformed.
- [Conditionals](https://velocity.apache.org/engine/1.7/user-guide.html#if-elseif-else)
  such as `if/else` can be handy.
- Macros are called as `#myMacro()`. You can define your own macros.
- VTL is very powerful. To fully take advantage of it, the
  [Velocity Template User Guide](https://velocity.apache.org/engine/1.7/user-guide.html).
- The `#include()` macro is a custom implementation.
  The line which this appears in will be replaced by the contents of the referenced file.
  The referenced file _must reside in_ the **\_includes** folder.
  The includes are recursive.

# Sample Project

The following is a directory structure for a React project.
It supplies two templates: **component** and **page**.

```text
- projectRoot
  - scaffolding
    - _templateHelpers
      - index.mjs
    - _includes
      - fileHeader.txt
    - component
      - __tests__
        - ${NAME}.test.tsx
      ${NAME}.tsx
      styles.ts
      scaffolding.config.mjs
    - page
      ${NAME}.tsx
      scaffolding.config.mjs
  - src
    - common
      - components
    - pages
```

## Component template

Let's look at the _component_ template.

### scaffolding/component/scaffolding.config.mjs

```javascript
import { capitalize } from '../_templateHelpers/index.mjs';
export default {
  name: 'React Component',
  description: 'for common components',
  variables: name => ({
    TEST_ID: name.replace(/([a-z])([A-Z])/, '$1-$2').toLowerCase(),
    PROPS_INTERFACE: `I${name}Props`,
  }),
  destinations: ['src/components'], //suggest most common name first
  srcRoot: './src', //default value,
  afterFileCreated: (path, variables) => {
    console.log(`adding ${path} to git`);
    return [`git add ${path}`];
  },
  macros: {
    capitalize,
  },
};
```

### scaffolding/component/${NAME}.tsx

There are two things to note about this file.
First, it is _including_ a file using the `#include()` directive.
Second, it is using a macro (`#capitalize()`).

```tsx
// #include(fileHeader.txt)
import * as React from 'react';
import {ReactElement} from 'react';
import {use${NAME}Styles} from './styles';

// testing macro #capitalize('word')

interface $PROPS_INTERFACE
{
}

export const $NAME = ({}: $PROPS_INTERFACE): ReactElement => {

    use${NAME}Styles();

    const someVar = 'Need to escape this';

    return (
        <div data-testid="${TEST_ID}">
            {`The name variable is \${someVar}`}
        </div>
    );
};
```

### scaffolding/component/**tests**/${NAME}.test.tsx

```tsx
import * as React from 'react';
import {render} from '@testing-library/react';
import '@testing-library/jest-dom';
import {${NAME}} from '../${NAME}';

describe('<${NAME} />', () => {
    it('should render without blowing up', () => {
        const result = render(<${NAME} />);
        expect(result.getByTestId('${TEST_ID}')).toBeInTheDocument();
    });
});
```

### scaffolding/component/styles.ts

```typescript
import {createUseStyles} from 'react-jss';
import {ITheme} from '@models';

export const use${NAME}Styles = (createUseStyles((theme: ITheme) => {
    return ({});
}));
```

## Page Template

This is the page template.

### scaffolding/page/scaffolding.config.mjs

```javascript
import { capitalize } from '../_templateHelpers/index.mjs';
export default {
  prompts: () => [
    {
      name: 'TITLE',
      message: 'Enter page heading',
    },
  ],
  macros: {
    truncate: (str, n) => (str || '').substring(0, n),
  },
};
```

### scaffolding/page/${NAME}.tsx

```tsx
import * as React from 'react';
import {ReactElement} from 'react';

export const ${NAME} = (): ReactElement => {

    return (
        <div>
            <h1>
                Title: #truncate(${TITLE}, 40)
            </h1>
            <p>
                This is page content.
            </p>
        </div>
    );
};
```

### scaffolding/\_includes/fileHeader.txt

Files in the `_includes` folder can be references made by other templates using the
`#include()` directive in the template.

```js
/**
 * This is a standar file header that I will include.
 */
```

### scaffolding/\_templateHelpers/index.mjs

You may find that a number of your templates have duplicated sections.
A tip is to create one (or more) helper files to eliminate the duplicate config in your templates.

```js
export const capitalize = str => str.charAt(0).toUpperCase() + str.slice(1);
```

---
_Source: https://npm.io/package/template-scaffolder · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
