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.
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.
Run without installing:
pnpm dlx treeport@latest -s ../main -d ../worktree "**/.env*"Install globally:
pnpm add -g treeportCheck the CLI:
treeport --help
treeport --versionCopy all .env files anywhere:
treeport -s ../main -d ../worktree "**/.env*"Preview first:
treeport -s ../main -d ../worktree "**/.env*" --dry-runCopy 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 ../worktreeTreePort:
- 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
--linkor--symlink - never deletes destination files
- always excludes
.gitandnode_modules
Example:
source: ../main/apps/web/.env.local
dest: ../worktree/apps/web/.env.localtreeport -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| 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 |
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*"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-runOverwrite existing destination files:
treeport -s ../main -d ../worktree "**/.env*" --overwriteCreate symlinks instead of copying files:
treeport -s ../main -d ../worktree "**/.env*" --linkEquivalent symlink alias:
treeport -s ../main -d ../worktree "**/.env*" --symlinkIgnore 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 shows what would happen without copying anything.
treeport -s ../main -d ../worktree "**/.env*" --dry-runTypical 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.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.productionIt does not match:
apps/web/.env
apps/api/.env.localUse this for nested env files:
treeport -s ../main -d ../worktree "**/.env*"TreePort supports global defaults.
Config path:
~/.config/treeport/config.jsonExample config:
{
"includes": ["**/.env*", "config/**"],
"excludes": ["**/node_modules/**", "**/.git/**"],
"overwrite": false
}Use config-only copying:
treeport -s ../main -d ../worktreeThat works only when global config has at least one include pattern.
Show include patterns:
treeport config listAdd include patterns:
treeport config add "**/.env*"
treeport config add "config/**"Remove include patterns:
treeport config remove "**/.env*"Clear config:
treeport config clearPrint config path:
treeport config pathShow exclude patterns:
treeport config exclude listAdd exclude patterns:
treeport config exclude add "**/dist/**"
treeport config exclude add "**/.next/**"Remove exclude patterns:
treeport config exclude remove "**/dist/**"By default:
final includes = global includes + CLI includes
final excludes = default excludes + global excludes + CLI excludesWith --no-config:
final includes = CLI includes only
final excludes = default excludes + CLI excludesIf 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/**TreePort always excludes:
**/.git/**
**/node_modules/**Those defaults are always applied, including when --no-config is used.
Default behavior: existing destination files are skipped.
treeport -s ../main -d ../worktree "**/.env*"Overwrite existing destination files:
treeport -s ../main -d ../worktree "**/.env*" --overwriteTreePort 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.localIf ../worktree/apps/web does not exist, TreePort creates it before copying or linking .env.local.
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*" --linkThis creates file symlinks:
../worktree/.env.local -> ../main/.env.local
../worktree/apps/web/.env.local -> ../main/apps/web/.env.localSymlink 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 .gitandnode_modulesare still excluded
Overwrite existing destination paths with symlinks:
treeport -s ../main -d ../worktree "**/.env*" --link --overwriteSuccessful 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.localCopy local environment files from your main checkout into a new worktree.
treeport -s ../my-app-main -d ../my-app-feature "**/.env*"Move private config from one clone to another.
treeport -s ~/code/main-app -d ~/code/main-app-debug "**/.env*" "config/local/**"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*"Copy local certificates while preserving folder layout.
treeport -s ../main -d ../worktree "certs/**" "secrets/**"Use dry run when handling sensitive files.
treeport -s ../main -d ../worktree "**/.env*" "secrets/**" --dry-runSave patterns once:
treeport config add "**/.env*"
treeport config add "config/local/**"
treeport config exclude add "**/dist/**"Reuse them:
treeport -s ../main -d ../worktreeTreePort is conservative by default:
- no patterns means no copy
- destination files are skipped unless
--overwriteis set - missing destination folders are created, but destination files are never deleted
- symlink mode uses the same skip/overwrite rules as copy mode
.gitandnode_modulesare forced excludes- no delete behavior exists
- dry-run is available for previewing sensitive operations
Install dependencies:
pnpm installTypecheck:
pnpm run typecheckTest:
pnpm run testBuild:
pnpm run buildRun locally after build:
node dist/index.js --helpCreate a changeset for every user-facing change:
pnpm changesetMerging 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
MIT

