Skip to content

Repository files navigation

TreePort copies selected files from a source tree to matching destination paths

TreePort

Selectively port files and folders across matching directory trees.

TreePort is a small TypeScript CLI for copying only the local files you choose from one repo, worktree, clone, or project directory into another while preserving relative paths.

treeport -s ../main -d ../worktree "**/.env*"

That command copies matching files from ../main into the same relative locations inside ../worktree.

Why

Developers often keep local-only files outside Git:

.env
.env.local
.env.production.local
config/local.json
certs/
secrets/

Those files are easy to forget when creating a Git worktree, cloning a repo again, or maintaining parallel project folders. TreePort copies only selected files/folders and leaves everything else alone.

Install

Run without installing:

pnpm dlx treeport@latest -s ../main -d ../worktree "**/.env*"

Install globally:

pnpm add -g treeport

Check the CLI:

treeport --help
treeport --version

Quick Start

Copy all .env files anywhere:

treeport -s ../main -d ../worktree "**/.env*"

Preview first:

treeport -s ../main -d ../worktree "**/.env*" --dry-run

Copy env files and a config folder:

treeport -s ../main -d ../worktree "**/.env*" "config/**"

Save defaults globally:

treeport config add "**/.env*"
treeport config add "config/**"

Use saved defaults:

treeport -s ../main -d ../worktree

What It Does

TreePort:

  • discovers files in the source directory using glob patterns
  • applies excludes after includes
  • preserves every matched file's relative path
  • creates destination folders as needed, including nested subfolders that do not exist yet
  • skips existing destination files by default
  • overwrites only with --overwrite
  • can create symbolic links instead of copies with --link or --symlink
  • never deletes destination files
  • always excludes .git and node_modules

Example:

source: ../main/apps/web/.env.local
dest:   ../worktree/apps/web/.env.local

CLI

treeport -s <source-dir> -d <dest-dir> <patterns...>

Full form:

treeport \
  --source <source-dir> \
  --dest <dest-dir> \
  --include <pattern> \
  --exclude <pattern> \
  --dry-run \
  --overwrite \
  --link \
  --no-config \
  --verbose

Flags

Flag Alias Description
--source <dir> -s Source directory to copy from
--dest <dir> -d Destination directory to copy into
--include <pattern> -i Include glob pattern. Can be repeated
--exclude <pattern> -e Exclude glob pattern. Can be repeated
--no-config Ignore global config
--dry-run Preview planned copies without writing files
--overwrite Replace existing destination files
--link Create symlinks instead of copying files
--symlink Alias for --link
--verbose -v Print absolute source/destination paths
--help -h Show help
--version Show version

Environment

TREEPORT_SOURCE_PATH and TREEPORT_DEST_PATH can provide defaults for --source and --dest. Flags take priority when both are set.

Patterns can be positional or passed with --include.

These are equivalent:

treeport -s ../main -d ../worktree "**/.env*"
treeport -s ../main -d ../worktree --include "**/.env*"

Examples

Copy all env files anywhere:

treeport -s ../main -d ../worktree "**/.env*"

Copy only .env.local files anywhere:

treeport -s ../main -d ../worktree "**/.env.local"

Copy all .local env variants anywhere:

treeport -s ../main -d ../worktree "**/.env*.local"

Copy root env files only:

treeport -s ../main -d ../worktree ".env*"

Copy config files:

treeport -s ../main -d ../worktree "config/**"

Copy certs:

treeport -s ../main -d ../worktree "certs/**"

Copy env files and config:

treeport -s ../main -d ../worktree "**/.env*" "config/**"

Copy JSON files anywhere:

treeport -s ../main -d ../worktree "**/*.json"

Copy multiple extensions:

treeport -s ../main -d ../worktree "**/*.{json,yml,yaml}"

Exclude generated folders:

treeport -s ../main -d ../worktree "**/.env*" --exclude "**/dist/**" --exclude "**/.next/**"

Use a negated positional pattern as an exclude:

treeport -s ../main -d ../worktree "**/.env*" "!apps/legacy/**"

Dry run:

treeport -s ../main -d ../worktree "**/.env*" --dry-run

Overwrite existing destination files:

treeport -s ../main -d ../worktree "**/.env*" --overwrite

Create symlinks instead of copying files:

treeport -s ../main -d ../worktree "**/.env*" --link

Equivalent symlink alias:

treeport -s ../main -d ../worktree "**/.env*" --symlink

Ignore saved config:

treeport -s ../main -d ../worktree --no-config --include "certs/**"

Use absolute paths:

treeport -s /Users/you/projects/main -d /Users/you/projects/worktree "**/.env*"

Dry Run

Dry run shows what would happen without copying anything.

treeport -s ../main -d ../worktree "**/.env*" --dry-run

TreePort dry run output preview

Typical dry-run output:

TreePort dry run. No files copied.

Source: ../main
Dest:   ../worktree

Would copy:
  apps/web/.env.local
  apps/api/.env

Would skip:
  .env already exists

Done. 2 would copy, 1 would skip.

Pattern Guide

TreePort uses full glob syntax through tinyglobby.

Pattern Meaning
.env* Root-level .env, .env.local, .env.production
**/.env* Env files anywhere
**/.env.local .env.local anywhere
**/.env*.local .env.local, .env.production.local, etc. anywhere
apps/**/.env* Env files inside apps
config/** All files inside config
certs/**/* All nested files inside certs
*.json JSON files at root scan level
**/*.json JSON files anywhere
*.{json,yml,yaml} Multiple extensions
{**/.env*,config/**} Grouped patterns
!**/node_modules/** Exclude node_modules
!**/.git/** Exclude git folder

Important distinction:

treeport -s ../main -d ../worktree ".env*"

Matches root-level env files only:

.env
.env.local
.env.production

It does not match:

apps/web/.env
apps/api/.env.local

Use this for nested env files:

treeport -s ../main -d ../worktree "**/.env*"

Config

TreePort supports global defaults.

Config path:

~/.config/treeport/config.json

Example config:

{
  "includes": ["**/.env*", "config/**"],
  "excludes": ["**/node_modules/**", "**/.git/**"],
  "overwrite": false
}

Use config-only copying:

treeport -s ../main -d ../worktree

That works only when global config has at least one include pattern.

Config Commands

Show include patterns:

treeport config list

Add include patterns:

treeport config add "**/.env*"
treeport config add "config/**"

Remove include patterns:

treeport config remove "**/.env*"

Clear config:

treeport config clear

Print config path:

treeport config path

Show exclude patterns:

treeport config exclude list

Add exclude patterns:

treeport config exclude add "**/dist/**"
treeport config exclude add "**/.next/**"

Remove exclude patterns:

treeport config exclude remove "**/dist/**"

Config Merge Rules

By default:

final includes = global includes + CLI includes
final excludes = default excludes + global excludes + CLI excludes

With --no-config:

final includes = CLI includes only
final excludes = default excludes + CLI excludes

If no include patterns exist from CLI or config, TreePort exits with an error.

Case Behavior
CLI includes + config includes Append CLI includes to config includes
CLI includes + --no-config Use only CLI includes
No CLI includes + config includes Use config includes
No CLI includes + no config includes Error
CLI excludes + config excludes Append CLI excludes to config excludes
Excludes conflict with includes Excludes win

Example:

{
  "includes": ["**/.env*", "config/**"],
  "excludes": ["**/node_modules/**", "**/.git/**"]
}

Command:

treeport -s ../main -d ../worktree --include "certs/**"

Effective includes:

**/.env*
config/**
certs/**

Effective excludes:

**/.git/**
**/node_modules/**

Default Excludes

TreePort always excludes:

**/.git/**
**/node_modules/**

Those defaults are always applied, including when --no-config is used.

Destination Rules

Default behavior: existing destination files are skipped.

treeport -s ../main -d ../worktree "**/.env*"

Overwrite existing destination files:

treeport -s ../main -d ../worktree "**/.env*" --overwrite

TreePort never deletes files from the destination. It is a selective copy tool, not a sync/delete tool.

TreePort creates missing destination folders automatically.

Example:

source: ../main/apps/web/.env.local
dest:   ../worktree/apps/web/.env.local

If ../worktree/apps/web does not exist, TreePort creates it before copying or linking .env.local.

Symlink Mode

Use --link or --symlink when you want destination files to point back to the source tree instead of copying file contents.

treeport -s ../main -d ../worktree "**/.env*" --link

This creates file symlinks:

../worktree/.env.local -> ../main/.env.local
../worktree/apps/web/.env.local -> ../main/apps/web/.env.local

Symlink mode follows the same safety rules as copy mode:

  • relative paths are preserved
  • missing destination folders are created
  • existing destination paths are skipped by default
  • existing destination paths are replaced only with --overwrite
  • .git and node_modules are still excluded

Overwrite existing destination paths with symlinks:

treeport -s ../main -d ../worktree "**/.env*" --link --overwrite

Output

Successful copy:

TreePort

Source: ../main
Dest:   ../worktree

Copied:
  apps/web/.env.local
  apps/api/.env

Skipped:
  .env already exists

Done. 2 copied, 1 skipped.

No include patterns:

No include patterns found.

Pass patterns:
  treeport -s ../main -d ../worktree "**/.env*"

Or add global defaults:
  treeport config add "**/.env*"

No matches:

No files matched the provided patterns.

Patterns:
  **/.env.local

Common Use Cases

Git worktrees

Copy local environment files from your main checkout into a new worktree.

treeport -s ../my-app-main -d ../my-app-feature "**/.env*"

Multiple local clones

Move private config from one clone to another.

treeport -s ~/code/main-app -d ~/code/main-app-debug "**/.env*" "config/local/**"

Monorepos

Copy env files from every package/app.

treeport -s ../main -d ../worktree "**/.env*"

Copy only app env files:

treeport -s ../main -d ../worktree "apps/**/.env*"

Certificates and local secrets

Copy local certificates while preserving folder layout.

treeport -s ../main -d ../worktree "certs/**" "secrets/**"

Review before copying

Use dry run when handling sensitive files.

treeport -s ../main -d ../worktree "**/.env*" "secrets/**" --dry-run

Repeatable defaults

Save patterns once:

treeport config add "**/.env*"
treeport config add "config/local/**"
treeport config exclude add "**/dist/**"

Reuse them:

treeport -s ../main -d ../worktree

Safety Model

TreePort is conservative by default:

  • no patterns means no copy
  • destination files are skipped unless --overwrite is set
  • missing destination folders are created, but destination files are never deleted
  • symlink mode uses the same skip/overwrite rules as copy mode
  • .git and node_modules are forced excludes
  • no delete behavior exists
  • dry-run is available for previewing sensitive operations

Development

Install dependencies:

pnpm install

Typecheck:

pnpm run typecheck

Test:

pnpm run test

Build:

pnpm run build

Run locally after build:

node dist/index.js --help

Releases

Create a changeset for every user-facing change:

pnpm changeset

Merging to main creates or updates the Changesets version PR. Merging that version PR publishes to npm from GitHub Actions through npm trusted publishing.

Trusted publishing setup:

  • Publisher: GitHub Actions
  • Organization/user: d3oxy
  • Repository: treeport
  • Workflow filename: release.yml
  • Environment: unset
  • Allowed action: npm publish

License

MIT

About

TreePort is a small TypeScript CLI for copying only the local files you choose from one repo, worktree, clone, or project directory into another while preserving relative paths.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages