diff --git a/README.md b/README.md index ccb727c..f8bd885 100644 --- a/README.md +++ b/README.md @@ -443,11 +443,15 @@ Useful commands: npm run sync-tmux npm run sync-tmux -- --reload npm run sync-tmux -- --bootstrap --reload +npm run restore-tmux ``` - `sync-tmux` copies this checkout into `~/.tmux/plugins/coding-agents-tmux` - `--reload` runs `tmux source-file ~/.tmux.conf` after syncing - `--bootstrap` reinstalls production dependencies in the synced plugin copy when `package.json` changed +- `restore-tmux` removes the synced development files, preserves `node_modules`, and returns the TPM checkout to a clean state so `prefix + U` can update it normally + +Run `npm run restore-tmux` when you finish local development or before asking TPM to update all plugins. ## CLI diff --git a/package.json b/package.json index 51bff6a..7829b92 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "check": "./bin/coding-agents-tmux --help", "typecheck": "tsc --project tsconfig.json", "sync-tmux": "./scripts/sync-tmux-plugin.sh", + "restore-tmux": "./scripts/sync-tmux-plugin.sh --restore", "fmt": "oxfmt .", "fmt:check": "oxfmt --check ." }, diff --git a/scripts/sync-tmux-plugin.sh b/scripts/sync-tmux-plugin.sh index fee3923..bd357b7 100755 --- a/scripts/sync-tmux-plugin.sh +++ b/scripts/sync-tmux-plugin.sh @@ -7,9 +7,10 @@ TARGET_DIR="${CODING_AGENTS_TMUX_PLUGIN_DIR:-$HOME/.tmux/plugins/coding-agents-t TMUX_CONF="${CODING_AGENTS_TMUX_TMUX_CONF:-$HOME/.tmux.conf}" RELOAD=0 BOOTSTRAP=0 +RESTORE=0 usage() { - printf 'Usage: %s [--target ] [--reload] [--bootstrap]\n' "${0##*/}" + printf 'Usage: %s [--target ] [--reload] [--bootstrap] [--restore]\n' "${0##*/}" printf '\n' printf 'Sync this checkout into the tmux plugin install used by TPM.\n' printf '\n' @@ -17,6 +18,7 @@ usage() { printf ' --target Override target plugin directory\n' printf ' --reload Reload tmux after syncing\n' printf ' --bootstrap Reinstall production npm deps in the target dir\n' + printf ' --restore Discard synced files and restore the TPM checkout\n' printf ' -h, --help Show this help\n' } @@ -76,6 +78,9 @@ while [ "$#" -gt 0 ]; do --bootstrap) BOOTSTRAP=1 ;; + --restore) + RESTORE=1 + ;; -h | --help) usage exit 0 @@ -89,6 +94,22 @@ while [ "$#" -gt 0 ]; do shift done +if [ "$RESTORE" -eq 1 ]; then + if [ ! -d "$TARGET_DIR/.git" ]; then + printf 'coding-agents-tmux: cannot restore non-git target: %s\n' "$TARGET_DIR" >&2 + exit 1 + fi + + git -C "$TARGET_DIR" reset --hard HEAD + git -C "$TARGET_DIR" clean -fd -e node_modules/ + printf 'Restored clean TPM checkout at %s\n' "$TARGET_DIR" + + if [ "$RELOAD" -eq 1 ]; then + reload_tmux + fi + exit 0 +fi + if ! command -v rsync >/dev/null 2>&1; then printf 'coding-agents-tmux: rsync is required to sync the plugin checkout\n' >&2 exit 1 diff --git a/test/sync-tmux-plugin.test.ts b/test/sync-tmux-plugin.test.ts new file mode 100644 index 0000000..a47c480 --- /dev/null +++ b/test/sync-tmux-plugin.test.ts @@ -0,0 +1,44 @@ +import assert from "node:assert/strict"; +import { execFileSync } from "node:child_process"; +import { mkdtempSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import test from "node:test"; + +const repoRoot = join(import.meta.dirname, ".."); +const syncScript = join(repoRoot, "scripts", "sync-tmux-plugin.sh"); + +function git(cwd: string, ...args: string[]) { + execFileSync("git", args, { cwd, stdio: "pipe" }); +} + +test("--restore returns a synced TPM checkout to a clean install", () => { + const tempRoot = mkdtempSync(join(tmpdir(), "coding-agents-tmux-sync-")); + const target = join(tempRoot, "plugin"); + + mkdirSync(join(target, "node_modules"), { recursive: true }); + writeFileSync(join(target, "README.md"), "installed version\n"); + writeFileSync(join(target, ".gitignore"), "node_modules/\n"); + writeFileSync(join(target, "node_modules", "keep.txt"), "keep\n"); + + git(target, "init", "-q"); + git(target, "config", "user.name", "Test User"); + git(target, "config", "user.email", "test@example.com"); + git(target, "add", "README.md", ".gitignore"); + git(target, "commit", "-qm", "installed plugin"); + + execFileSync(syncScript, ["--target", target], { cwd: repoRoot, stdio: "pipe" }); + assert.notEqual(readFileSync(join(target, "README.md"), "utf8"), "installed version\n"); + + execFileSync(syncScript, ["--target", target, "--restore"], { + cwd: repoRoot, + stdio: "pipe", + }); + + assert.equal(readFileSync(join(target, "README.md"), "utf8"), "installed version\n"); + assert.equal(readFileSync(join(target, "node_modules", "keep.txt"), "utf8"), "keep\n"); + assert.equal( + execFileSync("git", ["status", "--porcelain"], { cwd: target, encoding: "utf8" }), + "", + ); +});