diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..722602e --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,42 @@ +# Copilot instructions for `distributed-files` + +## Build, test, and lint commands + +Use npm scripts from `package.json`: + +- Install dependencies: `npm ci` +- Build TypeScript: `npm run build` +- Run full test suite: `npm test` (runs `vitest run`) +- Run a single test file: `npm test -- tests/status.test.ts` +- Run a single test by name: `npm test -- -t "determineState (glob patterns)"` + +Linting: + +- `npm run lint` — check for lint/format issues +- `npm run lint:fix` — auto-fix lint/format issues +- `npm run format` — format code + +## High-level architecture + +`dfiles` is a manifest-driven CLI that pulls selected paths from remote Git repos into local destinations. + +- **CLI entrypoint (`src/index.ts`)** wires Commander commands (`init`, `add`, `pull`, `status`, `list`, `remove`) to command modules and centralizes top-level error handling. +- **Manifest system (`src/config.ts`, `src/types.ts`)** defines and validates `dfiles.json` (`version: 1`, `entries[]`). Commands (except `init`) call `requireManifest()`, which discovers the manifest by walking up parent directories. +- **Cache/update layer (`src/cache.ts`)** keeps repo mirrors in `~/.dfiles/cache/`, where hash is SHA-256 of repo URL (first 16 chars). It uses shallow clone/fetch and recovery paths for stale locks, non-fast-forward pulls, and corrupted caches. +- **Sync layer (`src/sync.ts`)** copies file, directory, or glob sources from cache to destination. File copies are hash-based (`sha256`) so unchanged files are skipped. +- **Pull workflow (`src/commands/pull.ts`)** selects entries, groups by `repo#branch`, updates cache once per group, syncs each entry, then prints a summary and exits non-zero if any entry failed. +- **Status workflow (`src/commands/status.ts`)** is offline-only: it compares current local destinations against cached repo content (including directory hashing and glob-aware comparisons) and reports `current`, `outdated`, or `missing`. + +## Key repository conventions + +- **Always normalize destination paths with `resolvePath()`** (`src/config.ts`) so `~` expansion and absolute resolution stay consistent. +- **`source` supports file, directory, and glob semantics**; glob sync/status logic is shared via `isGlobPattern()` + `globBase()` (`src/sync.ts`) and treats glob matches as files with preserved relative structure. +- **Manifest writes use `saveManifest()`** (pretty JSON, 2-space indent) rather than ad hoc file writes. +- **Error model is command-level throw + top-level handling**: command modules usually throw `Error`, and `src/index.ts` converts failures into `Error: ...` plus exit code 1. +- **`pull` behavior is intentionally partial-failure tolerant**: failures are tracked per entry, printed in summary rows, and only then converted to process failure if any errors occurred. +- **Tests use Vitest with focused module mocks** (notably `simple-git` and cache functions), and test files are organized by subsystem under `tests/`. + +## Must follow workflow + +1. Always when implementing a feature or bug fix always create new tests and update exisiting ones as needed. Tests should cover both typical and edge cases. +2. Always make sure documentation is up to date, including README.md and any relevant comments in the code. \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5b1e462..b370376 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,9 @@ jobs: - name: Install dependencies run: npm ci + - name: Lint + run: npm run lint + - name: Build run: npm run build diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..f0fdf2c --- /dev/null +++ b/biome.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.4.12/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "includes": ["src/**", "tests/**"] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 100 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "assist": { + "enabled": true + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "semicolons": "always", + "trailingCommas": "all" + } + } +} diff --git a/package-lock.json b/package-lock.json index db2a181..47b4690 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@madkoo/dfiles", - "version": "1.1.0", + "version": "1.1.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@madkoo/dfiles", - "version": "1.1.0", + "version": "1.1.1", "license": "MIT", "dependencies": { "commander": "^14.0.3", @@ -17,15 +17,179 @@ "dfiles": "dist/index.js" }, "devDependencies": { + "@biomejs/biome": "^2.4.12", "@types/node": "^25.5.0", "ts-node": "^10.9.0", - "typescript": "^5.4.0", + "typescript": "^6.0.2", "vitest": "^4.0.0" }, "engines": { "node": ">=16.7.0" } }, + "node_modules/@biomejs/biome": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.4.12.tgz", + "integrity": "sha512-Rro7adQl3NLq/zJCIL98eElXKI8eEiBtoeu5TbXF/U3qbjuSc7Jb5rjUbeHHcquDWeSf3HnGP7XI5qGrlRk/pA==", + "dev": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "2.4.12", + "@biomejs/cli-darwin-x64": "2.4.12", + "@biomejs/cli-linux-arm64": "2.4.12", + "@biomejs/cli-linux-arm64-musl": "2.4.12", + "@biomejs/cli-linux-x64": "2.4.12", + "@biomejs/cli-linux-x64-musl": "2.4.12", + "@biomejs/cli-win32-arm64": "2.4.12", + "@biomejs/cli-win32-x64": "2.4.12" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.4.12.tgz", + "integrity": "sha512-BnMU4Pc3ciEVteVpZ0BK33MLr7X57F5w1dwDLDn+/iy/yTrA4Q/N2yftidFtsA4vrDh0FMXDpacNV/Tl3fbmng==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.4.12.tgz", + "integrity": "sha512-x9uJ0bI1rJsWICp3VH8w/5PnAVD3A7SqzDpbrfoUQX1QyWrK5jSU4fRLo/wSgGeplCivbxBRKmt5Xq4/nWvq8A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.4.12.tgz", + "integrity": "sha512-tOwuCuZZtKi1jVzbk/5nXmIsziOB6yqN8c9r9QM0EJYPU6DpQWf11uBOSCfFKKM4H3d9ZoarvlgMfbcuD051Pw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.4.12.tgz", + "integrity": "sha512-FhfpkAAlKL6kwvcVap0Hgp4AhZmtd3YImg0kK1jd7C/aSoh4SfsB2f++yG1rU0lr8Y5MCFJrcSkmssiL9Xnnig==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.4.12.tgz", + "integrity": "sha512-8pFeAnLU9QdW9jCIslB/v82bI0lhBmz2ZAKc8pVMFPO0t0wAHsoEkrUQUbMkIorTRIjbqyNZHA3lEXavsPWYSw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.4.12.tgz", + "integrity": "sha512-dwTIgZrGutzhkQCuvHynCkyW6hJxUuyZqKKO0YNfaS2GUoRO+tOvxXZqZB6SkWAOdfZTzwaw8IEdUnIkHKHoew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.4.12.tgz", + "integrity": "sha512-B0DLnx0vA9ya/3v7XyCaP+/lCpnbWbMOfUFFve+xb5OxyYvdHaS55YsSddr228Y+JAFk58agCuZTsqNiw2a6ig==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.4.12.tgz", + "integrity": "sha512-yMckRzTyZ83hkk8iDFWswqSdU8tvZxspJKnYNh7JZr/zhZNOlzH13k4ecboU6MurKExCe2HUkH75pGI/O2JwGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -40,21 +204,21 @@ } }, "node_modules/@emnapi/core": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.0.tgz", - "integrity": "sha512-0DQ98G9ZQZOxfUcQn1waV2yS8aWdZ6kJMbYCJB3oUBecjWYO1fqJ+a1DRfPF3O5JEkwqwP1A9QEN/9mYm2Yd0w==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.2.0", + "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "node_modules/@emnapi/runtime": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.0.tgz", - "integrity": "sha512-QN75eB0IH2ywSpRpNddCRfQIhmJYBCJ1x5Lb3IscKAL8bMnVAKnRg8dCoXbHzVLLH7P38N2Z3mtulB7W0J0FKw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", "dev": true, "license": "MIT", "optional": true, @@ -63,9 +227,9 @@ } }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz", - "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", "dev": true, "license": "MIT", "optional": true, @@ -117,20 +281,22 @@ "license": "MIT" }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", - "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "funding": { "type": "github", "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" } }, "node_modules/@nodelib/fs.scandir": { @@ -169,9 +335,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.120.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.120.0.tgz", - "integrity": "sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==", + "version": "0.124.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz", + "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==", "dev": true, "license": "MIT", "funding": { @@ -179,9 +345,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.10.tgz", - "integrity": "sha512-jOHxwXhxmFKuXztiu1ORieJeTbx5vrTkcOkkkn2d35726+iwhrY1w/+nYY/AGgF12thg33qC3R1LMBF5tHTZHg==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==", "cpu": [ "arm64" ], @@ -196,9 +362,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.10.tgz", - "integrity": "sha512-gED05Teg/vtTZbIJBc4VNMAxAFDUPkuO/rAIyyxZjTj1a1/s6z5TII/5yMGZ0uLRCifEtwUQn8OlYzuYc0m70w==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==", "cpu": [ "arm64" ], @@ -213,9 +379,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.10.tgz", - "integrity": "sha512-rI15NcM1mA48lqrIxVkHfAqcyFLcQwyXWThy+BQ5+mkKKPvSO26ir+ZDp36AgYoYVkqvMcdS8zOE6SeBsR9e8A==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==", "cpu": [ "x64" ], @@ -230,9 +396,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.10.tgz", - "integrity": "sha512-XZRXHdTa+4ME1MuDVp021+doQ+z6Ei4CCFmNc5/sKbqb8YmkiJdj8QKlV3rCI0AJtAeSB5n0WGPuJWNL9p/L2w==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz", + "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==", "cpu": [ "x64" ], @@ -247,9 +413,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.10.tgz", - "integrity": "sha512-R0SQMRluISSLzFE20sPWYHVmJdDQnRyc/FzSCN72BqQmh2SOZUFG+N3/vBZpR4C6WpEUVYJLrYUXaj43sJsNLA==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz", + "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==", "cpu": [ "arm" ], @@ -264,9 +430,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.10.tgz", - "integrity": "sha512-Y1reMrV/o+cwpduYhJuOE3OMKx32RMYCidf14y+HssARRmhDuWXJ4yVguDg2R/8SyyGNo+auzz64LnPK9Hq6jg==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==", "cpu": [ "arm64" ], @@ -281,9 +447,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.10.tgz", - "integrity": "sha512-vELN+HNb2IzuzSBUOD4NHmP9yrGwl1DVM29wlQvx1OLSclL0NgVWnVDKl/8tEks79EFek/kebQKnNJkIAA4W2g==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==", "cpu": [ "arm64" ], @@ -298,9 +464,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.10.tgz", - "integrity": "sha512-ZqrufYTgzxbHwpqOjzSsb0UV/aV2TFIY5rP8HdsiPTv/CuAgCRjM6s9cYFwQ4CNH+hf9Y4erHW1GjZuZ7WoI7w==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==", "cpu": [ "ppc64" ], @@ -315,9 +481,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.10.tgz", - "integrity": "sha512-gSlmVS1FZJSRicA6IyjoRoKAFK7IIHBs7xJuHRSmjImqk3mPPWbR7RhbnfH2G6bcmMEllCt2vQ/7u9e6bBnByg==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==", "cpu": [ "s390x" ], @@ -332,9 +498,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.10.tgz", - "integrity": "sha512-eOCKUpluKgfObT2pHjztnaWEIbUabWzk3qPZ5PuacuPmr4+JtQG4k2vGTY0H15edaTnicgU428XW/IH6AimcQw==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz", + "integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==", "cpu": [ "x64" ], @@ -349,9 +515,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.10.tgz", - "integrity": "sha512-Xdf2jQbfQowJnLcgYfD/m0Uu0Qj5OdxKallD78/IPPfzaiaI4KRAwZzHcKQ4ig1gtg1SuzC7jovNiM2TzQsBXA==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz", + "integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==", "cpu": [ "x64" ], @@ -366,9 +532,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.10.tgz", - "integrity": "sha512-o1hYe8hLi1EY6jgPFyxQgQ1wcycX+qz8eEbVmot2hFkgUzPxy9+kF0u0NIQBeDq+Mko47AkaFFaChcvZa9UX9Q==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz", + "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==", "cpu": [ "arm64" ], @@ -383,9 +549,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.10.tgz", - "integrity": "sha512-Ugv9o7qYJudqQO5Y5y2N2SOo6S4WiqiNOpuQyoPInnhVzCY+wi/GHltcLHypG9DEUYMB0iTB/huJrpadiAcNcA==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz", + "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==", "cpu": [ "wasm32" ], @@ -393,16 +559,18 @@ "license": "MIT", "optional": true, "dependencies": { - "@napi-rs/wasm-runtime": "^1.1.1" + "@emnapi/core": "1.9.2", + "@emnapi/runtime": "1.9.2", + "@napi-rs/wasm-runtime": "^1.1.3" }, "engines": { "node": ">=14.0.0" } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.10.tgz", - "integrity": "sha512-7UODQb4fQUNT/vmgDZBl3XOBAIOutP5R3O/rkxg0aLfEGQ4opbCgU5vOw/scPe4xOqBwL9fw7/RP1vAMZ6QlAQ==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==", "cpu": [ "arm64" ], @@ -417,9 +585,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.10.tgz", - "integrity": "sha512-PYxKHMVHOb5NJuDL53vBUl1VwUjymDcYI6rzpIni0C9+9mTiJedvUxSk7/RPp7OOAm3v+EjgMu9bIy3N6b408w==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz", + "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==", "cpu": [ "x64" ], @@ -434,9 +602,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.10.tgz", - "integrity": "sha512-UkVDEFk1w3mveXeKgaTuYfKWtPbvgck1dT8TUG3bnccrH0XtLTuAyfCoks4Q/M5ZGToSVJTIQYCzy2g/atAOeg==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz", + "integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==", "dev": true, "license": "MIT" }, @@ -1302,14 +1470,14 @@ } }, "node_modules/rolldown": { - "version": "1.0.0-rc.10", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.10.tgz", - "integrity": "sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA==", + "version": "1.0.0-rc.15", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", + "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.120.0", - "@rolldown/pluginutils": "1.0.0-rc.10" + "@oxc-project/types": "=0.124.0", + "@rolldown/pluginutils": "1.0.0-rc.15" }, "bin": { "rolldown": "bin/cli.mjs" @@ -1318,21 +1486,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.10", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.10", - "@rolldown/binding-darwin-x64": "1.0.0-rc.10", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.10", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.10", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.10", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.10", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.10", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.10", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.10", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.10", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.10", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.10", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.10", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.10" + "@rolldown/binding-android-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", + "@rolldown/binding-darwin-x64": "1.0.0-rc.15", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" } }, "node_modules/run-parallel": { @@ -1457,9 +1625,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -1544,9 +1712,9 @@ "optional": true }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", + "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1572,16 +1740,16 @@ "license": "MIT" }, "node_modules/vite": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.1.tgz", - "integrity": "sha512-wt+Z2qIhfFt85uiyRt5LPU4oVEJBXj8hZNWKeqFG4gRG/0RaRGJ7njQCwzFVjO+v4+Ipmf5CY7VdmZRAYYBPHw==", + "version": "8.0.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", + "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", - "picomatch": "^4.0.3", + "picomatch": "^4.0.4", "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.10", + "rolldown": "1.0.0-rc.15", "tinyglobby": "^0.2.15" }, "bin": { @@ -1599,7 +1767,7 @@ "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", - "esbuild": "^0.27.0", + "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", @@ -1650,9 +1818,9 @@ } }, "node_modules/vite/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -1745,9 +1913,9 @@ } }, "node_modules/vitest/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 52adf7c..cb66f7b 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,20 @@ { "name": "@madkoo/dfiles", - "version": "1.1.1", + "version": "1.1.2", "description": "Manage specific files/folders from multiple git repositories", "bin": { "dfiles": "./dist/index.js" }, "main": "./dist/index.js", - "files": ["dist"], + "files": [ + "dist" + ], "scripts": { "build": "tsc", "dev": "ts-node src/index.ts", + "lint": "biome check src/ tests/", + "lint:fix": "biome check --fix src/ tests/", + "format": "biome format --write src/ tests/", "prepublishOnly": "npm run build", "test": "vitest run" }, @@ -19,9 +24,10 @@ "simple-git": "^3.22.0" }, "devDependencies": { + "@biomejs/biome": "^2.4.12", "@types/node": "^25.5.0", "ts-node": "^10.9.0", - "typescript": "^5.4.0", + "typescript": "^6.0.2", "vitest": "^4.0.0" }, "engines": { diff --git a/src/cache.ts b/src/cache.ts index f71ac90..f18743d 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -1,7 +1,7 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as os from 'os'; -import * as crypto from 'crypto'; +import * as crypto from 'node:crypto'; +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; import simpleGit from 'simple-git'; export const CACHE_BASE = path.join(os.homedir(), '.dfiles', 'cache'); @@ -70,7 +70,7 @@ export async function ensureCached(repoUrl: string, branch: string): Promise { const { dir, manifest } = requireManifest(); @@ -54,8 +54,7 @@ export async function addCommand( } const duplicate = manifest.entries.find( - (entry) => - entry.repo === repo && entry.source === source && entry.destination === destination + (entry) => entry.repo === repo && entry.source === source && entry.destination === destination, ); if (duplicate) { diff --git a/src/commands/init.ts b/src/commands/init.ts index 600ec50..454fe68 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -1,7 +1,7 @@ -import * as fs from 'fs'; -import * as path from 'path'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; import { MANIFEST_FILENAME, saveManifest } from '../config'; -import { Manifest } from '../types'; +import type { Manifest } from '../types'; export async function initCommand(directory: string | undefined): Promise { const targetDir = path.resolve(directory ?? process.cwd()); diff --git a/src/commands/list.ts b/src/commands/list.ts index 7fda7b2..694cbee 100644 --- a/src/commands/list.ts +++ b/src/commands/list.ts @@ -44,9 +44,7 @@ export async function listCommand(options: ListOptions): Promise { }); const formatRow = (values: string[]): string => { - return values - .map((value, index) => value.padEnd(widths[index])) - .join(' '); + return values.map((value, index) => value.padEnd(widths[index])).join(' '); }; console.log(formatRow(columns)); diff --git a/src/commands/pull.ts b/src/commands/pull.ts index 9287e16..d340f3e 100644 --- a/src/commands/pull.ts +++ b/src/commands/pull.ts @@ -1,7 +1,7 @@ -import { requireManifest } from '../config'; import { ensureCached } from '../cache'; +import { requireManifest } from '../config'; import { syncEntry } from '../sync'; -import { PullResult, ManifestEntry } from '../types'; +import type { ManifestEntry, PullResult } from '../types'; interface PullOptions { dryRun?: boolean; @@ -50,18 +50,25 @@ function printSummary(results: PullResult[]): void { return; } - const statusWidth = Math.max('unchanged'.length, ...results.map((result) => result.status.length)); + const statusWidth = Math.max( + 'unchanged'.length, + ...results.map((result) => result.status.length), + ); const idWidth = Math.max(...results.map((result) => result.entry.id.length)); for (const result of results) { if (result.status === 'error') { const errorText = result.error ?? 'Unknown error'; - console.log(`✗ ${'error'.padEnd(statusWidth)} ${result.entry.id.padEnd(idWidth)} ${errorText}`); + console.log( + `✗ ${'error'.padEnd(statusWidth)} ${result.entry.id.padEnd(idWidth)} ${errorText}`, + ); continue; } const marker = result.status === 'updated' ? '✓' : ' '; - console.log(`${marker} ${result.status.padEnd(statusWidth)} ${result.entry.id.padEnd(idWidth)} ${result.entry.destination}`); + console.log( + `${marker} ${result.status.padEnd(statusWidth)} ${result.entry.id.padEnd(idWidth)} ${result.entry.destination}`, + ); } } @@ -78,7 +85,9 @@ export async function pullCommand(ids: string[], options: PullOptions): Promise< if (options.dryRun) { for (const entry of groupEntries) { - console.log(`[dry-run] Would pull ${entry.source} from ${entry.repo} → ${entry.destination}`); + console.log( + `[dry-run] Would pull ${entry.source} from ${entry.repo} → ${entry.destination}`, + ); results.push({ entry, status: 'unchanged' }); } continue; @@ -119,4 +128,4 @@ export async function pullCommand(ids: string[], options: PullOptions): Promise< if (results.some((result) => result.status === 'error')) { process.exit(1); } -} \ No newline at end of file +} diff --git a/src/commands/status.ts b/src/commands/status.ts index b42b1ac..effb61a 100644 --- a/src/commands/status.ts +++ b/src/commands/status.ts @@ -1,11 +1,11 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as crypto from 'crypto'; +import * as crypto from 'node:crypto'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; import fg from 'fast-glob'; -import { requireManifest, resolvePath } from '../config'; import { getCacheDir } from '../cache'; -import { fileHash, isGlobPattern, globBase } from '../sync'; -import { StatusResult, ManifestEntry } from '../types'; +import { requireManifest, resolvePath } from '../config'; +import { fileHash, globBase, isGlobPattern } from '../sync'; +import type { ManifestEntry, StatusResult } from '../types'; function collectDirectoryEntries(rootDir: string, currentDir: string, entries: string[]): void { const children = fs.readdirSync(currentDir, { withFileTypes: true }); @@ -140,9 +140,7 @@ export async function statusCommand(): Promise { }); const formatRow = (values: readonly string[]): string => { - return values - .map((value, index) => value.padEnd(widths[index])) - .join(' '); + return values.map((value, index) => value.padEnd(widths[index])).join(' '); }; console.log(formatRow(headers)); @@ -153,4 +151,4 @@ export async function statusCommand(): Promise { for (const row of rows) { console.log(formatRow(row)); } -} \ No newline at end of file +} diff --git a/src/config.ts b/src/config.ts index 84b4e29..c0f6af3 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,7 +1,7 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as os from 'os'; -import { Manifest } from './types'; +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; +import type { Manifest } from './types'; export const MANIFEST_FILENAME = 'dfiles.json'; @@ -84,7 +84,7 @@ export function loadManifest(dir: string): Manifest { if (!fs.existsSync(manifestPath)) { throw new Error( - `Manifest file not found at "${manifestPath}". Run "dfiles init" in your project root first.` + `Manifest file not found at "${manifestPath}". Run "dfiles init" in your project root first.`, ); } @@ -99,7 +99,7 @@ export function loadManifest(dir: string): Manifest { if (!isManifest(parsed)) { throw new Error( - `Manifest at "${manifestPath}" is malformed. Expected shape: { "version": 1, "entries": ManifestEntry[] }.` + `Manifest at "${manifestPath}" is malformed. Expected shape: { "version": 1, "entries": ManifestEntry[] }.`, ); } @@ -122,7 +122,7 @@ export function requireManifest(): { dir: string; manifest: Manifest } { const dir = findManifestDir(); if (!dir) { throw new Error( - `No ${MANIFEST_FILENAME} found in this directory or any parent directory. Run "dfiles init" in your project root first.` + `No ${MANIFEST_FILENAME} found in this directory or any parent directory. Run "dfiles init" in your project root first.`, ); } diff --git a/src/index.ts b/src/index.ts index 8aad515..89328a0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,10 @@ #!/usr/bin/env node import { Command } from 'commander'; -import { initCommand } from './commands/init'; import { addCommand } from './commands/add'; +import { initCommand } from './commands/init'; +import { listCommand } from './commands/list'; import { pullCommand } from './commands/pull'; import { statusCommand } from './commands/status'; -import { listCommand } from './commands/list'; import { requireManifest, saveManifest } from './config'; const program = new Command(); @@ -37,10 +37,10 @@ program repo: string, source: string, destination: string, - options: { branch: string; id?: string } + options: { branch: string; id?: string }, ) => { await addCommand(repo, source, destination, options); - } + }, ); // pull command diff --git a/src/sync.ts b/src/sync.ts index 82ead3f..78a41bf 100644 --- a/src/sync.ts +++ b/src/sync.ts @@ -1,9 +1,9 @@ -import * as fs from 'fs'; -import * as path from 'path'; -import * as crypto from 'crypto'; +import * as crypto from 'node:crypto'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; import fg from 'fast-glob'; -import { ManifestEntry } from './types'; import { resolvePath } from './config'; +import type { ManifestEntry } from './types'; /** * Returns SHA-256 hash of file contents, or null if file doesn't exist. @@ -43,7 +43,7 @@ export function globBase(pattern: string): string { */ export async function syncEntry( entry: ManifestEntry, - cacheDir: string + cacheDir: string, ): Promise<'updated' | 'unchanged'> { const destPath = resolvePath(entry.destination); @@ -83,7 +83,7 @@ export async function syncEntry( sourceStat = fs.statSync(sourcePath); } catch { throw new Error( - `Source path "${entry.source}" does not exist in cache "${cacheDir}" (resolved: "${sourcePath}").` + `Source path "${entry.source}" does not exist in cache "${cacheDir}" (resolved: "${sourcePath}").`, ); } @@ -107,6 +107,6 @@ export async function syncEntry( } throw new Error( - `Source path "${sourcePath}" in cache is neither a regular file nor a directory and cannot be synced.` + `Source path "${sourcePath}" in cache is neither a regular file nor a directory and cannot be synced.`, ); -} \ No newline at end of file +} diff --git a/tests/cache.test.ts b/tests/cache.test.ts index f6cfe3a..5483024 100644 --- a/tests/cache.test.ts +++ b/tests/cache.test.ts @@ -1,7 +1,6 @@ +import * as fs from 'node:fs'; +import * as path from 'node:path'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; // --------------------------------------------------------------------------- // Module-level mock — must appear before the import of the module under test @@ -17,7 +16,7 @@ const mockGit = { vi.mock('simple-git', () => ({ default: vi.fn(() => mockGit) })); -import { getRepoHash, getCacheDir, CACHE_BASE, ensureCached } from '../src/cache'; +import { CACHE_BASE, ensureCached, getCacheDir, getRepoHash } from '../src/cache'; // --------------------------------------------------------------------------- // Helpers @@ -28,20 +27,18 @@ const BRANCH = 'main'; const tmpDirs: string[] = []; -function makeTempDir(): string { - const d = fs.mkdtempSync(path.join(os.tmpdir(), 'dfiles-cache-test-')); - tmpDirs.push(d); - return d; -} - afterEach(() => { - tmpDirs.forEach(d => fs.rmSync(d, { recursive: true, force: true })); + tmpDirs.forEach((d) => { + fs.rmSync(d, { recursive: true, force: true }); + }); tmpDirs.length = 0; vi.restoreAllMocks(); }); beforeEach(() => { - Object.values(mockGit).forEach(m => m.mockReset().mockResolvedValue(undefined)); + Object.values(mockGit).forEach((m) => { + m.mockReset().mockResolvedValue(undefined); + }); }); // --------------------------------------------------------------------------- @@ -83,11 +80,12 @@ describe('ensureCached — not-cached path', () => { const result = await ensureCached(TEST_URL, BRANCH); // Assert - expect(mockGit.clone).toHaveBeenCalledWith( - TEST_URL, - cacheDir, - ['--depth', '1', '--branch', BRANCH], - ); + expect(mockGit.clone).toHaveBeenCalledWith(TEST_URL, cacheDir, [ + '--depth', + '1', + '--branch', + BRANCH, + ]); expect(result).toBe(cacheDir); }); @@ -103,11 +101,12 @@ describe('ensureCached — not-cached path', () => { // Assert — real rmSync ran so the dir is gone, then clone was called expect(fs.existsSync(cacheDir)).toBe(false); - expect(mockGit.clone).toHaveBeenCalledWith( - TEST_URL, - cacheDir, - ['--depth', '1', '--branch', BRANCH], - ); + expect(mockGit.clone).toHaveBeenCalledWith(TEST_URL, cacheDir, [ + '--depth', + '1', + '--branch', + BRANCH, + ]); }); it('throws an error containing "Failed to clone repository" when clone rejects', async () => { @@ -205,7 +204,9 @@ describe('ensureCached — cached path', () => { mockGit.fetch.mockRejectedValue(new Error('network timeout')); // Act / Assert - await expect(ensureCached(TEST_URL, BRANCH)).rejects.toThrow('Failed to update cached repository'); + await expect(ensureCached(TEST_URL, BRANCH)).rejects.toThrow( + 'Failed to update cached repository', + ); }); }); @@ -277,7 +278,9 @@ describe('ensureCached — cached path', () => { mockGit.pull.mockRejectedValue(new Error('connection refused')); // Act / Assert - await expect(ensureCached(TEST_URL, BRANCH)).rejects.toThrow('Failed to update cached repository'); + await expect(ensureCached(TEST_URL, BRANCH)).rejects.toThrow( + 'Failed to update cached repository', + ); }); }); }); diff --git a/tests/status.test.ts b/tests/status.test.ts index 61e0e25..98d6227 100644 --- a/tests/status.test.ts +++ b/tests/status.test.ts @@ -1,7 +1,7 @@ +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; import { afterEach, describe, expect, it, vi } from 'vitest'; -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; vi.mock('../src/cache', () => ({ getCacheDir: vi.fn(), @@ -23,7 +23,9 @@ function makeTempDir(): string { return dir; } -function makeEntry(overrides: Partial & { source: string; destination: string }): ManifestEntry { +function makeEntry( + overrides: Partial & { source: string; destination: string }, +): ManifestEntry { return { id: 'test', repo: 'https://github.com/org/repo', branch: 'main', ...overrides }; } @@ -44,7 +46,9 @@ describe('determineState (glob patterns)', () => { const destDir = makeTempDir(); vi.mocked(getCacheDir).mockReturnValue(cacheDir); - expect(determineState(makeEntry({ source: 'skills/canvas/**', destination: destDir }))).toBe('missing'); + expect(determineState(makeEntry({ source: 'skills/canvas/**', destination: destDir }))).toBe( + 'missing', + ); }); it('returns missing when glob matches no files in cache', () => { @@ -52,7 +56,9 @@ describe('determineState (glob patterns)', () => { const destDir = makeTempDir(); vi.mocked(getCacheDir).mockReturnValue(cacheDir); - expect(determineState(makeEntry({ source: 'skills/canvas/**', destination: destDir }))).toBe('missing'); + expect(determineState(makeEntry({ source: 'skills/canvas/**', destination: destDir }))).toBe( + 'missing', + ); }); it('returns missing when a matched file is absent from destination', () => { @@ -64,7 +70,9 @@ describe('determineState (glob patterns)', () => { fs.writeFileSync(path.join(cacheDir, 'skills/canvas/SKILL.md'), '# Skill'); // destDir exists but SKILL.md is not there - expect(determineState(makeEntry({ source: 'skills/canvas/**', destination: destDir }))).toBe('missing'); + expect(determineState(makeEntry({ source: 'skills/canvas/**', destination: destDir }))).toBe( + 'missing', + ); }); it('returns outdated when a matched file differs from destination', () => { @@ -76,7 +84,9 @@ describe('determineState (glob patterns)', () => { fs.writeFileSync(path.join(cacheDir, 'skills/canvas/SKILL.md'), '# Updated Skill'); fs.writeFileSync(path.join(destDir, 'SKILL.md'), '# Old Skill'); - expect(determineState(makeEntry({ source: 'skills/canvas/**', destination: destDir }))).toBe('outdated'); + expect(determineState(makeEntry({ source: 'skills/canvas/**', destination: destDir }))).toBe( + 'outdated', + ); }); it('returns current when all matched files are identical to destination', () => { @@ -91,7 +101,9 @@ describe('determineState (glob patterns)', () => { fs.writeFileSync(path.join(destDir, 'SKILL.md'), '# Skill'); fs.writeFileSync(path.join(destDir, 'examples/demo.md'), '# Demo'); - expect(determineState(makeEntry({ source: 'skills/canvas/**', destination: destDir }))).toBe('current'); + expect(determineState(makeEntry({ source: 'skills/canvas/**', destination: destDir }))).toBe( + 'current', + ); }); it('returns outdated when a nested file differs', () => { @@ -106,6 +118,8 @@ describe('determineState (glob patterns)', () => { fs.writeFileSync(path.join(destDir, 'SKILL.md'), '# Skill'); fs.writeFileSync(path.join(destDir, 'examples/demo.md'), '# Old Demo'); - expect(determineState(makeEntry({ source: 'skills/canvas/**', destination: destDir }))).toBe('outdated'); + expect(determineState(makeEntry({ source: 'skills/canvas/**', destination: destDir }))).toBe( + 'outdated', + ); }); -}); \ No newline at end of file +}); diff --git a/tests/sync.test.ts b/tests/sync.test.ts index a68ec03..e0be277 100644 --- a/tests/sync.test.ts +++ b/tests/sync.test.ts @@ -1,7 +1,7 @@ +import * as fs from 'node:fs'; +import * as os from 'node:os'; +import * as path from 'node:path'; import { afterEach, describe, expect, it, vi } from 'vitest'; -import * as fs from 'fs'; -import * as os from 'os'; -import * as path from 'path'; import { globBase, isGlobPattern, syncEntry } from '../src/sync'; // --------------------------------------------------------------------------- @@ -72,8 +72,14 @@ describe('syncEntry (glob patterns)', () => { fs.writeFileSync(path.join(cacheDir, 'skills/canvas/examples/demo.md'), '# Demo'); const result = await syncEntry( - { id: 'test', repo: 'https://github.com/org/repo', branch: 'main', source: 'skills/canvas/**', destination: destDir }, - cacheDir + { + id: 'test', + repo: 'https://github.com/org/repo', + branch: 'main', + source: 'skills/canvas/**', + destination: destDir, + }, + cacheDir, ); expect(result).toBe('updated'); @@ -90,8 +96,14 @@ describe('syncEntry (glob patterns)', () => { fs.writeFileSync(path.join(destDir, 'SKILL.md'), '# Skill'); const result = await syncEntry( - { id: 'test', repo: 'https://github.com/org/repo', branch: 'main', source: 'skills/canvas/**', destination: destDir }, - cacheDir + { + id: 'test', + repo: 'https://github.com/org/repo', + branch: 'main', + source: 'skills/canvas/**', + destination: destDir, + }, + cacheDir, ); expect(result).toBe('unchanged'); @@ -108,8 +120,14 @@ describe('syncEntry (glob patterns)', () => { fs.writeFileSync(path.join(destDir, 'README.md'), '# Readme'); const result = await syncEntry( - { id: 'test', repo: 'https://github.com/org/repo', branch: 'main', source: 'skills/canvas/**', destination: destDir }, - cacheDir + { + id: 'test', + repo: 'https://github.com/org/repo', + branch: 'main', + source: 'skills/canvas/**', + destination: destDir, + }, + cacheDir, ); expect(result).toBe('updated'); @@ -124,8 +142,14 @@ describe('syncEntry (glob patterns)', () => { const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true); const result = await syncEntry( - { id: 'test', repo: 'https://github.com/org/repo', branch: 'main', source: 'nonexistent/**', destination: destDir }, - cacheDir + { + id: 'test', + repo: 'https://github.com/org/repo', + branch: 'main', + source: 'nonexistent/**', + destination: destDir, + }, + cacheDir, ); expect(result).toBe('unchanged'); @@ -142,8 +166,14 @@ describe('syncEntry (glob patterns)', () => { fs.writeFileSync(path.join(cacheDir, 'skills/canvas/image.png'), 'binary'); await syncEntry( - { id: 'test', repo: 'https://github.com/org/repo', branch: 'main', source: 'skills/canvas/*.md', destination: destDir }, - cacheDir + { + id: 'test', + repo: 'https://github.com/org/repo', + branch: 'main', + source: 'skills/canvas/*.md', + destination: destDir, + }, + cacheDir, ); expect(fs.existsSync(path.join(destDir, 'SKILL.md'))).toBe(true);