FileSync 5.0.0
dotnet tool install --global FileSync --version 5.0.0
dotnet new tool-manifest
dotnet tool install --local FileSync --version 5.0.0
#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:
- 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.
- The core is unchanged.
syncis stillsync. A few things just moved to where they fit better — e.g. the oldcheckcommand (preview a sync without writing) is now simplysync --dry-run. - The config is de-emphasized. The
.filesyncfile 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.) - 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 untilexit. - Bare config → preloaded REPL:
filesync ./deploy.filesync(a config path, no command) loads it and drops into the REPL with it preloaded; a baresync/clean/watchthen 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 | Versions 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. |
This package has no dependencies.
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`.