touch-all
v2.1.2
Published
CLI tool to create folder structures from markdown tree representations
Maintainers
Readme
touch-all
CLI tool to create folder structures from Markdown tree representations.
It behaves like mkdir -p and touch combined, creating directories and files as needed. It can be used to quickly scaffold a project structure or generate placeholder files.
Features
- Accepts tree strings in box-drawing (
├──,└──,│) or indentation (spaces) format - Trailing slash
/marks a directory; no trailing/marks a file - Symlink creation with
link-name -> targetsyntax - Inline comments stripped automatically (
# ...,// ...,<- ...,← ...) --dry-run(-n) parses and validates without touching the file system- Prints a summary line on success (
✓ Done. N items created.);--verboseadds per-item detail - Path traversal protection — no file, folder, or symlink target can escape the target directory
- Importable as a Node.js library with full TypeScript types
- Pipable interface to run as
echo tree | touch-allin CI or scripts
Installation
npm install -g touch-allor with npx without installing:
npx touch-all@latest "..."Usage
Basic (current directory)
touch-all "
my-project/
├── src/
│ ├── index.ts
│ └── index.test.ts
├── package.json
└── README.md
"Arguments
--path,-p– specifies target directory. By default, the current working directory is used. Can be an absolute path or a path relative to the current working directory.
touch-all "..." --path=./my-project
touch-all "..." -p ~/Documents/my-project--dry-run,-n– parses and validates the tree string without creating any files or directories. Useful for testing and debugging.
touch-all "..." --dry-run
touch-all "..." -n--verbose,-v– prints every created path to the console. Useful for seeing exactly what will be created, especially with complex structures. By default only a summary line is printed on success (✓ Done. N items created.).--verboseadds per-item detail.
touch-all "..." --verbose
touch-all "..." -v--yes,-y– skips the confirmation prompt when symlinks point outside the project root. Required in non-interactive environments (scripts, CI).
touch-all "..." --yes
touch-all "..." -y--completions– generates a completion script for a specific shell. Supported shells:sh,bash,fish,zsh.--log-level– sets the minimum log level for a command. Supported levels:all,trace,debug,info,warning,error,fatal,none. The default log level iswarning.--help,-h– shows the help documentation for a command.--wizard– starts wizard mode for a command, providing an interactive step-by-step interface.--version– shows the version of the application.
Tree Format
Box-drawing characters
my-project/
├── .config/
│ ├── tsconfig.json
│ └── vite.config.ts
├── src/
│ ├── index.ts
│ └── index.test.ts
├── package.json
└── README.mdIndentation (spaces)
my-project/
.config/
tsconfig.json
vite.config.ts
src/
index.ts
index.test.ts
package.json
README.mdBoth formats produce identical results.
Rules
| Syntax | Meaning |
| ----------------- | -------------------------------- |
| name/ | directory |
| name | file |
| dir/sub/ | directory at an explicit subpath |
| dir/sub/file.ts | file at an explicit subpath |
| ... # comment | ignored (stripped) |
| ... // comment | ignored (stripped) |
| ... <- comment | ignored (stripped) |
| ... ← comment | ignored (stripped) |
| name -> target | symlink pointing to target |
Items at the root level (no indentation / no parent) are created directly inside the target directory.
Symlinks
Use link-name -> target to create a symlink. The target is passed as-is to the OS — use paths relative to the symlink's location, just as you would in a shell.
my-project/
├─ src/
│ ├─ index.ts
│ └─ utils -> ../shared/utils.ts # symlink to a sibling directory
└─ shared/
└─ utils.tsIf target ends with /, the symlink is created as a directory symlink (relevant on Windows). The link name's suffix is ignored.
[!WARNING] If any symlink target resolves outside the project root (
--path),touch-allwill prompt for confirmation before proceeding. Use--yesto skip the prompt in scripts or CI.When using
fileStructureCreatordirectly as a library, outside-root symlinks are rejected by default with aPathTraversalError. Pass{ allowOutsideSymlinks: true }as the third argument to allow them.
Library API
npm install touch-allimport {
parserFolderStructure,
fileStructureCreator,
resolveProjectPathToBase,
isSymlinkOutsideRoot,
PathTraversalError,
} from 'touch-all'
import type { ParserResult, ParserResultLineItem } from 'touch-all'parserFolderStructure(tree: string): ParserResult
Parses a tree string into a flat list of items. Pure function, no I/O.
Each item is one of:
type ParserResultLineItem =
| { type: 'file'; path: string }
| { type: 'folder'; path: string }
| { type: 'symlink'; path: string; target: string }const items = parserFolderStructure(`
src/
index.ts
link -> ../shared.ts
`)
// [
// { type: 'folder', path: 'src' },
// { type: 'file', path: 'src/index.ts' },
// { type: 'symlink', path: 'src/link', target: '../shared.ts' },
// ]fileStructureCreator(items, basePath, options?): Effect<void, PathTraversalError>
Creates the parsed structure on disk under basePath. Returns an Effect.
By default, symlinks whose targets resolve outside basePath are rejected with PathTraversalError. Pass { allowOutsideSymlinks: true } to allow them.
import { Effect } from 'effect'
import { NodeContext, NodeRuntime } from '@effect/platform-node'
const projectDirectory = '/absolute/target/path'
const items = parserFolderStructure(tree)
// Default: outside-root symlinks are rejected
fileStructureCreator(items, projectDirectory).pipe(Effect.provide(NodeContext.layer), NodeRuntime.runMain)
// Opt out of symlink containment check
fileStructureCreator(items, projectDirectory, { allowOutsideSymlinks: true }).pipe(
Effect.provide(NodeContext.layer),
NodeRuntime.runMain
)isSymlinkOutsideRoot(linkPath, target, basePath, path): boolean
Pure function that returns true if a symlink target resolves outside basePath. Useful for pre-validating items before passing them to fileStructureCreator.
import { Path } from '@effect/platform'
import { Effect } from 'effect'
import { NodeContext } from '@effect/platform-node'
import { isSymlinkOutsideRoot } from 'touch-all'
Effect.gen(function* () {
const path = yield* Path.Path
const outside = isSymlinkOutsideRoot('src/link', '../../etc/passwd', '/project', path)
// true — target escapes /project
}).pipe(Effect.provide(NodeContext.layer))resolveProjectPathToBase(projectPath: string, basePath: string): Effect<string, PathTraversalError>
Resolves projectPath relative to basePath and rejects any path that would escape basePath (path traversal protection).
[!WARNING]
projectPathcannot traverse outside ofbasePath. IfprojectPathis absolute, it treated as relative tobasePath. IfprojectPathis relative, it is resolved againstbasePath. In either case, if the resulting path is outside ofbasePath, aPathTraversalErroris thrown.
Error types
| Class | _tag | When thrown |
| -------------------- | ---------------------- | -------------------------------------------------------------------------- |
| PathTraversalError | 'PathTraversalError' | A file/folder path or symlink target escapes basePath (unless opted out) |
