FileSync 5.0.0

dotnet tool install --global FileSync --version 5.0.0
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local FileSync --version 5.0.0
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=FileSync&version=5.0.0
                    
nuke :add-package FileSync --version 5.0.0
                    

FileSync

A small command-line tool to sync / back up a local folder to another directory or to Azure Blob Storage, driven by a .filesync.yaml config file and/or inline options.

Built on .NET 10.

What's new in v5!

Total rewritten CLI layer, built on top of the beautiful CommandLine.EasyBuilder -- which sits thin on top of System.CommandLine. The formerly CLI wiring layer went from a nightmare to ever want to change anything, and easily 85% what would make us afraid to ever touch this app, to now: a handful of BEAUTIFULLY simple Cmd classes, with simple command and arg names and descriptions. Check out CommandLine.EasyBuilder, it makes CLI creation a breeze. --help is generated straight from these classes — so the documentation can't drift from the code.

Thanks to the one and only Claude Opus for knocking this migration out of the park! I simply would not have had the time to do this migration otherwise (not enough reward to do a major work like this).

Now in color

Run any command and you'll see it — the output got a real upgrade (built on the excellent Spectre.Console). Before a single byte is written, a tidy pre-op report frames exactly what's about to happen: source, destination, a per-target badge ([dir], [azure]), eligible file counts, and the active filters/excludes. Then the live work log is lightly color-coded — green for added/synced, red for trouble. The preloaded-REPL config banner is styled too.

And it degrades gracefully. Pass --no-color, set NO_COLOR, or pipe the output anywhere and you get clean plain text — piped logs stay byte-for-byte identical. Want just the log without the report? --quiet / -q. And expected errors now surface as a one-line Error: … instead of a stack trace (set FILESYNC_DEBUG to get the full trace when you actually want it).

Breaking changes — and the why

The changes are breaking, but deliberate:

  1. Conventions. When I first wrote FileSync I was new to CLI customs; v5 standardizes the command and flag names to match how mainstream tools behave.
  2. The core is unchanged. sync is still sync. A few things just moved to where they fit better — e.g. the old check command (preview a sync without writing) is now simply sync --dry-run.
  3. The config is de-emphasized. The .filesync file used to be a catch-all — it even encoded which operation to run (watch: / sync: booleans) and confirmation settings. Those now live on the CLI, so a config is just what to sync: source, destination, filters, target. (Old configs still load — legacy keys are recognized, retired ones ignored.)
  4. REPL is opt-in. It used to always stay running as a REPL. Now it acts like a normal CLI: give a command and it runs once; run with no command (or --repl) to get a REPL; and pointing it at a bare config — filesync some.filesync — opens a REPL with that config preloaded.

The sync engine is essentially untouched — just a bug fix or two (e.g. a dry-run or canceled sync no longer leaves an empty destination folder behind).

Migrating from earlier versions

Was Now
filesync check <config> filesync sync <config> --dry-run
filesync new <path> filesync init <path> (--format yaml\|json)
clean … --yes / -y clean … --noconfirm / -y
config watch: true / sync: true run the watch / sync command
config promptOnDelete clean confirms by default; --noconfirm skips

Existing .filesync files keep working — unknown or retired keys are simply ignored. Or run filesync migrate <old.filesync> to auto-convert a legacy config into a clean, notes-annotated .filesync.yaml (your original is left untouched).

.filesync config file now defaults to use *.filesync.yaml or *.filesync.json

Install

dotnet pack -c Release          # produces ./nupkg/FileSync.<version>.nupkg
dotnet tool install --global --add-source ./nupkg FileSync

The filesync command is then available globally (dotnet tool update --global FileSync to upgrade, dotnet tool uninstall --global FileSync to remove).

Usage

filesync <command> [config] [options]
Command Does
sync Copy/upload changed source files to the destination. --dry-run previews; --confirm asks first.
clean Delete destination files no longer in the source (orphans). --dry-run lists them; confirms by default (--noconfirm/-y to skip).
watch Watch the source and auto-sync changes until Ctrl+C.
init Scaffold a sample .filesync. --type azure, --format (yaml or json; default yaml).
migrate Convert an old .filesync to the new format — writes a new *.filesync.yaml (never overwrites) with inline migration notes. --format.

[config] is a .filesync file, or a directory containing one. If omitted (and no --src is given), a config in the current directory is used.

Running modes

  • One-shot (default): filesync sync ./deploy.filesync
  • REPL: run with no arguments, or pass --repl — you get a prompt to issue commands repeatedly until exit.
  • Bare config → preloaded REPL: filesync ./deploy.filesync (a config path, no command) loads it and drops into the REPL with it preloaded; a bare sync / clean / watch then acts on that config.

Common options

Shared by sync, clean, and watch:

Option Meaning
-s, --src Source directory (inline mode)
-d, --dest Destination directory, or cloud sub-path/prefix after the container
-t, --type directory (default), azure, ftp*, amazon*
-f, --filter Include glob, repeatable: --filter "**/*.js" --filter "**/*.css"
-x, --exclude Exclude glob, repeatable
-c, --container Cloud container name
--account-name / --account-key Azure account name / key
--creds / --creds-path Azure connection string inline / from a file
--since Ignore files older than e.g. 1h, 10m, 2.5d
-r, --root Root that relative inline paths resolve against (default: current dir)
-p, --parallel Parallel uploads — on by default (--parallel false, or config parallel: false, to disable)
-v, --verbose Verbose logging
-q, --quiet Suppress the pre-op report (the work log still prints)
--no-color Disable colored output (also honors NO_COLOR and piped/redirected output)

* ftp and amazon (S3) are recognized targets but not yet implemented (they throw if used) — directory and azure are the working targets.

The .filesync file

A config just says what to sync — source, destination, filters, target. Write it in YAML, named .filesync.yaml (JSON works too — .filesync.json — if you'd rather). Either is auto-discovered when you point FileSync at a directory, and filesync init scaffolds one for you. Relative src / dest / credsPath resolve against the config file's own folder (or --root).

The simplest config backs a folder up to another directory. directory is the default target, so no type is needed — and omitting filters means "everything":

src: ./photos
dest: D:/backup/photos

Narrow it with include / exclude globs — here, just the built JS and CSS, minus sourcemaps:

src: ./dist
dest: ./out
filters:
  - "**/*.js"
  - "**/*.css"
exclude:
  - "**/*.map"

Upload a built site to Azure Blob storage:

type: azure
src: ./dist
dest: v1                 # sub-path / prefix after the container (optional)
container: my-site
accountName: myaccount
credsPath: ./key.txt     # a file holding the key — or inline `creds` / `accountKey`

Run several syncs from one file by listing them under syncs:. Each entry takes the same fields; since (skip files untouched for 1h / 10m / 2.5d) is per-entry, and an optional profile-level root sets a shared base for relative paths:

syncs:
  - src: ./docs
    dest: ./backup/docs

  - type: azure
    src: ./dist
    dest: v1
    container: my-site
    accountName: myaccount
    credsPath: ./key.txt
    since: 12h           # this entry only: skip files untouched for 12h+

Fields: type (directory · azure; ftp / amazon are scaffolded only), src, dest, filters, exclude, container, accountName, accountKey, creds, credsPath, since, parallel — plus profile-level root and syncs. JSON configs additionally allow // comments and trailing commas, and anywhere a list is expected a single string works too.

Examples

No config — just point at a source and destination:

filesync sync --src ./photos --dest D:/backup/photos          # local → local
filesync sync -s ./dist -d ./out -f "**/*.js" -f "**/*.css"   # only JS + CSS
filesync sync -s ./dist -d ./out -x "**/*.map"                # everything except sourcemaps

Preview, confirm, then commit:

filesync sync ./deploy.filesync --dry-run     # show what would change, write nothing
filesync sync ./deploy.filesync --confirm     # show the plan, then ask before writing
filesync sync ./deploy.filesync               # just sync

Keep a destination live while you work, or clean up orphans:

filesync watch ./deploy.filesync              # re-sync on every change until Ctrl+C
filesync clean ./deploy.filesync --dry-run    # list destination files no longer in source
filesync clean ./deploy.filesync              # delete them (asks first; -y to skip)

Azure, entirely inline (no config file):

filesync sync -s ./dist -t azure -d v1 \
  -c my-site --account-name myaccount --creds-path ./key.txt

Recent files only, scaffold, migrate, explore:

filesync sync ./deploy.filesync --since 2h    # skip files untouched for 2h+
filesync init --type azure                    # write a starter .filesync.yaml (Azure flavor)
filesync migrate ./old.filesync               # convert a legacy config → .filesync.yaml
filesync ./deploy.filesync                     # bare config, no command → preloaded REPL
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
5.0.0 74 6/23/2026
4.0.5 259 8/21/2024
4.0.4 177 8/20/2024
4.0.3 208 3/21/2024
3.0.0 389 9/30/2022

5.0.0 — full rewrite on .NET 10 + CommandLine.EasyBuilder. Commands: sync, clean, watch, init. `sync --dry-run` previews and `--confirm` asks first; `clean` confirms by default (`--noconfirm`/`-y` to skip). Bare `filesync <config>` opens a preloaded REPL. JSON or YAML config — `.filesync` and `.filesync.{yaml,yml,json}` are all auto-discovered. Directory and Azure Blob targets (FTP and Amazon S3 scaffolded, not yet implemented); repeatable `--filter`/`--exclude`.