├── .cargo └── config.toml ├── .github └── workflows │ └── CI.yml ├── .gitignore ├── .npmignore ├── .vscode ├── extensions.json.template └── settings.json.template ├── Cargo.lock ├── Cargo.toml ├── README.md ├── __test__ ├── main.spec.ts ├── threads.spec.ts └── worker.js ├── benchmark.mjs ├── binding.d.ts ├── binding.js ├── build.rs ├── fixtures ├── ascii-file.js └── sample.pyc ├── index.d.ts ├── index.js ├── infinite_clone_test.mjs ├── npm ├── darwin-arm64 │ ├── README.md │ └── package.json ├── darwin-x64 │ ├── README.md │ └── package.json ├── linux-arm64-gnu │ ├── README.md │ └── package.json ├── linux-arm64-musl │ ├── README.md │ └── package.json ├── linux-x64-gnu │ ├── README.md │ └── package.json ├── linux-x64-musl │ ├── README.md │ └── package.json ├── win32-arm64-msvc │ ├── README.md │ └── package.json └── win32-x64-msvc │ ├── README.md │ └── package.json ├── package.json ├── pnpm-lock.yaml ├── rust-toolchain ├── src ├── error.rs └── lib.rs ├── tsconfig.json └── vitest.config.mts /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.aarch64-unknown-linux-musl] 2 | linker = "aarch64-linux-musl-gcc" 3 | rustflags = ["-C", "target-feature=-crt-static"] -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | env: 3 | DEBUG: napi:* 4 | APP_NAME: reflink 5 | MACOSX_DEPLOYMENT_TARGET: '10.13' 6 | permissions: 7 | contents: write 8 | id-token: write 9 | 10 | 'on': 11 | push: 12 | branches: 13 | - main 14 | tags-ignore: 15 | - '**' 16 | paths-ignore: 17 | - '**/*.md' 18 | - LICENSE 19 | - '**/*.gitignore' 20 | - .editorconfig 21 | - docs/** 22 | pull_request: null 23 | 24 | jobs: 25 | build: 26 | strategy: 27 | fail-fast: false 28 | matrix: 29 | settings: 30 | - host: macos-13 31 | target: x86_64-apple-darwin 32 | build: | 33 | pnpm build 34 | strip -x *.node 35 | - host: windows-latest 36 | build: pnpm build 37 | target: x86_64-pc-windows-msvc 38 | - host: ubuntu-latest 39 | target: x86_64-unknown-linux-gnu 40 | docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian 41 | build: |- 42 | set -e && 43 | rustup target add x86_64-unknown-linux-gnu && 44 | pnpm build --target x86_64-unknown-linux-gnu && 45 | strip *.node 46 | - host: ubuntu-latest 47 | target: x86_64-unknown-linux-musl 48 | docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine 49 | build: set -e && pnpm build && strip *.node 50 | - host: macos-latest 51 | target: aarch64-apple-darwin 52 | build: | 53 | pnpm build --target aarch64-apple-darwin 54 | strip -x *.node 55 | - host: ubuntu-latest 56 | target: aarch64-unknown-linux-gnu 57 | docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64 58 | build: |- 59 | set -e && 60 | rustup target add aarch64-unknown-linux-gnu && 61 | pnpm build --target aarch64-unknown-linux-gnu && 62 | aarch64-unknown-linux-gnu-strip *.node 63 | - host: ubuntu-latest 64 | target: aarch64-unknown-linux-musl 65 | docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine 66 | build: |- 67 | set -e && 68 | rustup target add aarch64-unknown-linux-musl && 69 | pnpm build --target aarch64-unknown-linux-musl && 70 | /aarch64-linux-musl-cross/bin/aarch64-linux-musl-strip *.node 71 | - host: windows-latest 72 | target: aarch64-pc-windows-msvc 73 | build: |- 74 | set -e && 75 | rustup target add aarch64-pc-windows-msvc && 76 | pnpm build --target aarch64-pc-windows-msvc 77 | name: stable - ${{ matrix.settings.target }} - node@22 78 | runs-on: ${{ matrix.settings.host }} 79 | steps: 80 | - uses: actions/checkout@v3 81 | - name: Setup node 82 | uses: actions/setup-node@v3 83 | if: ${{ !matrix.settings.docker }} 84 | with: 85 | node-version: 22 86 | 87 | - name: Setup pnpm 88 | uses: pnpm/action-setup@v2 89 | with: 90 | version: 9.12.3 91 | run_install: false 92 | standalone: true 93 | 94 | - name: Get pnpm store directory 95 | shell: bash 96 | run: | 97 | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV 98 | 99 | - name: Install 100 | uses: dtolnay/rust-toolchain@stable 101 | if: ${{ !matrix.settings.docker }} 102 | with: 103 | toolchain: stable 104 | targets: ${{ matrix.settings.target }} 105 | 106 | - name: Cache cargo 107 | uses: actions/cache@v3 108 | with: 109 | path: | 110 | ~/.cargo/registry/index/ 111 | ~/.cargo/registry/cache/ 112 | ~/.cargo/git/db/ 113 | .cargo-cache 114 | target/ 115 | ${{ env.STORE_PATH }} 116 | key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.host }} 117 | 118 | - uses: goto-bus-stop/setup-zig@v2 119 | if: ${{ matrix.settings.target == 'armv7-unknown-linux-gnueabihf' }} 120 | with: 121 | version: 0.10.1 122 | - name: Setup toolchain 123 | run: ${{ matrix.settings.setup }} 124 | if: ${{ matrix.settings.setup }} 125 | shell: bash 126 | - name: Setup node x86 127 | if: matrix.settings.target == 'i686-pc-windows-msvc' 128 | run: pnpm dlx @ialdama/jsonmod --key pnpm.supportedArchitectures.cpu --values ia32 current 129 | shell: bash 130 | - name: Install dependencies 131 | run: pnpm install 132 | - name: Setup node x86 133 | uses: actions/setup-node@v3 134 | if: matrix.settings.target == 'i686-pc-windows-msvc' 135 | with: 136 | node-version: 22 137 | check-latest: true 138 | architecture: x86 139 | - name: Build in docker 140 | uses: addnab/docker-run-action@v3 141 | if: ${{ matrix.settings.docker }} 142 | with: 143 | image: ${{ matrix.settings.docker }} 144 | options: '--user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index -v ${{ github.workspace }}:/build -w /build' 145 | run: ${{ matrix.settings.build }} 146 | - name: Build 147 | run: ${{ matrix.settings.build }} 148 | if: ${{ !matrix.settings.docker }} 149 | shell: bash 150 | - name: Upload artifact 151 | uses: actions/upload-artifact@v3 152 | with: 153 | name: bindings-${{ matrix.settings.target }} 154 | path: ${{ env.APP_NAME }}.*.node 155 | if-no-files-found: error 156 | test-macOS-windows-binding: 157 | name: Test bindings on ${{ matrix.settings.target }} - node@${{ matrix.node }} 158 | needs: 159 | - build 160 | strategy: 161 | fail-fast: false 162 | matrix: 163 | settings: 164 | - host: macos-13 165 | target: x86_64-apple-darwin 166 | - host: windows-latest 167 | target: x86_64-pc-windows-msvc 168 | node: 169 | - '20' 170 | - '22' 171 | runs-on: ${{ matrix.settings.host }} 172 | steps: 173 | - uses: actions/checkout@v3 174 | - name: Setup node 175 | uses: actions/setup-node@v3 176 | with: 177 | node-version: ${{ matrix.node }} 178 | check-latest: true 179 | 180 | - name: Setup pnpm 181 | uses: pnpm/action-setup@v2 182 | with: 183 | version: 9.12.3 184 | run_install: false 185 | standalone: true 186 | 187 | - name: Install dependencies 188 | run: pnpm install 189 | - name: Download artifacts 190 | uses: actions/download-artifact@v3 191 | with: 192 | name: bindings-${{ matrix.settings.target }} 193 | path: . 194 | - name: List packages 195 | run: ls -R . 196 | shell: bash 197 | - name: Test bindings 198 | run: pnpm test || true 199 | test-linux-x64-gnu-binding: 200 | name: Test bindings on Linux-x64-gnu - node@${{ matrix.node }} 201 | needs: 202 | - build 203 | strategy: 204 | fail-fast: false 205 | matrix: 206 | node: 207 | - '20' 208 | - '22' 209 | runs-on: ubuntu-latest 210 | steps: 211 | - uses: actions/checkout@v3 212 | - name: Setup node 213 | uses: actions/setup-node@v3 214 | with: 215 | node-version: ${{ matrix.node }} 216 | check-latest: true 217 | 218 | - name: Setup pnpm 219 | uses: pnpm/action-setup@v2 220 | with: 221 | version: 9.12.3 222 | run_install: false 223 | standalone: true 224 | 225 | - name: Install dependencies 226 | run: pnpm install 227 | - name: Download artifacts 228 | uses: actions/download-artifact@v3 229 | with: 230 | name: bindings-x86_64-unknown-linux-gnu 231 | path: . 232 | - name: List packages 233 | run: ls -R . 234 | shell: bash 235 | - name: Test bindings 236 | run: docker run --rm -v $(pwd):/build -w /build node:${{ matrix.node }}-slim pnpm test || true 237 | test-linux-x64-musl-binding: 238 | name: Test bindings on x86_64-unknown-linux-musl - node@${{ matrix.node }} 239 | needs: 240 | - build 241 | strategy: 242 | fail-fast: false 243 | matrix: 244 | node: 245 | - '20' 246 | - '22' 247 | runs-on: ubuntu-latest 248 | steps: 249 | - uses: actions/checkout@v3 250 | - name: Setup node 251 | uses: actions/setup-node@v3 252 | with: 253 | node-version: ${{ matrix.node }} 254 | check-latest: true 255 | 256 | - name: Setup pnpm 257 | uses: pnpm/action-setup@v2 258 | with: 259 | version: 9.12.3 260 | run_install: false 261 | standalone: true 262 | 263 | - name: Install dependencies 264 | run: | 265 | pnpm dlx @ialdama/jsonmod --key pnpm.supportedArchitectures.libc --values musl current 266 | pnpm install 267 | - name: Download artifacts 268 | uses: actions/download-artifact@v3 269 | with: 270 | name: bindings-x86_64-unknown-linux-musl 271 | path: . 272 | - name: List packages 273 | run: ls -R . 274 | shell: bash 275 | - name: Test bindings 276 | run: docker run --rm -v $(pwd):/build -w /build node:${{ matrix.node }}-alpine pnpm test || true 277 | test-linux-aarch64-gnu-binding: 278 | name: Test bindings on aarch64-unknown-linux-gnu - node@${{ matrix.node }} 279 | needs: 280 | - build 281 | strategy: 282 | fail-fast: false 283 | matrix: 284 | node: 285 | - '20' 286 | - '22' 287 | runs-on: ubuntu-latest 288 | steps: 289 | - uses: actions/checkout@v3 290 | - name: Download artifacts 291 | uses: actions/download-artifact@v3 292 | with: 293 | name: bindings-aarch64-unknown-linux-gnu 294 | path: . 295 | 296 | - name: Setup pnpm 297 | uses: pnpm/action-setup@v2 298 | with: 299 | version: 9.12.3 300 | run_install: false 301 | standalone: true 302 | 303 | - name: List packages 304 | run: ls -R . 305 | shell: bash 306 | - name: Install dependencies 307 | run: | 308 | pnpm dlx @ialdama/jsonmod --key pnpm.supportedArchitectures.cpu --values arm64 current 309 | pnpm dlx @ialdama/jsonmod --key pnpm.supportedArchitectures.libc --values glibc current 310 | pnpm install 311 | - name: Set up QEMU 312 | uses: docker/setup-qemu-action@v2 313 | with: 314 | platforms: arm64 315 | - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 316 | - name: Setup and run tests 317 | uses: addnab/docker-run-action@v3 318 | with: 319 | image: node:${{ matrix.node }}-slim 320 | options: '--platform linux/arm64 -v ${{ github.workspace }}:/build -w /build' 321 | run: | 322 | curl -fsSL https://get.pnpm.io/install.sh | sh - 323 | set -e 324 | pnpm test || true 325 | ls -la 326 | test-linux-aarch64-musl-binding: 327 | name: Test bindings on aarch64-unknown-linux-musl - node@${{ matrix.node }} 328 | needs: 329 | - build 330 | runs-on: ubuntu-latest 331 | steps: 332 | - uses: actions/checkout@v3 333 | - name: Download artifacts 334 | uses: actions/download-artifact@v3 335 | with: 336 | name: bindings-aarch64-unknown-linux-musl 337 | path: . 338 | 339 | - name: Setup pnpm 340 | uses: pnpm/action-setup@v2 341 | with: 342 | version: 9.12.3 343 | run_install: false 344 | standalone: true 345 | 346 | - name: List packages 347 | run: ls -R . 348 | shell: bash 349 | - name: Install dependencies 350 | run: | 351 | pnpm dlx @ialdama/jsonmod --key pnpm.supportedArchitectures.cpu --values arm64 current 352 | pnpm dlx @ialdama/jsonmod --key pnpm.supportedArchitectures.libc --values musl current 353 | pnpm install 354 | - name: Set up QEMU 355 | uses: docker/setup-qemu-action@v2 356 | with: 357 | platforms: arm64 358 | - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 359 | - name: Setup and run tests 360 | uses: addnab/docker-run-action@v3 361 | with: 362 | image: node:lts-alpine 363 | options: '--platform linux/arm64 -v ${{ github.workspace }}:/build -w /build' 364 | run: | 365 | curl -fsSL https://get.pnpm.io/install.sh | sh - 366 | set -e 367 | pnpm test || true 368 | publish: 369 | name: Publish 370 | runs-on: ubuntu-latest 371 | needs: 372 | - test-macOS-windows-binding 373 | - test-linux-x64-gnu-binding 374 | - test-linux-x64-musl-binding 375 | - test-linux-aarch64-gnu-binding 376 | - test-linux-aarch64-musl-binding 377 | steps: 378 | - uses: actions/checkout@v3 379 | - name: Setup node 380 | uses: actions/setup-node@v3 381 | with: 382 | node-version: 22 383 | check-latest: true 384 | 385 | - name: Setup pnpm 386 | uses: pnpm/action-setup@v2 387 | with: 388 | version: 9.12.3 389 | run_install: false 390 | standalone: true 391 | 392 | - name: Install dependencies 393 | run: pnpm install 394 | - name: Download all artifacts 395 | uses: actions/download-artifact@v3 396 | with: 397 | path: artifacts 398 | - name: Move artifacts 399 | run: pnpm artifacts 400 | - name: List packages 401 | run: ls -R ./npm 402 | shell: bash 403 | - name: Publish 404 | run: | 405 | npm config set provenance true 406 | if git log -1 --pretty=%B | grep "^[0-9]\+\.[0-9]\+\.[0-9]\+$"; 407 | then 408 | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc 409 | npm publish --access public 410 | elif git log -1 --pretty=%B | grep "^[0-9]\+\.[0-9]\+\.[0-9]\+"; 411 | then 412 | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc 413 | npm publish --tag next --access public 414 | else 415 | echo "Not a release, skipping publish" 416 | fi 417 | env: 418 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 419 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 420 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/node 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=node 3 | 4 | ### Node ### 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | node_modules/ 46 | jspm_packages/ 47 | 48 | # TypeScript v1 declaration files 49 | typings/ 50 | 51 | # TypeScript cache 52 | *.tsbuildinfo 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variables file 76 | .env 77 | .env.test 78 | 79 | # parcel-bundler cache (https://parceljs.org/) 80 | .cache 81 | 82 | # Next.js build output 83 | .next 84 | 85 | # Nuxt.js build / generate output 86 | .nuxt 87 | dist 88 | 89 | # Gatsby files 90 | .cache/ 91 | # Comment in the public line in if your project uses Gatsby and not Next.js 92 | # https://nextjs.org/blog/next-9-1#public-directory-support 93 | # public 94 | 95 | # vuepress build output 96 | .vuepress/dist 97 | 98 | # Serverless directories 99 | .serverless/ 100 | 101 | # FuseBox cache 102 | .fusebox/ 103 | 104 | # DynamoDB Local files 105 | .dynamodb/ 106 | 107 | # TernJS port file 108 | .tern-port 109 | 110 | # Stores VSCode versions used for testing VSCode extensions 111 | .vscode-test 112 | 113 | # End of https://www.toptal.com/developers/gitignore/api/node 114 | 115 | # Created by https://www.toptal.com/developers/gitignore/api/macos 116 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos 117 | 118 | ### macOS ### 119 | # General 120 | .DS_Store 121 | .AppleDouble 122 | .LSOverride 123 | 124 | # Icon must end with two 125 | Icon 126 | 127 | 128 | # Thumbnails 129 | ._* 130 | 131 | # Files that might appear in the root of a volume 132 | .DocumentRevisions-V100 133 | .fseventsd 134 | .Spotlight-V100 135 | .TemporaryItems 136 | .Trashes 137 | .VolumeIcon.icns 138 | .com.apple.timemachine.donotpresent 139 | 140 | # Directories potentially created on remote AFP share 141 | .AppleDB 142 | .AppleDesktop 143 | Network Trash Folder 144 | Temporary Items 145 | .apdisk 146 | 147 | ### macOS Patch ### 148 | # iCloud generated files 149 | *.icloud 150 | 151 | # End of https://www.toptal.com/developers/gitignore/api/macos 152 | 153 | # Created by https://www.toptal.com/developers/gitignore/api/windows 154 | # Edit at https://www.toptal.com/developers/gitignore?templates=windows 155 | 156 | ### Windows ### 157 | # Windows thumbnail cache files 158 | Thumbs.db 159 | Thumbs.db:encryptable 160 | ehthumbs.db 161 | ehthumbs_vista.db 162 | 163 | # Dump file 164 | *.stackdump 165 | 166 | # Folder config file 167 | [Dd]esktop.ini 168 | 169 | # Recycle Bin used on file shares 170 | $RECYCLE.BIN/ 171 | 172 | # Windows Installer files 173 | *.cab 174 | *.msi 175 | *.msix 176 | *.msm 177 | *.msp 178 | 179 | # Windows shortcuts 180 | *.lnk 181 | 182 | # End of https://www.toptal.com/developers/gitignore/api/windows 183 | 184 | #Added by cargo 185 | 186 | /target 187 | 188 | .pnp.* 189 | .yarn/* 190 | !.yarn/patches 191 | !.yarn/plugins 192 | !.yarn/releases 193 | !.yarn/sdks 194 | !.yarn/versions 195 | 196 | *.node 197 | *.exe 198 | 199 | @reflink 200 | 201 | sandbox 202 | __reflink-tests-* 203 | *.json.bak 204 | 205 | /.vscode/*.json 206 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | .cargo 4 | .github 5 | npm 6 | .eslintrc 7 | .prettierignore 8 | rustfmt.toml 9 | yarn.lock 10 | *.node 11 | .yarn 12 | __test__ 13 | renovate.json 14 | -------------------------------------------------------------------------------- /.vscode/extensions.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "arcanis.vscode-zipfs" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "search.exclude": { 3 | "**/.yarn": true, 4 | "**/.pnp.*": true 5 | }, 6 | "typescript.tsdk": ".yarn/sdks/typescript/lib", 7 | "typescript.enablePromptUseWorkspaceTsdk": true 8 | } 9 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "autocfg" 16 | version = "1.1.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 19 | 20 | [[package]] 21 | name = "bitflags" 22 | version = "2.4.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" 25 | 26 | [[package]] 27 | name = "cfg-if" 28 | version = "1.0.0" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 31 | 32 | [[package]] 33 | name = "convert_case" 34 | version = "0.6.0" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" 37 | dependencies = [ 38 | "unicode-segmentation", 39 | ] 40 | 41 | [[package]] 42 | name = "copy_on_write" 43 | version = "0.1.3" 44 | source = "registry+https://github.com/rust-lang/crates.io-index" 45 | checksum = "231be9abfd4fd31196f0e2ba13b314391969131530e14d84d2b9e954ff27d509" 46 | dependencies = [ 47 | "env_logger", 48 | "lazy_static", 49 | "log", 50 | "reflink-copy", 51 | "tempfile", 52 | "widestring", 53 | "windows", 54 | ] 55 | 56 | [[package]] 57 | name = "ctor" 58 | version = "0.2.5" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "37e366bff8cd32dd8754b0991fb66b279dc48f598c3a18914852a6673deef583" 61 | dependencies = [ 62 | "quote", 63 | "syn", 64 | ] 65 | 66 | [[package]] 67 | name = "env_logger" 68 | version = "0.10.2" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" 71 | dependencies = [ 72 | "humantime", 73 | "is-terminal", 74 | "log", 75 | "regex", 76 | "termcolor", 77 | ] 78 | 79 | [[package]] 80 | name = "errno" 81 | version = "0.3.9" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 84 | dependencies = [ 85 | "libc", 86 | "windows-sys 0.52.0", 87 | ] 88 | 89 | [[package]] 90 | name = "fastrand" 91 | version = "2.2.0" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" 94 | 95 | [[package]] 96 | name = "futures" 97 | version = "0.3.31" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" 100 | dependencies = [ 101 | "futures-channel", 102 | "futures-core", 103 | "futures-executor", 104 | "futures-io", 105 | "futures-sink", 106 | "futures-task", 107 | "futures-util", 108 | ] 109 | 110 | [[package]] 111 | name = "futures-channel" 112 | version = "0.3.31" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 115 | dependencies = [ 116 | "futures-core", 117 | "futures-sink", 118 | ] 119 | 120 | [[package]] 121 | name = "futures-core" 122 | version = "0.3.31" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 125 | 126 | [[package]] 127 | name = "futures-executor" 128 | version = "0.3.31" 129 | source = "registry+https://github.com/rust-lang/crates.io-index" 130 | checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" 131 | dependencies = [ 132 | "futures-core", 133 | "futures-task", 134 | "futures-util", 135 | ] 136 | 137 | [[package]] 138 | name = "futures-io" 139 | version = "0.3.31" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" 142 | 143 | [[package]] 144 | name = "futures-macro" 145 | version = "0.3.31" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" 148 | dependencies = [ 149 | "proc-macro2", 150 | "quote", 151 | "syn", 152 | ] 153 | 154 | [[package]] 155 | name = "futures-sink" 156 | version = "0.3.31" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 159 | 160 | [[package]] 161 | name = "futures-task" 162 | version = "0.3.31" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 165 | 166 | [[package]] 167 | name = "futures-util" 168 | version = "0.3.31" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 171 | dependencies = [ 172 | "futures-channel", 173 | "futures-core", 174 | "futures-io", 175 | "futures-macro", 176 | "futures-sink", 177 | "futures-task", 178 | "memchr", 179 | "pin-project-lite", 180 | "pin-utils", 181 | "slab", 182 | ] 183 | 184 | [[package]] 185 | name = "hermit-abi" 186 | version = "0.4.0" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" 189 | 190 | [[package]] 191 | name = "humantime" 192 | version = "2.1.0" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 195 | 196 | [[package]] 197 | name = "is-terminal" 198 | version = "0.4.13" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" 201 | dependencies = [ 202 | "hermit-abi", 203 | "libc", 204 | "windows-sys 0.52.0", 205 | ] 206 | 207 | [[package]] 208 | name = "lazy_static" 209 | version = "1.5.0" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 212 | 213 | [[package]] 214 | name = "libc" 215 | version = "0.2.164" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" 218 | 219 | [[package]] 220 | name = "libloading" 221 | version = "0.8.5" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" 224 | dependencies = [ 225 | "cfg-if", 226 | "windows-targets 0.52.6", 227 | ] 228 | 229 | [[package]] 230 | name = "linux-raw-sys" 231 | version = "0.4.14" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 234 | 235 | [[package]] 236 | name = "log" 237 | version = "0.4.22" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 240 | 241 | [[package]] 242 | name = "memchr" 243 | version = "2.6.4" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" 246 | 247 | [[package]] 248 | name = "napi" 249 | version = "2.16.13" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "214f07a80874bb96a8433b3cdfc84980d56c7b02e1a0d7ba4ba0db5cef785e2b" 252 | dependencies = [ 253 | "bitflags", 254 | "ctor", 255 | "napi-derive", 256 | "napi-sys", 257 | "once_cell", 258 | ] 259 | 260 | [[package]] 261 | name = "napi-build" 262 | version = "2.1.3" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "e1c0f5d67ee408a4685b61f5ab7e58605c8ae3f2b4189f0127d804ff13d5560a" 265 | 266 | [[package]] 267 | name = "napi-derive" 268 | version = "2.16.12" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "17435f7a00bfdab20b0c27d9c56f58f6499e418252253081bfff448099da31d1" 271 | dependencies = [ 272 | "cfg-if", 273 | "convert_case", 274 | "napi-derive-backend", 275 | "proc-macro2", 276 | "quote", 277 | "syn", 278 | ] 279 | 280 | [[package]] 281 | name = "napi-derive-backend" 282 | version = "1.0.74" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "967c485e00f0bf3b1bdbe510a38a4606919cf1d34d9a37ad41f25a81aa077abe" 285 | dependencies = [ 286 | "convert_case", 287 | "once_cell", 288 | "proc-macro2", 289 | "quote", 290 | "regex", 291 | "semver", 292 | "syn", 293 | ] 294 | 295 | [[package]] 296 | name = "napi-sys" 297 | version = "2.4.0" 298 | source = "registry+https://github.com/rust-lang/crates.io-index" 299 | checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3" 300 | dependencies = [ 301 | "libloading", 302 | ] 303 | 304 | [[package]] 305 | name = "once_cell" 306 | version = "1.20.2" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 309 | 310 | [[package]] 311 | name = "pin-project-lite" 312 | version = "0.2.13" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" 315 | 316 | [[package]] 317 | name = "pin-utils" 318 | version = "0.1.0" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 321 | 322 | [[package]] 323 | name = "pipe-trait" 324 | version = "0.4.0" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "c1be1ec9e59f0360aefe84efa6f699198b685ab0d5718081e9f72aa2344289e2" 327 | 328 | [[package]] 329 | name = "proc-macro2" 330 | version = "1.0.91" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "307e3004becf10f5a6e0d59d20f3cd28231b0e0827a96cd3e0ce6d14bc1e4bb3" 333 | dependencies = [ 334 | "unicode-ident", 335 | ] 336 | 337 | [[package]] 338 | name = "quote" 339 | version = "1.0.37" 340 | source = "registry+https://github.com/rust-lang/crates.io-index" 341 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 342 | dependencies = [ 343 | "proc-macro2", 344 | ] 345 | 346 | [[package]] 347 | name = "reflink" 348 | version = "0.0.0" 349 | dependencies = [ 350 | "copy_on_write", 351 | "futures", 352 | "napi", 353 | "napi-build", 354 | "napi-derive", 355 | "pipe-trait", 356 | ] 357 | 358 | [[package]] 359 | name = "reflink-copy" 360 | version = "0.1.11" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "f97f7665e51f23760e9e4949d454a4782c76ef954acaeec9d1b0f48a58e4529e" 363 | dependencies = [ 364 | "cfg-if", 365 | "rustix", 366 | "windows", 367 | ] 368 | 369 | [[package]] 370 | name = "regex" 371 | version = "1.10.2" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" 374 | dependencies = [ 375 | "aho-corasick", 376 | "memchr", 377 | "regex-automata", 378 | "regex-syntax", 379 | ] 380 | 381 | [[package]] 382 | name = "regex-automata" 383 | version = "0.4.3" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" 386 | dependencies = [ 387 | "aho-corasick", 388 | "memchr", 389 | "regex-syntax", 390 | ] 391 | 392 | [[package]] 393 | name = "regex-syntax" 394 | version = "0.8.2" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" 397 | 398 | [[package]] 399 | name = "rustix" 400 | version = "0.38.41" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6" 403 | dependencies = [ 404 | "bitflags", 405 | "errno", 406 | "libc", 407 | "linux-raw-sys", 408 | "windows-sys 0.52.0", 409 | ] 410 | 411 | [[package]] 412 | name = "semver" 413 | version = "1.0.20" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" 416 | 417 | [[package]] 418 | name = "slab" 419 | version = "0.4.9" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 422 | dependencies = [ 423 | "autocfg", 424 | ] 425 | 426 | [[package]] 427 | name = "syn" 428 | version = "2.0.89" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" 431 | dependencies = [ 432 | "proc-macro2", 433 | "quote", 434 | "unicode-ident", 435 | ] 436 | 437 | [[package]] 438 | name = "tempfile" 439 | version = "3.14.0" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" 442 | dependencies = [ 443 | "cfg-if", 444 | "fastrand", 445 | "once_cell", 446 | "rustix", 447 | "windows-sys 0.59.0", 448 | ] 449 | 450 | [[package]] 451 | name = "termcolor" 452 | version = "1.4.1" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 455 | dependencies = [ 456 | "winapi-util", 457 | ] 458 | 459 | [[package]] 460 | name = "unicode-ident" 461 | version = "1.0.12" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 464 | 465 | [[package]] 466 | name = "unicode-segmentation" 467 | version = "1.10.1" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" 470 | 471 | [[package]] 472 | name = "widestring" 473 | version = "1.1.0" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" 476 | 477 | [[package]] 478 | name = "winapi-util" 479 | version = "0.1.9" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 482 | dependencies = [ 483 | "windows-sys 0.59.0", 484 | ] 485 | 486 | [[package]] 487 | name = "windows" 488 | version = "0.51.1" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "ca229916c5ee38c2f2bc1e9d8f04df975b4bd93f9955dc69fabb5d91270045c9" 491 | dependencies = [ 492 | "windows-core", 493 | "windows-targets 0.48.5", 494 | ] 495 | 496 | [[package]] 497 | name = "windows-core" 498 | version = "0.51.1" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" 501 | dependencies = [ 502 | "windows-targets 0.48.5", 503 | ] 504 | 505 | [[package]] 506 | name = "windows-sys" 507 | version = "0.52.0" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 510 | dependencies = [ 511 | "windows-targets 0.52.6", 512 | ] 513 | 514 | [[package]] 515 | name = "windows-sys" 516 | version = "0.59.0" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 519 | dependencies = [ 520 | "windows-targets 0.52.6", 521 | ] 522 | 523 | [[package]] 524 | name = "windows-targets" 525 | version = "0.48.5" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 528 | dependencies = [ 529 | "windows_aarch64_gnullvm 0.48.5", 530 | "windows_aarch64_msvc 0.48.5", 531 | "windows_i686_gnu 0.48.5", 532 | "windows_i686_msvc 0.48.5", 533 | "windows_x86_64_gnu 0.48.5", 534 | "windows_x86_64_gnullvm 0.48.5", 535 | "windows_x86_64_msvc 0.48.5", 536 | ] 537 | 538 | [[package]] 539 | name = "windows-targets" 540 | version = "0.52.6" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 543 | dependencies = [ 544 | "windows_aarch64_gnullvm 0.52.6", 545 | "windows_aarch64_msvc 0.52.6", 546 | "windows_i686_gnu 0.52.6", 547 | "windows_i686_gnullvm", 548 | "windows_i686_msvc 0.52.6", 549 | "windows_x86_64_gnu 0.52.6", 550 | "windows_x86_64_gnullvm 0.52.6", 551 | "windows_x86_64_msvc 0.52.6", 552 | ] 553 | 554 | [[package]] 555 | name = "windows_aarch64_gnullvm" 556 | version = "0.48.5" 557 | source = "registry+https://github.com/rust-lang/crates.io-index" 558 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 559 | 560 | [[package]] 561 | name = "windows_aarch64_gnullvm" 562 | version = "0.52.6" 563 | source = "registry+https://github.com/rust-lang/crates.io-index" 564 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 565 | 566 | [[package]] 567 | name = "windows_aarch64_msvc" 568 | version = "0.48.5" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 571 | 572 | [[package]] 573 | name = "windows_aarch64_msvc" 574 | version = "0.52.6" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 577 | 578 | [[package]] 579 | name = "windows_i686_gnu" 580 | version = "0.48.5" 581 | source = "registry+https://github.com/rust-lang/crates.io-index" 582 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 583 | 584 | [[package]] 585 | name = "windows_i686_gnu" 586 | version = "0.52.6" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 589 | 590 | [[package]] 591 | name = "windows_i686_gnullvm" 592 | version = "0.52.6" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 595 | 596 | [[package]] 597 | name = "windows_i686_msvc" 598 | version = "0.48.5" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 601 | 602 | [[package]] 603 | name = "windows_i686_msvc" 604 | version = "0.52.6" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 607 | 608 | [[package]] 609 | name = "windows_x86_64_gnu" 610 | version = "0.48.5" 611 | source = "registry+https://github.com/rust-lang/crates.io-index" 612 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 613 | 614 | [[package]] 615 | name = "windows_x86_64_gnu" 616 | version = "0.52.6" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 619 | 620 | [[package]] 621 | name = "windows_x86_64_gnullvm" 622 | version = "0.48.5" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 625 | 626 | [[package]] 627 | name = "windows_x86_64_gnullvm" 628 | version = "0.52.6" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 631 | 632 | [[package]] 633 | name = "windows_x86_64_msvc" 634 | version = "0.48.5" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 637 | 638 | [[package]] 639 | name = "windows_x86_64_msvc" 640 | version = "0.52.6" 641 | source = "registry+https://github.com/rust-lang/crates.io-index" 642 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 643 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "reflink" 4 | version = "0.0.0" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | 9 | [dependencies] 10 | copy_on_write = "0.1.3" 11 | futures = "0.3.31" 12 | # Default enable napi4 feature, see https://nodejs.org/api/n-api.html#node-api-version-matrix 13 | napi = { version = "2.16.13", default-features = false, features = ["napi4"] } 14 | napi-derive = "2.16.12" 15 | pipe-trait = "0.4.0" 16 | 17 | [build-dependencies] 18 | napi-build = "2.1.3" 19 | 20 | [profile.release] 21 | lto = true 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @reflink/reflink 2 | 3 | [![npm version](https://badge.fury.io/js/%40reflink%2Freflink.svg)](https://www.npmjs.com/package/@reflink/reflink) 4 | [![Build Status](https://github.com/pnpm/reflink/workflows/CI/badge.svg)](https://github.com/pnpm/reflink/actions) 5 | 6 | Copy-on-write file cloning for Node.js, powered by NAPI-RS and built upon [reflink-copy](https://github.com/cargo-bins/reflink-copy). This package supports a variety of platforms, including ARM and x86 architectures. 7 | 8 | ### Supported Platforms 9 | - Linux 10 | - MacOS 11 | - Windows (Server 2012+ and Windows Dev Drives) 12 | 13 | ## Installation 14 | 15 | Just install `@reflink/reflink` using your favorite package manager: 16 | 17 | ```bash 18 | pnpm add @reflink/reflink 19 | ``` 20 | 21 | ## Usage 22 | 23 | The package provides both synchronous and asynchronous methods to clone files. 24 | 25 | ### TypeScript Usage 26 | 27 | First, import the package: 28 | 29 | ```typescript 30 | import { reflinkFileSync, reflinkFile } from '@reflink/reflink'; 31 | ``` 32 | 33 | #### Synchronous Method 34 | 35 | ```typescript 36 | reflinkFileSync('source.txt', 'destination.txt'); 37 | ``` 38 | 39 | #### Asynchronous Method 40 | 41 | ```typescript 42 | await reflinkFile('source.txt', 'destination.txt'); 43 | ``` 44 | 45 | ## Testing 46 | 47 | This package is tested using `vitest`. You can run the tests locally using: 48 | 49 | ```bash 50 | pnpm install 51 | pnpm build 52 | pnpm test 53 | ``` 54 | -------------------------------------------------------------------------------- /__test__/main.spec.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, describe, expect, it } from 'vitest'; 2 | import { constants } from 'os'; 3 | import { join, resolve } from 'path'; 4 | import { mkdir, rm, writeFile } from 'fs/promises'; 5 | import { readFileSync } from 'fs'; 6 | import { randomUUID, createHash } from 'crypto'; 7 | import { rimraf } from 'rimraf'; 8 | import { reflinkFileSync, reflinkFile } from '../index.js'; 9 | 10 | const sandboxDir = () => join(process.cwd(), `__reflink-tests-${randomUUID()}`); 11 | 12 | const sandboxFiles = [ 13 | { 14 | path: 'file1.txt', 15 | content: 'Hello World!', 16 | sha: createHash('sha256').update('Hello World!').digest('hex'), 17 | }, 18 | { 19 | path: 'file2.txt', 20 | content: 'Hello World!', 21 | sha: createHash('sha256').update('Hello World!').digest('hex'), 22 | }, 23 | { 24 | path: 'file3.txt', 25 | content: 'Hello World!', 26 | sha: createHash('sha256').update('Hello World!').digest('hex'), 27 | }, 28 | ]; 29 | 30 | const sandboxDirectories: string[] = []; 31 | 32 | async function prepare(dir: string) { 33 | await mkdir(dir, { recursive: true }); 34 | 35 | sandboxDirectories.push(dir); 36 | 37 | return Promise.all( 38 | sandboxFiles.map(async (file) => { 39 | await writeFile(join(dir, file.path), file.content); 40 | return { 41 | ...file, 42 | path: join(dir, file.path), 43 | }; 44 | }) 45 | ); 46 | } 47 | 48 | describe('reflink', () => { 49 | afterAll(async () => { 50 | await Promise.all( 51 | sandboxDirectories.map(async (dir) => { 52 | await rimraf(dir).catch(() => {}); 53 | }) 54 | ); 55 | }); 56 | 57 | it('should correctly clone a file (sync)', async () => { 58 | const dir = sandboxDir(); 59 | const files = await prepare(dir); 60 | const file = files[0]; 61 | 62 | reflinkFileSync(file.path, join(dir, 'file1-copy.txt')); 63 | 64 | const content = readFileSync(join(dir, 'file1-copy.txt'), 'utf-8'); 65 | 66 | expect(content).toBe(file.content); 67 | }); 68 | 69 | it('should correctly clone a file (async)', async () => { 70 | const dir = sandboxDir(); 71 | const files = await prepare(dir); 72 | const file = files[0]; 73 | 74 | await reflinkFile(file.path, join(dir, 'file1-copy.txt')); 75 | 76 | const content = readFileSync(join(dir, 'file1-copy.txt'), 'utf-8'); 77 | 78 | expect(content).toBe(file.content); 79 | }); 80 | 81 | it('should keep the same content in source file after editing the cloned file', async () => { 82 | const dir = sandboxDir(); 83 | const files = await prepare(dir); 84 | const file = files[0]; 85 | 86 | await reflinkFile(file.path, join(dir, 'file1-copy.txt')); 87 | 88 | await writeFile( 89 | join(dir, 'file1-copy.txt'), 90 | file.content + '\nAdded content!' 91 | ); 92 | 93 | const originalContent = readFileSync(file.path, 'utf-8'); 94 | 95 | expect(originalContent).toBe(file.content); 96 | }); 97 | 98 | it('should fail if the source file does not exist (sync)', async () => { 99 | const dir = sandboxDir(); 100 | await prepare(dir); 101 | 102 | try { 103 | reflinkFileSync( 104 | join(dir, 'file-does-not-exist.txt'), 105 | join(dir, 'file1-copy.txt') 106 | ); 107 | } catch (error) { 108 | expect(error).toMatchObject({ 109 | message: expect.any(String), 110 | code: 'ENOENT', 111 | errno: constants.errno.ENOENT, 112 | }); 113 | return; 114 | } 115 | throw new Error('Expecting an error, but none was thrown'); 116 | }); 117 | 118 | it('should fail if the source file does not exist (async)', async () => { 119 | const dir = sandboxDir(); 120 | await prepare(dir); 121 | 122 | await expect( 123 | reflinkFile( 124 | join(dir, 'file-does-not-exist.txt'), 125 | join(dir, 'file1-copy.txt') 126 | ) 127 | ).rejects.toMatchObject({ 128 | message: expect.any(String), 129 | code: 'ENOENT', 130 | errno: constants.errno.ENOENT, 131 | }); 132 | }); 133 | 134 | it('should fail if the destination file already exists (sync)', async () => { 135 | const dir = sandboxDir(); 136 | const sandboxFiles = await prepare(dir); 137 | 138 | try { 139 | reflinkFileSync(sandboxFiles[0].path, sandboxFiles[1].path); 140 | } catch (error) { 141 | expect(error).toMatchObject({ 142 | message: expect.any(String), 143 | code: 'EEXIST', 144 | errno: constants.errno.EEXIST, 145 | }); 146 | return; 147 | } 148 | throw new Error('Expecting an error, but none was thrown'); 149 | }); 150 | 151 | it('should fail if the destination file already exists (async)', async () => { 152 | const dir = sandboxDir(); 153 | const sandboxFiles = await prepare(dir); 154 | await expect( 155 | reflinkFile(sandboxFiles[0].path, sandboxFiles[1].path) 156 | ).rejects.toMatchObject({ 157 | message: expect.any(String), 158 | code: 'EEXIST', 159 | errno: constants.errno.EEXIST, 160 | }); 161 | }); 162 | 163 | it('should fail if the source file is a directory (sync)', async () => { 164 | const dir = sandboxDir(); 165 | const sandboxFiles = await prepare(dir); 166 | expect(() => { 167 | reflinkFileSync(dir, sandboxFiles[1].path); 168 | }).toThrow(); 169 | }); 170 | 171 | it('should fail if the source file is a directory (async)', async () => { 172 | const dir = sandboxDir(); 173 | const sandboxFiles = await prepare(dir); 174 | await expect(reflinkFile(dir, sandboxFiles[1].path)).rejects.toThrow(); 175 | }); 176 | 177 | it('should fail if the source and destination files are the same (sync)', async () => { 178 | const dir = sandboxDir(); 179 | const sandboxFiles = await prepare(dir); 180 | expect(() => { 181 | reflinkFileSync(sandboxFiles[0].path, sandboxFiles[0].path); 182 | }).toThrow(); 183 | }); 184 | 185 | it('should fail if the source and destination files are the same (async)', async () => { 186 | const dir = sandboxDir(); 187 | const sandboxFiles = await prepare(dir); 188 | await expect( 189 | reflinkFile(sandboxFiles[0].path, sandboxFiles[0].path) 190 | ).rejects.toThrow(); 191 | }); 192 | 193 | it('should fail if the destination parent directory does not exist (sync)', async () => { 194 | const dir = sandboxDir(); 195 | const sandboxFiles = await prepare(dir); 196 | expect(() => { 197 | reflinkFileSync( 198 | sandboxFiles[0].path, 199 | join(dir, 'does-not-exist', 'file1-copy.txt') 200 | ); 201 | }).toThrow(); 202 | }); 203 | 204 | it('should not fail with relative paths', async () => { 205 | const file = { 206 | path: 'file.txt', 207 | content: 'Hello World!', 208 | }; 209 | 210 | const dest = 'file-copy.txt'; 211 | 212 | await rm(dest, { force: true }); 213 | await writeFile(file.path, file.content); 214 | 215 | await reflinkFile(file.path, dest); 216 | 217 | const content = readFileSync(dest, 'utf-8'); 218 | 219 | expect(content).toBe(file.content); 220 | 221 | // clean both files 222 | await rm('file.txt'); 223 | await rm('file-copy.txt'); 224 | }); 225 | 226 | it('should not fail with nested relative paths', async () => { 227 | const file = { 228 | path: 'nested/file.txt', 229 | content: 'Hello World!', 230 | }; 231 | 232 | const dest = 'nested/file-copy.txt'; 233 | 234 | await rm(dest, { force: true }); 235 | await mkdir('nested', { recursive: true }); 236 | await writeFile(file.path, file.content); 237 | 238 | await reflinkFile(file.path, dest); 239 | 240 | const content = readFileSync(dest, 'utf-8'); 241 | 242 | expect(content).toBe(file.content); 243 | 244 | // clean both files 245 | await rm('nested', { recursive: true }); 246 | }); 247 | 248 | it('should correctly clone 1000 files (sync)', async () => { 249 | const dir = sandboxDir(); 250 | const sandboxFiles = await prepare(dir); 251 | 252 | const files = Array.from({ length: 1000 }, (_, i) => ({ 253 | path: join(dir, `file${i}.txt`), 254 | content: 'Hello World!', 255 | })); 256 | 257 | await Promise.all( 258 | files.map(async (file) => writeFile(file.path, file.content)) 259 | ); 260 | 261 | await Promise.all( 262 | files.map(async (file, i) => 263 | reflinkFileSync(file.path, join(dir, `file${i}-copy.txt`)) 264 | ) 265 | ); 266 | 267 | files.forEach((file, i) => { 268 | const content = readFileSync(join(dir, `file${i}-copy.txt`), 'utf-8'); 269 | expect(content).toBe(file.content); 270 | }); 271 | }); 272 | 273 | it('should correctly clone 1000 files (async)', async () => { 274 | const dir = sandboxDir(); 275 | const sandboxFiles = await prepare(dir); 276 | const files = Array.from({ length: 1000 }, (_, i) => ({ 277 | path: join(dir, `file${i}.txt`), 278 | content: 'Hello World!', 279 | hash: createHash('sha256').update('Hello World!').digest('hex'), 280 | })); 281 | 282 | await Promise.all( 283 | files.map(async (file) => writeFile(file.path, file.content)) 284 | ); 285 | 286 | await Promise.all( 287 | files.map(async (file, i) => 288 | reflinkFile(file.path, join(dir, `file${i}-copy.txt`)) 289 | ) 290 | ); 291 | 292 | files.forEach((file, i) => { 293 | const content = readFileSync(join(dir, `file${i}-copy.txt`), 'utf-8'); 294 | const hash = createHash('sha256').update(content).digest('hex'); 295 | expect(content).toBe(file.content); 296 | expect(hash).toBe(file.hash); 297 | }); 298 | }); 299 | 300 | it('should keep the same hash when cloning a file more than 3,000 times', async () => { 301 | const dir = sandboxDir(); 302 | const sandboxFiles = await prepare(dir); 303 | const srcFile = { 304 | path: resolve('./package.json'), 305 | content: readFileSync(join('./package.json'), 'utf-8'), 306 | }; 307 | 308 | const destFiles = Array.from({ length: 3_000 }, (_, i) => ({ 309 | path: join(dir, `file1-copy-${i}.txt`), 310 | hash: createHash('sha256').update(srcFile.content).digest('hex'), 311 | })); 312 | 313 | const clonedFiles = await Promise.all( 314 | destFiles.map(async (file) => { 315 | reflinkFileSync(srcFile.path, file.path); 316 | return file; 317 | }) 318 | ); 319 | 320 | clonedFiles.forEach((file) => { 321 | const sourceContent = readFileSync(srcFile.path, 'utf-8'); 322 | const sourceHash = createHash('sha256') 323 | .update(sourceContent) 324 | .digest('hex'); 325 | 326 | expect(file.hash).toBe(sourceHash); 327 | 328 | const destContent = readFileSync(file.path, 'utf-8'); 329 | const destHash = createHash('sha256').update(destContent).digest('hex'); 330 | expect(destContent).toBe(sourceContent); 331 | expect(destHash).toBe(sourceHash); 332 | }); 333 | }); 334 | 335 | it('should clone "sample.pyc" file correctly (sync)', async () => { 336 | const dir = sandboxDir(); 337 | const sandboxFiles = await prepare(dir); 338 | const srcFile = { 339 | path: resolve(join('fixtures', 'sample.pyc')), 340 | content: readFileSync(join('fixtures', 'sample.pyc')), 341 | }; 342 | 343 | const destFile = { 344 | path: join(dir, 'sample.pyc'), 345 | hash: createHash('sha256').update(srcFile.content).digest('hex'), 346 | }; 347 | 348 | reflinkFileSync(srcFile.path, destFile.path); 349 | 350 | const destContent = readFileSync(destFile.path); 351 | const destHash = createHash('sha256').update(destContent).digest('hex'); 352 | 353 | expect(destContent).toStrictEqual(srcFile.content); 354 | expect(destHash).toStrictEqual(destFile.hash); 355 | }); 356 | 357 | /** 358 | * The issue with empty cloned files doesn't seem related to ASCII characters 359 | */ 360 | it.skip('should clone "ascii-file.js" file correctly (sync)', async () => { 361 | const dir = sandboxDir(); 362 | const sandboxFiles = await prepare(dir); 363 | const srcFile = { 364 | path: resolve(join('fixtures', 'ascii-file.js')), 365 | content: readFileSync(join('fixtures', 'ascii-file.js')), 366 | }; 367 | 368 | const destFile = { 369 | path: join(dir, 'ascii-file.js'), 370 | hash: createHash('sha256').update(srcFile.content).digest('hex'), 371 | }; 372 | 373 | reflinkFileSync(srcFile.path, destFile.path); 374 | 375 | const destContent = readFileSync(destFile.path); 376 | const destHash = createHash('sha256').update(destContent).digest('hex'); 377 | 378 | const sourceContent = readFileSync(srcFile.path); 379 | const sourceHash = createHash('sha256').update(sourceContent).digest('hex'); 380 | 381 | expect(sourceContent).toStrictEqual(srcFile.content); 382 | expect(sourceHash).toStrictEqual(destFile.hash); 383 | 384 | expect(destContent).toStrictEqual(srcFile.content); 385 | expect(destHash).toStrictEqual(destFile.hash); 386 | }); 387 | 388 | it('should clone "sample.pyc" file correctly (async)', async () => { 389 | const dir = sandboxDir(); 390 | const sandboxFiles = await prepare(dir); 391 | const srcFile = { 392 | path: resolve(join('fixtures', 'sample.pyc')), 393 | content: readFileSync(join('fixtures', 'sample.pyc')), 394 | }; 395 | 396 | const destFile = { 397 | path: join(dir, 'sample.pyc'), 398 | hash: createHash('sha256').update(srcFile.content).digest('hex'), 399 | }; 400 | 401 | await reflinkFile(srcFile.path, destFile.path); 402 | 403 | const destContent = readFileSync(destFile.path); 404 | const destHash = createHash('sha256').update(destContent).digest('hex'); 405 | 406 | expect(destContent).toStrictEqual(srcFile.content); 407 | expect(destHash).toStrictEqual(destFile.hash); 408 | }); 409 | }); 410 | -------------------------------------------------------------------------------- /__test__/threads.spec.ts: -------------------------------------------------------------------------------- 1 | import { afterAll, beforeEach, describe, expect, it } from 'vitest'; 2 | import { Worker } from 'worker_threads'; 3 | import { join, relative, resolve, sep } from 'path'; 4 | import { mkdir, rm, writeFile } from 'fs/promises'; 5 | import { readFileSync } from 'fs'; 6 | import { randomUUID, createHash } from 'crypto'; 7 | 8 | const TEST_DIR = resolve(__dirname, '__reflink-tests-' + randomUUID()); 9 | const workerFile = 10 | `.${sep}` + relative(process.cwd(), join(__dirname, 'worker.js')); 11 | 12 | describe('reflink worker', () => { 13 | beforeEach(async () => { 14 | await rm(TEST_DIR, { recursive: true, force: true }); 15 | await mkdir(TEST_DIR, { recursive: true }); 16 | }); 17 | 18 | afterAll(async () => { 19 | await rm(TEST_DIR, { recursive: true, force: true }); 20 | }); 21 | 22 | it('clone the same file to different location simultaneously (sync)', async () => { 23 | const src = { 24 | path: join(process.cwd(), 'fixtures', 'ascii-file.js'), 25 | content: readFileSync(join(process.cwd(), 'fixtures', 'ascii-file.js')), 26 | }; 27 | 28 | const destFiles = Array.from({ length: 100 }, () => ({ 29 | path: join(TEST_DIR, `dest-${randomUUID()}.js`), 30 | })); 31 | 32 | await writeFile(src.path, src.content); 33 | 34 | const workers = destFiles.map((dest) => { 35 | const worker = new Worker(workerFile); 36 | 37 | worker.on('error', (err) => { 38 | throw err; 39 | }); 40 | 41 | return worker; 42 | }); 43 | 44 | workers.forEach((worker, i) => { 45 | worker.postMessage({ 46 | type: 'sync', 47 | src: src.path, 48 | dest: destFiles[i].path, 49 | }); 50 | }); 51 | 52 | await Promise.all( 53 | workers.map( 54 | (worker) => new Promise((resolve) => worker.on('message', resolve)) 55 | ) 56 | ); 57 | 58 | const srcHash = createHash('sha256').update(src.content).digest('hex'); 59 | 60 | for (const dest of destFiles) { 61 | const destContent = readFileSync(dest.path, 'utf8'); 62 | const destHash = createHash('sha256').update(destContent).digest('hex'); 63 | 64 | expect(destHash).toBe(srcHash); 65 | } 66 | }); 67 | 68 | it('clone the same file to different location simultaneously (async)', async () => { 69 | const src = { 70 | path: join(process.cwd(), 'fixtures', 'ascii-file.js'), 71 | content: readFileSync(join(process.cwd(), 'fixtures', 'ascii-file.js')), 72 | }; 73 | 74 | const destFiles = Array.from({ length: 100 }, () => ({ 75 | path: join(TEST_DIR, `dest-${randomUUID()}.js`), 76 | })); 77 | 78 | await writeFile(src.path, src.content); 79 | 80 | const workers = destFiles.map((dest) => { 81 | const worker = new Worker(workerFile); 82 | 83 | worker.on('error', (err) => { 84 | throw err; 85 | }); 86 | 87 | return worker; 88 | }); 89 | 90 | workers.forEach((worker, i) => { 91 | worker.postMessage({ 92 | type: 'async', 93 | src: src.path, 94 | dest: destFiles[i].path, 95 | }); 96 | }); 97 | 98 | await Promise.all( 99 | workers.map( 100 | (worker) => new Promise((resolve) => worker.on('message', resolve)) 101 | ) 102 | ); 103 | 104 | const srcHash = createHash('sha256').update(src.content).digest('hex'); 105 | 106 | for (const dest of destFiles) { 107 | const destContent = readFileSync(dest.path, 'utf8'); 108 | const destHash = createHash('sha256').update(destContent).digest('hex'); 109 | 110 | expect(destHash).toBe(srcHash); 111 | } 112 | }); 113 | }); 114 | -------------------------------------------------------------------------------- /__test__/worker.js: -------------------------------------------------------------------------------- 1 | const { parentPort } = require('worker_threads'); 2 | const { reflinkFileSync, reflinkFile } = require('../index.js'); 3 | 4 | parentPort?.on('message', async (data) => { 5 | if (data.type === 'sync') { 6 | reflinkFileSync(data.src, data.dest); 7 | } else { 8 | await reflinkFile(data.src, data.dest); 9 | } 10 | 11 | // console.log(`Cloned to ${data.dest} at ${new Date().toISOString()}`); 12 | 13 | parentPort?.postMessage({ type: 'done' }); 14 | // Kill the worker 15 | parentPort?.close(); 16 | }); 17 | -------------------------------------------------------------------------------- /benchmark.mjs: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { mkdir, rm, writeFile, link } from 'fs/promises'; 3 | import { linkSync } from 'fs'; 4 | import { randomUUID } from 'crypto'; 5 | import { performance } from 'perf_hooks'; 6 | import chalk from 'chalk'; 7 | import { reflinkFileSync, reflinkFile } from './index.js'; 8 | 9 | const sandboxDir = join(process.cwd(), `__link-tests-${randomUUID()}`); 10 | const testFilePath = join(sandboxDir, 'testFile.txt'); 11 | 12 | const results = {}; 13 | 14 | async function setup() { 15 | await rm(sandboxDir, { recursive: true, force: true }); 16 | await mkdir(sandboxDir, { recursive: true }); 17 | await writeFile(testFilePath, 'Hello, world!'); 18 | } 19 | 20 | async function teardown() { 21 | await rm(sandboxDir, { recursive: true, force: true }); 22 | } 23 | 24 | async function runBenchmark(name, fn) { 25 | await setup(); 26 | const start = performance.now(); 27 | for (let i = 0; i < 1000; i++) { 28 | const destPath = join(sandboxDir, `clone-${i}.txt`); 29 | await fn(destPath); 30 | } 31 | const end = performance.now(); 32 | const time = end - start; 33 | results[name] = time.toFixed(2); 34 | console.log(chalk.green(`${name}: ${chalk.blue(time)} ms`)); 35 | await teardown(); 36 | } 37 | 38 | function delay(ms) { 39 | return new Promise((resolve) => setTimeout(resolve, ms)); 40 | } 41 | 42 | async function main() { 43 | console.log(chalk.bold('Running Benchmarks...')); 44 | 45 | await runBenchmark('Node fs.linkSync', (destPath) => { 46 | linkSync(testFilePath, destPath); 47 | }); 48 | await delay(2000); 49 | 50 | await runBenchmark('Node fs.promises.link', async (destPath) => { 51 | await link(testFilePath, destPath); 52 | }); 53 | await delay(2000); 54 | 55 | await runBenchmark('reflinkFileSync', (destPath) => { 56 | reflinkFileSync(testFilePath, destPath); 57 | }); 58 | await delay(2000); 59 | 60 | await runBenchmark('reflinkFile', async (destPath) => { 61 | await reflinkFile(testFilePath, destPath); 62 | }); 63 | 64 | console.log(chalk.bold('\nBenchmark Summary:')); 65 | for (const [name, time] of Object.entries(results)) { 66 | console.log(`${name}: ${time} ms`); 67 | } 68 | 69 | const fastest = Object.entries(results).sort((a, b) => a[1] - b[1])[0]; 70 | console.log( 71 | chalk.green.bold(`\nFastest is ${fastest[0]} with ${fastest[1]} ms`) 72 | ); 73 | } 74 | 75 | main().catch((err) => { 76 | console.error(chalk.red('An error occurred:', err)); 77 | process.exit(1); 78 | }); 79 | -------------------------------------------------------------------------------- /binding.d.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | 4 | /* auto-generated by NAPI-RS */ 5 | 6 | export declare function reflinkFile(src: string, dst: string): Promise 7 | export declare function reflinkFileSync(src: string, dst: string): number | ReflinkError 8 | /** Contains all properties to construct an actual error. */ 9 | export class ReflinkError { 10 | message: string 11 | path: string 12 | dest: string 13 | code?: string 14 | errno?: number 15 | constructor(message: string, path: string, dest: string, code?: string, errno?: number) 16 | } 17 | -------------------------------------------------------------------------------- /binding.js: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | /* prettier-ignore */ 4 | 5 | /* auto-generated by NAPI-RS */ 6 | 7 | const { existsSync, readFileSync } = require('fs') 8 | const { join } = require('path') 9 | 10 | const { platform, arch } = process 11 | 12 | let nativeBinding = null 13 | let localFileExisted = false 14 | let loadError = null 15 | 16 | function isMusl() { 17 | // For Node 10 18 | if (!process.report || typeof process.report.getReport !== 'function') { 19 | try { 20 | const lddPath = require('child_process').execSync('which ldd').toString().trim() 21 | return readFileSync(lddPath, 'utf8').includes('musl') 22 | } catch (e) { 23 | return true 24 | } 25 | } else { 26 | const { glibcVersionRuntime } = process.report.getReport().header 27 | return !glibcVersionRuntime 28 | } 29 | } 30 | 31 | switch (platform) { 32 | case 'android': 33 | switch (arch) { 34 | case 'arm64': 35 | localFileExisted = existsSync(join(__dirname, 'reflink.android-arm64.node')) 36 | try { 37 | if (localFileExisted) { 38 | nativeBinding = require('./reflink.android-arm64.node') 39 | } else { 40 | nativeBinding = require('@reflink/reflink-android-arm64') 41 | } 42 | } catch (e) { 43 | loadError = e 44 | } 45 | break 46 | case 'arm': 47 | localFileExisted = existsSync(join(__dirname, 'reflink.android-arm-eabi.node')) 48 | try { 49 | if (localFileExisted) { 50 | nativeBinding = require('./reflink.android-arm-eabi.node') 51 | } else { 52 | nativeBinding = require('@reflink/reflink-android-arm-eabi') 53 | } 54 | } catch (e) { 55 | loadError = e 56 | } 57 | break 58 | default: 59 | throw new Error(`Unsupported architecture on Android ${arch}`) 60 | } 61 | break 62 | case 'win32': 63 | switch (arch) { 64 | case 'x64': 65 | localFileExisted = existsSync( 66 | join(__dirname, 'reflink.win32-x64-msvc.node') 67 | ) 68 | try { 69 | if (localFileExisted) { 70 | nativeBinding = require('./reflink.win32-x64-msvc.node') 71 | } else { 72 | nativeBinding = require('@reflink/reflink-win32-x64-msvc') 73 | } 74 | } catch (e) { 75 | loadError = e 76 | } 77 | break 78 | case 'ia32': 79 | localFileExisted = existsSync( 80 | join(__dirname, 'reflink.win32-ia32-msvc.node') 81 | ) 82 | try { 83 | if (localFileExisted) { 84 | nativeBinding = require('./reflink.win32-ia32-msvc.node') 85 | } else { 86 | nativeBinding = require('@reflink/reflink-win32-ia32-msvc') 87 | } 88 | } catch (e) { 89 | loadError = e 90 | } 91 | break 92 | case 'arm64': 93 | localFileExisted = existsSync( 94 | join(__dirname, 'reflink.win32-arm64-msvc.node') 95 | ) 96 | try { 97 | if (localFileExisted) { 98 | nativeBinding = require('./reflink.win32-arm64-msvc.node') 99 | } else { 100 | nativeBinding = require('@reflink/reflink-win32-arm64-msvc') 101 | } 102 | } catch (e) { 103 | loadError = e 104 | } 105 | break 106 | default: 107 | throw new Error(`Unsupported architecture on Windows: ${arch}`) 108 | } 109 | break 110 | case 'darwin': 111 | localFileExisted = existsSync(join(__dirname, 'reflink.darwin-universal.node')) 112 | try { 113 | if (localFileExisted) { 114 | nativeBinding = require('./reflink.darwin-universal.node') 115 | } else { 116 | nativeBinding = require('@reflink/reflink-darwin-universal') 117 | } 118 | break 119 | } catch {} 120 | switch (arch) { 121 | case 'x64': 122 | localFileExisted = existsSync(join(__dirname, 'reflink.darwin-x64.node')) 123 | try { 124 | if (localFileExisted) { 125 | nativeBinding = require('./reflink.darwin-x64.node') 126 | } else { 127 | nativeBinding = require('@reflink/reflink-darwin-x64') 128 | } 129 | } catch (e) { 130 | loadError = e 131 | } 132 | break 133 | case 'arm64': 134 | localFileExisted = existsSync( 135 | join(__dirname, 'reflink.darwin-arm64.node') 136 | ) 137 | try { 138 | if (localFileExisted) { 139 | nativeBinding = require('./reflink.darwin-arm64.node') 140 | } else { 141 | nativeBinding = require('@reflink/reflink-darwin-arm64') 142 | } 143 | } catch (e) { 144 | loadError = e 145 | } 146 | break 147 | default: 148 | throw new Error(`Unsupported architecture on macOS: ${arch}`) 149 | } 150 | break 151 | case 'freebsd': 152 | if (arch !== 'x64') { 153 | throw new Error(`Unsupported architecture on FreeBSD: ${arch}`) 154 | } 155 | localFileExisted = existsSync(join(__dirname, 'reflink.freebsd-x64.node')) 156 | try { 157 | if (localFileExisted) { 158 | nativeBinding = require('./reflink.freebsd-x64.node') 159 | } else { 160 | nativeBinding = require('@reflink/reflink-freebsd-x64') 161 | } 162 | } catch (e) { 163 | loadError = e 164 | } 165 | break 166 | case 'linux': 167 | switch (arch) { 168 | case 'x64': 169 | if (isMusl()) { 170 | localFileExisted = existsSync( 171 | join(__dirname, 'reflink.linux-x64-musl.node') 172 | ) 173 | try { 174 | if (localFileExisted) { 175 | nativeBinding = require('./reflink.linux-x64-musl.node') 176 | } else { 177 | nativeBinding = require('@reflink/reflink-linux-x64-musl') 178 | } 179 | } catch (e) { 180 | loadError = e 181 | } 182 | } else { 183 | localFileExisted = existsSync( 184 | join(__dirname, 'reflink.linux-x64-gnu.node') 185 | ) 186 | try { 187 | if (localFileExisted) { 188 | nativeBinding = require('./reflink.linux-x64-gnu.node') 189 | } else { 190 | nativeBinding = require('@reflink/reflink-linux-x64-gnu') 191 | } 192 | } catch (e) { 193 | loadError = e 194 | } 195 | } 196 | break 197 | case 'arm64': 198 | if (isMusl()) { 199 | localFileExisted = existsSync( 200 | join(__dirname, 'reflink.linux-arm64-musl.node') 201 | ) 202 | try { 203 | if (localFileExisted) { 204 | nativeBinding = require('./reflink.linux-arm64-musl.node') 205 | } else { 206 | nativeBinding = require('@reflink/reflink-linux-arm64-musl') 207 | } 208 | } catch (e) { 209 | loadError = e 210 | } 211 | } else { 212 | localFileExisted = existsSync( 213 | join(__dirname, 'reflink.linux-arm64-gnu.node') 214 | ) 215 | try { 216 | if (localFileExisted) { 217 | nativeBinding = require('./reflink.linux-arm64-gnu.node') 218 | } else { 219 | nativeBinding = require('@reflink/reflink-linux-arm64-gnu') 220 | } 221 | } catch (e) { 222 | loadError = e 223 | } 224 | } 225 | break 226 | case 'arm': 227 | localFileExisted = existsSync( 228 | join(__dirname, 'reflink.linux-arm-gnueabihf.node') 229 | ) 230 | try { 231 | if (localFileExisted) { 232 | nativeBinding = require('./reflink.linux-arm-gnueabihf.node') 233 | } else { 234 | nativeBinding = require('@reflink/reflink-linux-arm-gnueabihf') 235 | } 236 | } catch (e) { 237 | loadError = e 238 | } 239 | break 240 | default: 241 | throw new Error(`Unsupported architecture on Linux: ${arch}`) 242 | } 243 | break 244 | default: 245 | throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`) 246 | } 247 | 248 | if (!nativeBinding) { 249 | if (loadError) { 250 | throw loadError 251 | } 252 | throw new Error(`Failed to load native binding`) 253 | } 254 | 255 | const { ReflinkError, reflinkFile, reflinkFileSync } = nativeBinding 256 | 257 | module.exports.ReflinkError = ReflinkError 258 | module.exports.reflinkFile = reflinkFile 259 | module.exports.reflinkFileSync = reflinkFileSync 260 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | extern crate napi_build; 2 | 3 | fn main() { 4 | napi_build::setup(); 5 | } 6 | -------------------------------------------------------------------------------- /fixtures/ascii-file.js: -------------------------------------------------------------------------------- 1 | // 2 | // list 3 | // ┌──────┐ 4 | // ┌──────────────┼─head │ 5 | // │ │ tail─┼──────────────┐ 6 | // │ └──────┘ │ 7 | // ▼ ▼ 8 | // item item item item 9 | // ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐ 10 | // null ◀──┼─prev │◀───┼─prev │◀───┼─prev │◀───┼─prev │ 11 | // │ next─┼───▶│ next─┼───▶│ next─┼───▶│ next─┼──▶ null 12 | // ├──────┤ ├──────┤ ├──────┤ ├──────┤ 13 | // │ data │ │ data │ │ data │ │ data │ 14 | // └──────┘ └──────┘ └──────┘ └──────┘ 15 | // 16 | 17 | let releasedCursors = null; 18 | 19 | export class List { 20 | static createItem(data) { 21 | return { 22 | prev: null, 23 | next: null, 24 | data, 25 | }; 26 | } 27 | 28 | constructor() { 29 | this.head = null; 30 | this.tail = null; 31 | this.cursor = null; 32 | } 33 | createItem(data) { 34 | return List.createItem(data); 35 | } 36 | 37 | // cursor helpers 38 | allocateCursor(prev, next) { 39 | let cursor; 40 | 41 | if (releasedCursors !== null) { 42 | cursor = releasedCursors; 43 | releasedCursors = releasedCursors.cursor; 44 | cursor.prev = prev; 45 | cursor.next = next; 46 | cursor.cursor = this.cursor; 47 | } else { 48 | cursor = { 49 | prev, 50 | next, 51 | cursor: this.cursor, 52 | }; 53 | } 54 | 55 | this.cursor = cursor; 56 | 57 | return cursor; 58 | } 59 | releaseCursor() { 60 | const { cursor } = this; 61 | 62 | this.cursor = cursor.cursor; 63 | cursor.prev = null; 64 | cursor.next = null; 65 | cursor.cursor = releasedCursors; 66 | releasedCursors = cursor; 67 | } 68 | updateCursors(prevOld, prevNew, nextOld, nextNew) { 69 | let { cursor } = this; 70 | 71 | while (cursor !== null) { 72 | if (cursor.prev === prevOld) { 73 | cursor.prev = prevNew; 74 | } 75 | 76 | if (cursor.next === nextOld) { 77 | cursor.next = nextNew; 78 | } 79 | 80 | cursor = cursor.cursor; 81 | } 82 | } 83 | *[Symbol.iterator]() { 84 | for (let cursor = this.head; cursor !== null; cursor = cursor.next) { 85 | yield cursor.data; 86 | } 87 | } 88 | 89 | // getters 90 | get size() { 91 | let size = 0; 92 | 93 | for (let cursor = this.head; cursor !== null; cursor = cursor.next) { 94 | size++; 95 | } 96 | 97 | return size; 98 | } 99 | get isEmpty() { 100 | return this.head === null; 101 | } 102 | get first() { 103 | return this.head && this.head.data; 104 | } 105 | get last() { 106 | return this.tail && this.tail.data; 107 | } 108 | 109 | // convertors 110 | fromArray(array) { 111 | let cursor = null; 112 | this.head = null; 113 | 114 | for (let data of array) { 115 | const item = List.createItem(data); 116 | 117 | if (cursor !== null) { 118 | cursor.next = item; 119 | } else { 120 | this.head = item; 121 | } 122 | 123 | item.prev = cursor; 124 | cursor = item; 125 | } 126 | 127 | this.tail = cursor; 128 | return this; 129 | } 130 | toArray() { 131 | return [...this]; 132 | } 133 | toJSON() { 134 | return [...this]; 135 | } 136 | 137 | // array-like methods 138 | forEach(fn, thisArg = this) { 139 | // push cursor 140 | const cursor = this.allocateCursor(null, this.head); 141 | 142 | while (cursor.next !== null) { 143 | const item = cursor.next; 144 | cursor.next = item.next; 145 | fn.call(thisArg, item.data, item, this); 146 | } 147 | 148 | // pop cursor 149 | this.releaseCursor(); 150 | } 151 | forEachRight(fn, thisArg = this) { 152 | // push cursor 153 | const cursor = this.allocateCursor(this.tail, null); 154 | 155 | while (cursor.prev !== null) { 156 | const item = cursor.prev; 157 | cursor.prev = item.prev; 158 | fn.call(thisArg, item.data, item, this); 159 | } 160 | 161 | // pop cursor 162 | this.releaseCursor(); 163 | } 164 | reduce(fn, initialValue, thisArg = this) { 165 | // push cursor 166 | let cursor = this.allocateCursor(null, this.head); 167 | let acc = initialValue; 168 | let item; 169 | 170 | while (cursor.next !== null) { 171 | item = cursor.next; 172 | cursor.next = item.next; 173 | 174 | acc = fn.call(thisArg, acc, item.data, item, this); 175 | } 176 | 177 | // pop cursor 178 | this.releaseCursor(); 179 | 180 | return acc; 181 | } 182 | reduceRight(fn, initialValue, thisArg = this) { 183 | // push cursor 184 | let cursor = this.allocateCursor(this.tail, null); 185 | let acc = initialValue; 186 | let item; 187 | 188 | while (cursor.prev !== null) { 189 | item = cursor.prev; 190 | cursor.prev = item.prev; 191 | 192 | acc = fn.call(thisArg, acc, item.data, item, this); 193 | } 194 | 195 | // pop cursor 196 | this.releaseCursor(); 197 | 198 | return acc; 199 | } 200 | some(fn, thisArg = this) { 201 | for (let cursor = this.head; cursor !== null; cursor = cursor.next) { 202 | if (fn.call(thisArg, cursor.data, cursor, this)) { 203 | return true; 204 | } 205 | } 206 | 207 | return false; 208 | } 209 | map(fn, thisArg = this) { 210 | const result = new List(); 211 | 212 | for (let cursor = this.head; cursor !== null; cursor = cursor.next) { 213 | result.appendData(fn.call(thisArg, cursor.data, cursor, this)); 214 | } 215 | 216 | return result; 217 | } 218 | filter(fn, thisArg = this) { 219 | const result = new List(); 220 | 221 | for (let cursor = this.head; cursor !== null; cursor = cursor.next) { 222 | if (fn.call(thisArg, cursor.data, cursor, this)) { 223 | result.appendData(cursor.data); 224 | } 225 | } 226 | 227 | return result; 228 | } 229 | 230 | nextUntil(start, fn, thisArg = this) { 231 | if (start === null) { 232 | return; 233 | } 234 | 235 | // push cursor 236 | const cursor = this.allocateCursor(null, start); 237 | 238 | while (cursor.next !== null) { 239 | const item = cursor.next; 240 | cursor.next = item.next; 241 | if (fn.call(thisArg, item.data, item, this)) { 242 | break; 243 | } 244 | } 245 | 246 | // pop cursor 247 | this.releaseCursor(); 248 | } 249 | prevUntil(start, fn, thisArg = this) { 250 | if (start === null) { 251 | return; 252 | } 253 | 254 | // push cursor 255 | const cursor = this.allocateCursor(start, null); 256 | 257 | while (cursor.prev !== null) { 258 | const item = cursor.prev; 259 | cursor.prev = item.prev; 260 | if (fn.call(thisArg, item.data, item, this)) { 261 | break; 262 | } 263 | } 264 | 265 | // pop cursor 266 | this.releaseCursor(); 267 | } 268 | 269 | // mutation 270 | clear() { 271 | this.head = null; 272 | this.tail = null; 273 | } 274 | copy() { 275 | const result = new List(); 276 | 277 | for (let data of this) { 278 | result.appendData(data); 279 | } 280 | 281 | return result; 282 | } 283 | prepend(item) { 284 | // head 285 | // ^ 286 | // item 287 | this.updateCursors(null, item, this.head, item); 288 | 289 | // insert to the beginning of the list 290 | if (this.head !== null) { 291 | // new item <- first item 292 | this.head.prev = item; 293 | // new item -> first item 294 | item.next = this.head; 295 | } else { 296 | // if list has no head, then it also has no tail 297 | // in this case tail points to the new item 298 | this.tail = item; 299 | } 300 | 301 | // head always points to new item 302 | this.head = item; 303 | return this; 304 | } 305 | prependData(data) { 306 | return this.prepend(List.createItem(data)); 307 | } 308 | append(item) { 309 | return this.insert(item); 310 | } 311 | appendData(data) { 312 | return this.insert(List.createItem(data)); 313 | } 314 | insert(item, before = null) { 315 | if (before !== null) { 316 | // prev before 317 | // ^ 318 | // item 319 | this.updateCursors(before.prev, item, before, item); 320 | 321 | if (before.prev === null) { 322 | // insert to the beginning of list 323 | if (this.head !== before) { 324 | throw new Error("before doesn't belong to list"); 325 | } 326 | // since head points to before therefore list doesn't empty 327 | // no need to check tail 328 | this.head = item; 329 | before.prev = item; 330 | item.next = before; 331 | this.updateCursors(null, item); 332 | } else { 333 | // insert between two items 334 | before.prev.next = item; 335 | item.prev = before.prev; 336 | before.prev = item; 337 | item.next = before; 338 | } 339 | } else { 340 | // tail 341 | // ^ 342 | // item 343 | this.updateCursors(this.tail, item, null, item); 344 | 345 | // insert to the ending of the list 346 | if (this.tail !== null) { 347 | // last item -> new item 348 | this.tail.next = item; 349 | // last item <- new item 350 | item.prev = this.tail; 351 | } else { 352 | // if list has no tail, then it also has no head 353 | // in this case head points to new item 354 | this.head = item; 355 | } 356 | 357 | // tail always points to new item 358 | this.tail = item; 359 | } 360 | 361 | return this; 362 | } 363 | insertData(data, before) { 364 | return this.insert(List.createItem(data), before); 365 | } 366 | remove(item) { 367 | // item 368 | // ^ 369 | // prev next 370 | this.updateCursors(item, item.prev, item, item.next); 371 | 372 | if (item.prev !== null) { 373 | item.prev.next = item.next; 374 | } else { 375 | if (this.head !== item) { 376 | throw new Error("item doesn't belong to list"); 377 | } 378 | 379 | this.head = item.next; 380 | } 381 | 382 | if (item.next !== null) { 383 | item.next.prev = item.prev; 384 | } else { 385 | if (this.tail !== item) { 386 | throw new Error("item doesn't belong to list"); 387 | } 388 | 389 | this.tail = item.prev; 390 | } 391 | 392 | item.prev = null; 393 | item.next = null; 394 | 395 | return item; 396 | } 397 | push(data) { 398 | this.insert(List.createItem(data)); 399 | } 400 | pop() { 401 | return this.tail !== null ? this.remove(this.tail) : null; 402 | } 403 | unshift(data) { 404 | this.prepend(List.createItem(data)); 405 | } 406 | shift() { 407 | return this.head !== null ? this.remove(this.head) : null; 408 | } 409 | prependList(list) { 410 | return this.insertList(list, this.head); 411 | } 412 | appendList(list) { 413 | return this.insertList(list); 414 | } 415 | insertList(list, before) { 416 | // ignore empty lists 417 | if (list.head === null) { 418 | return this; 419 | } 420 | 421 | if (before !== undefined && before !== null) { 422 | this.updateCursors(before.prev, list.tail, before, list.head); 423 | 424 | // insert in the middle of dist list 425 | if (before.prev !== null) { 426 | // before.prev <-> list.head 427 | before.prev.next = list.head; 428 | list.head.prev = before.prev; 429 | } else { 430 | this.head = list.head; 431 | } 432 | 433 | before.prev = list.tail; 434 | list.tail.next = before; 435 | } else { 436 | this.updateCursors(this.tail, list.tail, null, list.head); 437 | 438 | // insert to end of the list 439 | if (this.tail !== null) { 440 | // if destination list has a tail, then it also has a head, 441 | // but head doesn't change 442 | // dest tail -> source head 443 | this.tail.next = list.head; 444 | // dest tail <- source head 445 | list.head.prev = this.tail; 446 | } else { 447 | // if list has no a tail, then it also has no a head 448 | // in this case points head to new item 449 | this.head = list.head; 450 | } 451 | 452 | // tail always start point to new item 453 | this.tail = list.tail; 454 | } 455 | 456 | list.head = null; 457 | list.tail = null; 458 | return this; 459 | } 460 | replace(oldItem, newItemOrList) { 461 | if ('head' in newItemOrList) { 462 | this.insertList(newItemOrList, oldItem); 463 | } else { 464 | this.insert(newItemOrList, oldItem); 465 | } 466 | 467 | this.remove(oldItem); 468 | } 469 | } 470 | -------------------------------------------------------------------------------- /fixtures/sample.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pnpm/reflink/2223f06f51cca2635846937281892673415f332b/fixtures/sample.pyc -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Create a reflink asynchronously. 3 | * @param {String} src Source of the reflink. 4 | * @param {String} dst Target of the reflink. 5 | * @returns {Promise.} 6 | */ 7 | export function reflinkFile(src: string, dst: string): Promise; 8 | /** 9 | * Create a reflink asynchronously. 10 | * @param {String} src Source of the reflink. 11 | * @param {String} dst Target of the reflink. 12 | * @returns {number} 13 | */ 14 | export function reflinkFileSync(src: string, dst: string): number; 15 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | const binding = require('./binding'); 3 | 4 | /** 5 | * Throw a reflink error. 6 | * @param {binding.ReflinkError} reflinkError Error properties. 7 | * @returns {Error & binding.ReflinkError} 8 | */ 9 | function createReflinkError(reflinkError) { 10 | const error = new Error(reflinkError.message) 11 | return Object.assign(error, { 12 | path: reflinkError.path, 13 | dest: reflinkError.dest, 14 | code: reflinkError.code, 15 | errno: reflinkError.errno, 16 | }); 17 | } 18 | 19 | /** 20 | * Either throw the reflink error or return the number. 21 | * @param {number | binding.ReflinkError} result Result of the binding function. 22 | * @returns {number} 23 | */ 24 | function handleReflinkResult(result) { 25 | if (typeof result === 'number') { 26 | return result; 27 | } else { 28 | throw createReflinkError(result); 29 | } 30 | } 31 | 32 | /** 33 | * Create a reflink asynchronously. 34 | * @param {String} src Source of the reflink. 35 | * @param {String} dst Target of the reflink. 36 | * @returns {Promise.} 37 | */ 38 | const reflinkFile = (src, dst) => binding.reflinkFile(src, dst).then(handleReflinkResult); 39 | 40 | /** 41 | * Create a reflink asynchronously. 42 | * @param {String} src Source of the reflink. 43 | * @param {String} dst Target of the reflink. 44 | * @returns {number} 45 | */ 46 | const reflinkFileSync = (src, dst) => handleReflinkResult(binding.reflinkFileSync(src, dst)); 47 | 48 | module.exports = { 49 | reflinkFile, 50 | reflinkFileSync, 51 | }; 52 | -------------------------------------------------------------------------------- /infinite_clone_test.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import fs from 'fs/promises'; 3 | import crypto from 'crypto'; 4 | import path from 'path'; 5 | import { rimraf } from 'rimraf'; 6 | import chalk from 'chalk'; 7 | import { reflinkFile } from './index.js'; 8 | 9 | async function main() { 10 | const originalSrcPath = path.resolve('./package.json'); 11 | const originalContent = await fs.readFile(originalSrcPath, 'utf-8'); 12 | 13 | let iteration = 0; 14 | 15 | while (iteration < 50) { 16 | // Remove and recreate the sandbox directory 17 | rimraf.sync('./sandbox'); // Using synchronous rimraf for simplicity 18 | await fs.mkdir('./sandbox', { recursive: true }); 19 | 20 | // Create a random name for the new base file and copy package.json to sandbox directory 21 | const randomName = `base-${Math.random().toString(36).substring(2)}.json`; 22 | const newSrcPath = path.join('./sandbox', randomName); 23 | await fs.writeFile(newSrcPath, originalContent); 24 | 25 | const srcFile = { 26 | path: newSrcPath, 27 | content: originalContent, 28 | }; 29 | const srcHash = createHash(srcFile.content); 30 | 31 | for (let i = 0; i < 1000; i++) { 32 | const destPath = path.join('./sandbox', `file1-copy-${i}.txt`); 33 | 34 | await reflinkFile(srcFile.path, destPath); 35 | 36 | const destContent = await fs.readFile(destPath, 'utf-8'); 37 | const destHash = createHash(destContent); 38 | 39 | if (destHash !== srcHash) { 40 | console.log(`Hash mismatch detected on file: ${destPath}`); 41 | console.log(`Src Hash: ${srcHash}, Dest Hash: ${destHash}`); 42 | return; 43 | } 44 | } 45 | 46 | iteration++; 47 | 48 | console.log( 49 | chalk.green( 50 | `Iteration ${iteration} successful ${chalk.gray.dim( 51 | `[${iteration * 1000} files cloned successfully]` 52 | )}` 53 | ) 54 | ); 55 | } 56 | } 57 | 58 | function createHash(content) { 59 | return crypto.createHash('sha256').update(content).digest('hex'); 60 | } 61 | 62 | main().catch((err) => { 63 | console.error(err); 64 | process.exit(1); 65 | }); 66 | -------------------------------------------------------------------------------- /npm/darwin-arm64/README.md: -------------------------------------------------------------------------------- 1 | # `reflink-darwin-arm64` 2 | 3 | This is the **aarch64-apple-darwin** binary for `reflink` 4 | -------------------------------------------------------------------------------- /npm/darwin-arm64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@reflink/reflink-darwin-arm64", 3 | "version": "0.1.16", 4 | "repository": { 5 | "url": "https://github.com/pnpm/reflink.git", 6 | "type": "git", 7 | "directory": "darwin-arm64" 8 | }, 9 | "os": [ 10 | "darwin" 11 | ], 12 | "cpu": [ 13 | "arm64" 14 | ], 15 | "main": "reflink.darwin-arm64.node", 16 | "files": [ 17 | "reflink.darwin-arm64.node" 18 | ], 19 | "license": "MIT", 20 | "engines": { 21 | "node": ">= 10" 22 | } 23 | } -------------------------------------------------------------------------------- /npm/darwin-x64/README.md: -------------------------------------------------------------------------------- 1 | # `reflink-darwin-x64` 2 | 3 | This is the **x86_64-apple-darwin** binary for `reflink` 4 | -------------------------------------------------------------------------------- /npm/darwin-x64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@reflink/reflink-darwin-x64", 3 | "version": "0.1.16", 4 | "repository": { 5 | "url": "https://github.com/pnpm/reflink.git", 6 | "type": "git", 7 | "directory": "darwin-x64" 8 | }, 9 | "os": [ 10 | "darwin" 11 | ], 12 | "cpu": [ 13 | "x64" 14 | ], 15 | "main": "reflink.darwin-x64.node", 16 | "files": [ 17 | "reflink.darwin-x64.node" 18 | ], 19 | "license": "MIT", 20 | "engines": { 21 | "node": ">= 10" 22 | } 23 | } -------------------------------------------------------------------------------- /npm/linux-arm64-gnu/README.md: -------------------------------------------------------------------------------- 1 | # `reflink-linux-arm64-gnu` 2 | 3 | This is the **aarch64-unknown-linux-gnu** binary for `reflink` 4 | -------------------------------------------------------------------------------- /npm/linux-arm64-gnu/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@reflink/reflink-linux-arm64-gnu", 3 | "version": "0.1.16", 4 | "repository": { 5 | "url": "https://github.com/pnpm/reflink.git", 6 | "type": "git", 7 | "directory": "linux-arm64-gnu" 8 | }, 9 | "os": [ 10 | "linux" 11 | ], 12 | "cpu": [ 13 | "arm64" 14 | ], 15 | "main": "reflink.linux-arm64-gnu.node", 16 | "files": [ 17 | "reflink.linux-arm64-gnu.node" 18 | ], 19 | "license": "MIT", 20 | "engines": { 21 | "node": ">= 10" 22 | }, 23 | "libc": [ 24 | "glibc" 25 | ] 26 | } -------------------------------------------------------------------------------- /npm/linux-arm64-musl/README.md: -------------------------------------------------------------------------------- 1 | # `reflink-linux-arm64-musl` 2 | 3 | This is the **aarch64-unknown-linux-musl** binary for `reflink` 4 | -------------------------------------------------------------------------------- /npm/linux-arm64-musl/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@reflink/reflink-linux-arm64-musl", 3 | "version": "0.1.16", 4 | "repository": { 5 | "url": "https://github.com/pnpm/reflink.git", 6 | "type": "git", 7 | "directory": "linux-arm64-musl" 8 | }, 9 | "os": [ 10 | "linux" 11 | ], 12 | "cpu": [ 13 | "arm64" 14 | ], 15 | "main": "reflink.linux-arm64-musl.node", 16 | "files": [ 17 | "reflink.linux-arm64-musl.node" 18 | ], 19 | "license": "MIT", 20 | "engines": { 21 | "node": ">= 10" 22 | }, 23 | "libc": [ 24 | "musl" 25 | ] 26 | } -------------------------------------------------------------------------------- /npm/linux-x64-gnu/README.md: -------------------------------------------------------------------------------- 1 | # `reflink-linux-x64-gnu` 2 | 3 | This is the **x86_64-unknown-linux-gnu** binary for `reflink` 4 | -------------------------------------------------------------------------------- /npm/linux-x64-gnu/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@reflink/reflink-linux-x64-gnu", 3 | "version": "0.1.16", 4 | "repository": { 5 | "url": "https://github.com/pnpm/reflink.git", 6 | "type": "git", 7 | "directory": "linux-x64-gnu" 8 | }, 9 | "os": [ 10 | "linux" 11 | ], 12 | "cpu": [ 13 | "x64" 14 | ], 15 | "main": "reflink.linux-x64-gnu.node", 16 | "files": [ 17 | "reflink.linux-x64-gnu.node" 18 | ], 19 | "license": "MIT", 20 | "engines": { 21 | "node": ">= 10" 22 | }, 23 | "libc": [ 24 | "glibc" 25 | ] 26 | } -------------------------------------------------------------------------------- /npm/linux-x64-musl/README.md: -------------------------------------------------------------------------------- 1 | # `reflink-linux-x64-musl` 2 | 3 | This is the **x86_64-unknown-linux-musl** binary for `reflink` 4 | -------------------------------------------------------------------------------- /npm/linux-x64-musl/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@reflink/reflink-linux-x64-musl", 3 | "version": "0.1.16", 4 | "repository": { 5 | "url": "https://github.com/pnpm/reflink.git", 6 | "type": "git", 7 | "directory": "linux-x64-musl" 8 | }, 9 | "os": [ 10 | "linux" 11 | ], 12 | "cpu": [ 13 | "x64" 14 | ], 15 | "main": "reflink.linux-x64-musl.node", 16 | "files": [ 17 | "reflink.linux-x64-musl.node" 18 | ], 19 | "license": "MIT", 20 | "engines": { 21 | "node": ">= 10" 22 | }, 23 | "libc": [ 24 | "musl" 25 | ] 26 | } -------------------------------------------------------------------------------- /npm/win32-arm64-msvc/README.md: -------------------------------------------------------------------------------- 1 | # `reflink-win32-arm64-msvc` 2 | 3 | This is the **aarch64-pc-windows-msvc** binary for `reflink` 4 | -------------------------------------------------------------------------------- /npm/win32-arm64-msvc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@reflink/reflink-win32-arm64-msvc", 3 | "version": "0.1.16", 4 | "repository": { 5 | "url": "https://github.com/pnpm/reflink.git", 6 | "type": "git", 7 | "directory": "win32-arm64-msvc" 8 | }, 9 | "os": [ 10 | "win32" 11 | ], 12 | "cpu": [ 13 | "arm64" 14 | ], 15 | "main": "reflink.win32-arm64-msvc.node", 16 | "files": [ 17 | "reflink.win32-arm64-msvc.node" 18 | ], 19 | "license": "MIT", 20 | "engines": { 21 | "node": ">= 10" 22 | } 23 | } -------------------------------------------------------------------------------- /npm/win32-x64-msvc/README.md: -------------------------------------------------------------------------------- 1 | # `reflink-win32-x64-msvc` 2 | 3 | This is the **x86_64-pc-windows-msvc** binary for `reflink` 4 | -------------------------------------------------------------------------------- /npm/win32-x64-msvc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@reflink/reflink-win32-x64-msvc", 3 | "version": "0.1.16", 4 | "repository": { 5 | "url": "https://github.com/pnpm/reflink.git", 6 | "type": "git", 7 | "directory": "win32-x64-msvc" 8 | }, 9 | "os": [ 10 | "win32" 11 | ], 12 | "cpu": [ 13 | "x64" 14 | ], 15 | "main": "reflink.win32-x64-msvc.node", 16 | "files": [ 17 | "reflink.win32-x64-msvc.node" 18 | ], 19 | "license": "MIT", 20 | "engines": { 21 | "node": ">= 10" 22 | } 23 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@reflink/reflink", 3 | "version": "0.1.19", 4 | "main": "index.js", 5 | "types": "index.d.ts", 6 | "repository": { 7 | "url": "https://github.com/pnpm/reflink" 8 | }, 9 | "napi": { 10 | "name": "reflink", 11 | "triples": { 12 | "additional": [ 13 | "aarch64-apple-darwin", 14 | "aarch64-unknown-linux-gnu", 15 | "aarch64-unknown-linux-musl", 16 | "aarch64-pc-windows-msvc", 17 | "x86_64-unknown-linux-musl" 18 | ] 19 | }, 20 | "npmClient": "npm" 21 | }, 22 | "files": [ 23 | "binding.js", 24 | "binding.d.ts", 25 | "index.js", 26 | "index.d.ts" 27 | ], 28 | "license": "MIT", 29 | "devDependencies": { 30 | "@napi-rs/cli": "^2.16.3", 31 | "@types/node": "^20.8.0", 32 | "chalk": "^5.3.0", 33 | "rimraf": "^5.0.5", 34 | "typescript": "^5.2.2", 35 | "vitest": "^0.34.6" 36 | }, 37 | "engines": { 38 | "node": ">= 10" 39 | }, 40 | "scripts": { 41 | "artifacts": "napi artifacts", 42 | "build": "pnpm gen-dts && napi build --js binding.js --dts binding.d.ts --platform --release", 43 | "build:debug": "pnpm gen-dts && napi build --js binding.js --dts binding.d.ts --platform", 44 | "gen-dts": "tsc --checkJs index.js --declaration --emitDeclarationOnly", 45 | "prepublishOnly": "napi prepublish -t npm", 46 | "pretest": "pnpm build", 47 | "test": "cargo t && vitest && rimraf -g __reflink-tests-*", 48 | "bench": "node benchmark.mjs", 49 | "universal": "napi universal", 50 | "version": "napi version" 51 | }, 52 | "packageManager": "pnpm@9.12.3" 53 | } 54 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | devDependencies: 8 | '@napi-rs/cli': 9 | specifier: ^2.16.3 10 | version: 2.16.3 11 | '@types/node': 12 | specifier: ^20.8.0 13 | version: 20.8.9 14 | chalk: 15 | specifier: ^5.3.0 16 | version: 5.3.0 17 | rimraf: 18 | specifier: ^5.0.5 19 | version: 5.0.5 20 | typescript: 21 | specifier: ^5.2.2 22 | version: 5.2.2 23 | vitest: 24 | specifier: ^0.34.6 25 | version: 0.34.6 26 | 27 | packages: 28 | 29 | /@esbuild/android-arm64@0.18.20: 30 | resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} 31 | engines: {node: '>=12'} 32 | cpu: [arm64] 33 | os: [android] 34 | requiresBuild: true 35 | dev: true 36 | optional: true 37 | 38 | /@esbuild/android-arm@0.18.20: 39 | resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} 40 | engines: {node: '>=12'} 41 | cpu: [arm] 42 | os: [android] 43 | requiresBuild: true 44 | dev: true 45 | optional: true 46 | 47 | /@esbuild/android-x64@0.18.20: 48 | resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} 49 | engines: {node: '>=12'} 50 | cpu: [x64] 51 | os: [android] 52 | requiresBuild: true 53 | dev: true 54 | optional: true 55 | 56 | /@esbuild/darwin-arm64@0.18.20: 57 | resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} 58 | engines: {node: '>=12'} 59 | cpu: [arm64] 60 | os: [darwin] 61 | requiresBuild: true 62 | dev: true 63 | optional: true 64 | 65 | /@esbuild/darwin-x64@0.18.20: 66 | resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} 67 | engines: {node: '>=12'} 68 | cpu: [x64] 69 | os: [darwin] 70 | requiresBuild: true 71 | dev: true 72 | optional: true 73 | 74 | /@esbuild/freebsd-arm64@0.18.20: 75 | resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} 76 | engines: {node: '>=12'} 77 | cpu: [arm64] 78 | os: [freebsd] 79 | requiresBuild: true 80 | dev: true 81 | optional: true 82 | 83 | /@esbuild/freebsd-x64@0.18.20: 84 | resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} 85 | engines: {node: '>=12'} 86 | cpu: [x64] 87 | os: [freebsd] 88 | requiresBuild: true 89 | dev: true 90 | optional: true 91 | 92 | /@esbuild/linux-arm64@0.18.20: 93 | resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} 94 | engines: {node: '>=12'} 95 | cpu: [arm64] 96 | os: [linux] 97 | requiresBuild: true 98 | dev: true 99 | optional: true 100 | 101 | /@esbuild/linux-arm@0.18.20: 102 | resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} 103 | engines: {node: '>=12'} 104 | cpu: [arm] 105 | os: [linux] 106 | requiresBuild: true 107 | dev: true 108 | optional: true 109 | 110 | /@esbuild/linux-ia32@0.18.20: 111 | resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} 112 | engines: {node: '>=12'} 113 | cpu: [ia32] 114 | os: [linux] 115 | requiresBuild: true 116 | dev: true 117 | optional: true 118 | 119 | /@esbuild/linux-loong64@0.18.20: 120 | resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} 121 | engines: {node: '>=12'} 122 | cpu: [loong64] 123 | os: [linux] 124 | requiresBuild: true 125 | dev: true 126 | optional: true 127 | 128 | /@esbuild/linux-mips64el@0.18.20: 129 | resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} 130 | engines: {node: '>=12'} 131 | cpu: [mips64el] 132 | os: [linux] 133 | requiresBuild: true 134 | dev: true 135 | optional: true 136 | 137 | /@esbuild/linux-ppc64@0.18.20: 138 | resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} 139 | engines: {node: '>=12'} 140 | cpu: [ppc64] 141 | os: [linux] 142 | requiresBuild: true 143 | dev: true 144 | optional: true 145 | 146 | /@esbuild/linux-riscv64@0.18.20: 147 | resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} 148 | engines: {node: '>=12'} 149 | cpu: [riscv64] 150 | os: [linux] 151 | requiresBuild: true 152 | dev: true 153 | optional: true 154 | 155 | /@esbuild/linux-s390x@0.18.20: 156 | resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} 157 | engines: {node: '>=12'} 158 | cpu: [s390x] 159 | os: [linux] 160 | requiresBuild: true 161 | dev: true 162 | optional: true 163 | 164 | /@esbuild/linux-x64@0.18.20: 165 | resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} 166 | engines: {node: '>=12'} 167 | cpu: [x64] 168 | os: [linux] 169 | requiresBuild: true 170 | dev: true 171 | optional: true 172 | 173 | /@esbuild/netbsd-x64@0.18.20: 174 | resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} 175 | engines: {node: '>=12'} 176 | cpu: [x64] 177 | os: [netbsd] 178 | requiresBuild: true 179 | dev: true 180 | optional: true 181 | 182 | /@esbuild/openbsd-x64@0.18.20: 183 | resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} 184 | engines: {node: '>=12'} 185 | cpu: [x64] 186 | os: [openbsd] 187 | requiresBuild: true 188 | dev: true 189 | optional: true 190 | 191 | /@esbuild/sunos-x64@0.18.20: 192 | resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} 193 | engines: {node: '>=12'} 194 | cpu: [x64] 195 | os: [sunos] 196 | requiresBuild: true 197 | dev: true 198 | optional: true 199 | 200 | /@esbuild/win32-arm64@0.18.20: 201 | resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} 202 | engines: {node: '>=12'} 203 | cpu: [arm64] 204 | os: [win32] 205 | requiresBuild: true 206 | dev: true 207 | optional: true 208 | 209 | /@esbuild/win32-ia32@0.18.20: 210 | resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} 211 | engines: {node: '>=12'} 212 | cpu: [ia32] 213 | os: [win32] 214 | requiresBuild: true 215 | dev: true 216 | optional: true 217 | 218 | /@esbuild/win32-x64@0.18.20: 219 | resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} 220 | engines: {node: '>=12'} 221 | cpu: [x64] 222 | os: [win32] 223 | requiresBuild: true 224 | dev: true 225 | optional: true 226 | 227 | /@isaacs/cliui@8.0.2: 228 | resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} 229 | engines: {node: '>=12'} 230 | dependencies: 231 | string-width: 5.1.2 232 | string-width-cjs: /string-width@4.2.3 233 | strip-ansi: 7.1.0 234 | strip-ansi-cjs: /strip-ansi@6.0.1 235 | wrap-ansi: 8.1.0 236 | wrap-ansi-cjs: /wrap-ansi@7.0.0 237 | dev: true 238 | 239 | /@jest/schemas@29.6.3: 240 | resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} 241 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 242 | dependencies: 243 | '@sinclair/typebox': 0.27.8 244 | dev: true 245 | 246 | /@jridgewell/sourcemap-codec@1.4.15: 247 | resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} 248 | dev: true 249 | 250 | /@napi-rs/cli@2.16.3: 251 | resolution: {integrity: sha512-3mLNPlbbOhpbIUKicLrJtIearlHXUuXL3UeueYyRRplpVMNkdn8xCyzY6PcYZi3JXR8bmCOiWgkVmLnrSL7DKw==} 252 | engines: {node: '>= 10'} 253 | hasBin: true 254 | dev: true 255 | 256 | /@pkgjs/parseargs@0.11.0: 257 | resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} 258 | engines: {node: '>=14'} 259 | requiresBuild: true 260 | dev: true 261 | optional: true 262 | 263 | /@sinclair/typebox@0.27.8: 264 | resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} 265 | dev: true 266 | 267 | /@types/chai-subset@1.3.4: 268 | resolution: {integrity: sha512-CCWNXrJYSUIojZ1149ksLl3AN9cmZ5djf+yUoVVV+NuYrtydItQVlL2ZDqyC6M6O9LWRnVf8yYDxbXHO2TfQZg==} 269 | dependencies: 270 | '@types/chai': 4.3.9 271 | dev: true 272 | 273 | /@types/chai@4.3.9: 274 | resolution: {integrity: sha512-69TtiDzu0bcmKQv3yg1Zx409/Kd7r0b5F1PfpYJfSHzLGtB53547V4u+9iqKYsTu/O2ai6KTb0TInNpvuQ3qmg==} 275 | dev: true 276 | 277 | /@types/node@20.8.9: 278 | resolution: {integrity: sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==} 279 | dependencies: 280 | undici-types: 5.26.5 281 | dev: true 282 | 283 | /@vitest/expect@0.34.6: 284 | resolution: {integrity: sha512-QUzKpUQRc1qC7qdGo7rMK3AkETI7w18gTCUrsNnyjjJKYiuUB9+TQK3QnR1unhCnWRC0AbKv2omLGQDF/mIjOw==} 285 | dependencies: 286 | '@vitest/spy': 0.34.6 287 | '@vitest/utils': 0.34.6 288 | chai: 4.3.10 289 | dev: true 290 | 291 | /@vitest/runner@0.34.6: 292 | resolution: {integrity: sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==} 293 | dependencies: 294 | '@vitest/utils': 0.34.6 295 | p-limit: 4.0.0 296 | pathe: 1.1.1 297 | dev: true 298 | 299 | /@vitest/snapshot@0.34.6: 300 | resolution: {integrity: sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==} 301 | dependencies: 302 | magic-string: 0.30.5 303 | pathe: 1.1.1 304 | pretty-format: 29.7.0 305 | dev: true 306 | 307 | /@vitest/spy@0.34.6: 308 | resolution: {integrity: sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==} 309 | dependencies: 310 | tinyspy: 2.2.0 311 | dev: true 312 | 313 | /@vitest/utils@0.34.6: 314 | resolution: {integrity: sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==} 315 | dependencies: 316 | diff-sequences: 29.6.3 317 | loupe: 2.3.7 318 | pretty-format: 29.7.0 319 | dev: true 320 | 321 | /acorn-walk@8.3.0: 322 | resolution: {integrity: sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==} 323 | engines: {node: '>=0.4.0'} 324 | dev: true 325 | 326 | /acorn@8.11.2: 327 | resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} 328 | engines: {node: '>=0.4.0'} 329 | hasBin: true 330 | dev: true 331 | 332 | /ansi-regex@5.0.1: 333 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 334 | engines: {node: '>=8'} 335 | dev: true 336 | 337 | /ansi-regex@6.0.1: 338 | resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} 339 | engines: {node: '>=12'} 340 | dev: true 341 | 342 | /ansi-styles@4.3.0: 343 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 344 | engines: {node: '>=8'} 345 | dependencies: 346 | color-convert: 2.0.1 347 | dev: true 348 | 349 | /ansi-styles@5.2.0: 350 | resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} 351 | engines: {node: '>=10'} 352 | dev: true 353 | 354 | /ansi-styles@6.2.1: 355 | resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} 356 | engines: {node: '>=12'} 357 | dev: true 358 | 359 | /assertion-error@1.1.0: 360 | resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} 361 | dev: true 362 | 363 | /balanced-match@1.0.2: 364 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 365 | dev: true 366 | 367 | /brace-expansion@2.0.1: 368 | resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 369 | dependencies: 370 | balanced-match: 1.0.2 371 | dev: true 372 | 373 | /cac@6.7.14: 374 | resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} 375 | engines: {node: '>=8'} 376 | dev: true 377 | 378 | /chai@4.3.10: 379 | resolution: {integrity: sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==} 380 | engines: {node: '>=4'} 381 | dependencies: 382 | assertion-error: 1.1.0 383 | check-error: 1.0.3 384 | deep-eql: 4.1.3 385 | get-func-name: 2.0.2 386 | loupe: 2.3.7 387 | pathval: 1.1.1 388 | type-detect: 4.0.8 389 | dev: true 390 | 391 | /chalk@5.3.0: 392 | resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} 393 | engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} 394 | dev: true 395 | 396 | /check-error@1.0.3: 397 | resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} 398 | dependencies: 399 | get-func-name: 2.0.2 400 | dev: true 401 | 402 | /color-convert@2.0.1: 403 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 404 | engines: {node: '>=7.0.0'} 405 | dependencies: 406 | color-name: 1.1.4 407 | dev: true 408 | 409 | /color-name@1.1.4: 410 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 411 | dev: true 412 | 413 | /cross-spawn@7.0.3: 414 | resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} 415 | engines: {node: '>= 8'} 416 | dependencies: 417 | path-key: 3.1.1 418 | shebang-command: 2.0.0 419 | which: 2.0.2 420 | dev: true 421 | 422 | /debug@4.3.4: 423 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 424 | engines: {node: '>=6.0'} 425 | peerDependencies: 426 | supports-color: '*' 427 | peerDependenciesMeta: 428 | supports-color: 429 | optional: true 430 | dependencies: 431 | ms: 2.1.2 432 | dev: true 433 | 434 | /deep-eql@4.1.3: 435 | resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} 436 | engines: {node: '>=6'} 437 | dependencies: 438 | type-detect: 4.0.8 439 | dev: true 440 | 441 | /diff-sequences@29.6.3: 442 | resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} 443 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 444 | dev: true 445 | 446 | /eastasianwidth@0.2.0: 447 | resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} 448 | dev: true 449 | 450 | /emoji-regex@8.0.0: 451 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 452 | dev: true 453 | 454 | /emoji-regex@9.2.2: 455 | resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} 456 | dev: true 457 | 458 | /esbuild@0.18.20: 459 | resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==} 460 | engines: {node: '>=12'} 461 | hasBin: true 462 | requiresBuild: true 463 | optionalDependencies: 464 | '@esbuild/android-arm': 0.18.20 465 | '@esbuild/android-arm64': 0.18.20 466 | '@esbuild/android-x64': 0.18.20 467 | '@esbuild/darwin-arm64': 0.18.20 468 | '@esbuild/darwin-x64': 0.18.20 469 | '@esbuild/freebsd-arm64': 0.18.20 470 | '@esbuild/freebsd-x64': 0.18.20 471 | '@esbuild/linux-arm': 0.18.20 472 | '@esbuild/linux-arm64': 0.18.20 473 | '@esbuild/linux-ia32': 0.18.20 474 | '@esbuild/linux-loong64': 0.18.20 475 | '@esbuild/linux-mips64el': 0.18.20 476 | '@esbuild/linux-ppc64': 0.18.20 477 | '@esbuild/linux-riscv64': 0.18.20 478 | '@esbuild/linux-s390x': 0.18.20 479 | '@esbuild/linux-x64': 0.18.20 480 | '@esbuild/netbsd-x64': 0.18.20 481 | '@esbuild/openbsd-x64': 0.18.20 482 | '@esbuild/sunos-x64': 0.18.20 483 | '@esbuild/win32-arm64': 0.18.20 484 | '@esbuild/win32-ia32': 0.18.20 485 | '@esbuild/win32-x64': 0.18.20 486 | dev: true 487 | 488 | /foreground-child@3.1.1: 489 | resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} 490 | engines: {node: '>=14'} 491 | dependencies: 492 | cross-spawn: 7.0.3 493 | signal-exit: 4.1.0 494 | dev: true 495 | 496 | /fsevents@2.3.3: 497 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 498 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 499 | os: [darwin] 500 | requiresBuild: true 501 | dev: true 502 | optional: true 503 | 504 | /get-func-name@2.0.2: 505 | resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} 506 | dev: true 507 | 508 | /glob@10.3.10: 509 | resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} 510 | engines: {node: '>=16 || 14 >=14.17'} 511 | hasBin: true 512 | dependencies: 513 | foreground-child: 3.1.1 514 | jackspeak: 2.3.6 515 | minimatch: 9.0.3 516 | minipass: 7.0.4 517 | path-scurry: 1.10.1 518 | dev: true 519 | 520 | /is-fullwidth-code-point@3.0.0: 521 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 522 | engines: {node: '>=8'} 523 | dev: true 524 | 525 | /isexe@2.0.0: 526 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 527 | dev: true 528 | 529 | /jackspeak@2.3.6: 530 | resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} 531 | engines: {node: '>=14'} 532 | dependencies: 533 | '@isaacs/cliui': 8.0.2 534 | optionalDependencies: 535 | '@pkgjs/parseargs': 0.11.0 536 | dev: true 537 | 538 | /jsonc-parser@3.2.0: 539 | resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} 540 | dev: true 541 | 542 | /local-pkg@0.4.3: 543 | resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} 544 | engines: {node: '>=14'} 545 | dev: true 546 | 547 | /loupe@2.3.7: 548 | resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} 549 | dependencies: 550 | get-func-name: 2.0.2 551 | dev: true 552 | 553 | /lru-cache@10.0.1: 554 | resolution: {integrity: sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==} 555 | engines: {node: 14 || >=16.14} 556 | dev: true 557 | 558 | /magic-string@0.30.5: 559 | resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} 560 | engines: {node: '>=12'} 561 | dependencies: 562 | '@jridgewell/sourcemap-codec': 1.4.15 563 | dev: true 564 | 565 | /minimatch@9.0.3: 566 | resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} 567 | engines: {node: '>=16 || 14 >=14.17'} 568 | dependencies: 569 | brace-expansion: 2.0.1 570 | dev: true 571 | 572 | /minipass@7.0.4: 573 | resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} 574 | engines: {node: '>=16 || 14 >=14.17'} 575 | dev: true 576 | 577 | /mlly@1.4.2: 578 | resolution: {integrity: sha512-i/Ykufi2t1EZ6NaPLdfnZk2AX8cs0d+mTzVKuPfqPKPatxLApaBoxJQ9x1/uckXtrS/U5oisPMDkNs0yQTaBRg==} 579 | dependencies: 580 | acorn: 8.11.2 581 | pathe: 1.1.1 582 | pkg-types: 1.0.3 583 | ufo: 1.3.1 584 | dev: true 585 | 586 | /ms@2.1.2: 587 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 588 | dev: true 589 | 590 | /nanoid@3.3.6: 591 | resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} 592 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 593 | hasBin: true 594 | dev: true 595 | 596 | /p-limit@4.0.0: 597 | resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} 598 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 599 | dependencies: 600 | yocto-queue: 1.0.0 601 | dev: true 602 | 603 | /path-key@3.1.1: 604 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 605 | engines: {node: '>=8'} 606 | dev: true 607 | 608 | /path-scurry@1.10.1: 609 | resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} 610 | engines: {node: '>=16 || 14 >=14.17'} 611 | dependencies: 612 | lru-cache: 10.0.1 613 | minipass: 7.0.4 614 | dev: true 615 | 616 | /pathe@1.1.1: 617 | resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} 618 | dev: true 619 | 620 | /pathval@1.1.1: 621 | resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} 622 | dev: true 623 | 624 | /picocolors@1.0.0: 625 | resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} 626 | dev: true 627 | 628 | /pkg-types@1.0.3: 629 | resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} 630 | dependencies: 631 | jsonc-parser: 3.2.0 632 | mlly: 1.4.2 633 | pathe: 1.1.1 634 | dev: true 635 | 636 | /postcss@8.4.31: 637 | resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} 638 | engines: {node: ^10 || ^12 || >=14} 639 | dependencies: 640 | nanoid: 3.3.6 641 | picocolors: 1.0.0 642 | source-map-js: 1.0.2 643 | dev: true 644 | 645 | /pretty-format@29.7.0: 646 | resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} 647 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 648 | dependencies: 649 | '@jest/schemas': 29.6.3 650 | ansi-styles: 5.2.0 651 | react-is: 18.2.0 652 | dev: true 653 | 654 | /react-is@18.2.0: 655 | resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} 656 | dev: true 657 | 658 | /rimraf@5.0.5: 659 | resolution: {integrity: sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==} 660 | engines: {node: '>=14'} 661 | hasBin: true 662 | dependencies: 663 | glob: 10.3.10 664 | dev: true 665 | 666 | /rollup@3.29.4: 667 | resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==} 668 | engines: {node: '>=14.18.0', npm: '>=8.0.0'} 669 | hasBin: true 670 | optionalDependencies: 671 | fsevents: 2.3.3 672 | dev: true 673 | 674 | /shebang-command@2.0.0: 675 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 676 | engines: {node: '>=8'} 677 | dependencies: 678 | shebang-regex: 3.0.0 679 | dev: true 680 | 681 | /shebang-regex@3.0.0: 682 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 683 | engines: {node: '>=8'} 684 | dev: true 685 | 686 | /siginfo@2.0.0: 687 | resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} 688 | dev: true 689 | 690 | /signal-exit@4.1.0: 691 | resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} 692 | engines: {node: '>=14'} 693 | dev: true 694 | 695 | /source-map-js@1.0.2: 696 | resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} 697 | engines: {node: '>=0.10.0'} 698 | dev: true 699 | 700 | /stackback@0.0.2: 701 | resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} 702 | dev: true 703 | 704 | /std-env@3.4.3: 705 | resolution: {integrity: sha512-f9aPhy8fYBuMN+sNfakZV18U39PbalgjXG3lLB9WkaYTxijru61wb57V9wxxNthXM5Sd88ETBWi29qLAsHO52Q==} 706 | dev: true 707 | 708 | /string-width@4.2.3: 709 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 710 | engines: {node: '>=8'} 711 | dependencies: 712 | emoji-regex: 8.0.0 713 | is-fullwidth-code-point: 3.0.0 714 | strip-ansi: 6.0.1 715 | dev: true 716 | 717 | /string-width@5.1.2: 718 | resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} 719 | engines: {node: '>=12'} 720 | dependencies: 721 | eastasianwidth: 0.2.0 722 | emoji-regex: 9.2.2 723 | strip-ansi: 7.1.0 724 | dev: true 725 | 726 | /strip-ansi@6.0.1: 727 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 728 | engines: {node: '>=8'} 729 | dependencies: 730 | ansi-regex: 5.0.1 731 | dev: true 732 | 733 | /strip-ansi@7.1.0: 734 | resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} 735 | engines: {node: '>=12'} 736 | dependencies: 737 | ansi-regex: 6.0.1 738 | dev: true 739 | 740 | /strip-literal@1.3.0: 741 | resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} 742 | dependencies: 743 | acorn: 8.11.2 744 | dev: true 745 | 746 | /tinybench@2.5.1: 747 | resolution: {integrity: sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==} 748 | dev: true 749 | 750 | /tinypool@0.7.0: 751 | resolution: {integrity: sha512-zSYNUlYSMhJ6Zdou4cJwo/p7w5nmAH17GRfU/ui3ctvjXFErXXkruT4MWW6poDeXgCaIBlGLrfU6TbTXxyGMww==} 752 | engines: {node: '>=14.0.0'} 753 | dev: true 754 | 755 | /tinyspy@2.2.0: 756 | resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==} 757 | engines: {node: '>=14.0.0'} 758 | dev: true 759 | 760 | /type-detect@4.0.8: 761 | resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} 762 | engines: {node: '>=4'} 763 | dev: true 764 | 765 | /typescript@5.2.2: 766 | resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} 767 | engines: {node: '>=14.17'} 768 | hasBin: true 769 | dev: true 770 | 771 | /ufo@1.3.1: 772 | resolution: {integrity: sha512-uY/99gMLIOlJPwATcMVYfqDSxUR9//AUcgZMzwfSTJPDKzA1S8mX4VLqa+fiAtveraQUBCz4FFcwVZBGbwBXIw==} 773 | dev: true 774 | 775 | /undici-types@5.26.5: 776 | resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} 777 | dev: true 778 | 779 | /vite-node@0.34.6(@types/node@20.8.9): 780 | resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==} 781 | engines: {node: '>=v14.18.0'} 782 | hasBin: true 783 | dependencies: 784 | cac: 6.7.14 785 | debug: 4.3.4 786 | mlly: 1.4.2 787 | pathe: 1.1.1 788 | picocolors: 1.0.0 789 | vite: 4.5.0(@types/node@20.8.9) 790 | transitivePeerDependencies: 791 | - '@types/node' 792 | - less 793 | - lightningcss 794 | - sass 795 | - stylus 796 | - sugarss 797 | - supports-color 798 | - terser 799 | dev: true 800 | 801 | /vite@4.5.0(@types/node@20.8.9): 802 | resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==} 803 | engines: {node: ^14.18.0 || >=16.0.0} 804 | hasBin: true 805 | peerDependencies: 806 | '@types/node': '>= 14' 807 | less: '*' 808 | lightningcss: ^1.21.0 809 | sass: '*' 810 | stylus: '*' 811 | sugarss: '*' 812 | terser: ^5.4.0 813 | peerDependenciesMeta: 814 | '@types/node': 815 | optional: true 816 | less: 817 | optional: true 818 | lightningcss: 819 | optional: true 820 | sass: 821 | optional: true 822 | stylus: 823 | optional: true 824 | sugarss: 825 | optional: true 826 | terser: 827 | optional: true 828 | dependencies: 829 | '@types/node': 20.8.9 830 | esbuild: 0.18.20 831 | postcss: 8.4.31 832 | rollup: 3.29.4 833 | optionalDependencies: 834 | fsevents: 2.3.3 835 | dev: true 836 | 837 | /vitest@0.34.6: 838 | resolution: {integrity: sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==} 839 | engines: {node: '>=v14.18.0'} 840 | hasBin: true 841 | peerDependencies: 842 | '@edge-runtime/vm': '*' 843 | '@vitest/browser': '*' 844 | '@vitest/ui': '*' 845 | happy-dom: '*' 846 | jsdom: '*' 847 | playwright: '*' 848 | safaridriver: '*' 849 | webdriverio: '*' 850 | peerDependenciesMeta: 851 | '@edge-runtime/vm': 852 | optional: true 853 | '@vitest/browser': 854 | optional: true 855 | '@vitest/ui': 856 | optional: true 857 | happy-dom: 858 | optional: true 859 | jsdom: 860 | optional: true 861 | playwright: 862 | optional: true 863 | safaridriver: 864 | optional: true 865 | webdriverio: 866 | optional: true 867 | dependencies: 868 | '@types/chai': 4.3.9 869 | '@types/chai-subset': 1.3.4 870 | '@types/node': 20.8.9 871 | '@vitest/expect': 0.34.6 872 | '@vitest/runner': 0.34.6 873 | '@vitest/snapshot': 0.34.6 874 | '@vitest/spy': 0.34.6 875 | '@vitest/utils': 0.34.6 876 | acorn: 8.11.2 877 | acorn-walk: 8.3.0 878 | cac: 6.7.14 879 | chai: 4.3.10 880 | debug: 4.3.4 881 | local-pkg: 0.4.3 882 | magic-string: 0.30.5 883 | pathe: 1.1.1 884 | picocolors: 1.0.0 885 | std-env: 3.4.3 886 | strip-literal: 1.3.0 887 | tinybench: 2.5.1 888 | tinypool: 0.7.0 889 | vite: 4.5.0(@types/node@20.8.9) 890 | vite-node: 0.34.6(@types/node@20.8.9) 891 | why-is-node-running: 2.2.2 892 | transitivePeerDependencies: 893 | - less 894 | - lightningcss 895 | - sass 896 | - stylus 897 | - sugarss 898 | - supports-color 899 | - terser 900 | dev: true 901 | 902 | /which@2.0.2: 903 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 904 | engines: {node: '>= 8'} 905 | hasBin: true 906 | dependencies: 907 | isexe: 2.0.0 908 | dev: true 909 | 910 | /why-is-node-running@2.2.2: 911 | resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} 912 | engines: {node: '>=8'} 913 | hasBin: true 914 | dependencies: 915 | siginfo: 2.0.0 916 | stackback: 0.0.2 917 | dev: true 918 | 919 | /wrap-ansi@7.0.0: 920 | resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} 921 | engines: {node: '>=10'} 922 | dependencies: 923 | ansi-styles: 4.3.0 924 | string-width: 4.2.3 925 | strip-ansi: 6.0.1 926 | dev: true 927 | 928 | /wrap-ansi@8.1.0: 929 | resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} 930 | engines: {node: '>=12'} 931 | dependencies: 932 | ansi-styles: 6.2.1 933 | string-width: 5.1.2 934 | strip-ansi: 7.1.0 935 | dev: true 936 | 937 | /yocto-queue@1.0.0: 938 | resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} 939 | engines: {node: '>=12.20'} 940 | dev: true 941 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | 1.82.0 2 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use napi_derive::napi; 2 | use std::io::ErrorKind; 3 | 4 | /// Infer error code and its number. 5 | /// 6 | /// The error number should match `require('os').constants.errno[code]` 7 | fn error_code(kind: ErrorKind) -> Option<(&'static str, i16)> { 8 | // For now, we only return some error codes that pnpm uses. 9 | // Future contributors may add more if they need it. 10 | Some(match kind { 11 | ErrorKind::AlreadyExists => ("EEXIST", 17), 12 | ErrorKind::InvalidInput | ErrorKind::NotFound => ("ENOENT", 2), 13 | ErrorKind::PermissionDenied => ("EPERM", 1), 14 | _ => return None, 15 | }) 16 | } 17 | 18 | /// Contains all properties to construct an actual error. 19 | #[derive(Debug, Clone)] 20 | #[napi(constructor)] 21 | pub struct ReflinkError { 22 | pub message: String, 23 | pub path: String, 24 | pub dest: String, 25 | pub code: Option<&'static str>, 26 | pub errno: Option, 27 | } 28 | 29 | impl ReflinkError { 30 | pub fn new(io_error: std::io::Error, path: String, dest: String) -> Self { 31 | let message = format!("{io_error}, reflink '{path}' -> '{dest}'"); 32 | let (code, errno) = match error_code(io_error.kind()) { 33 | Some((code, errno)) => (Some(code), Some(errno)), 34 | None => (None, None), 35 | }; 36 | ReflinkError { 37 | message, 38 | path, 39 | dest, 40 | code, 41 | errno, 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(clippy::all)] 2 | 3 | mod error; 4 | 5 | use copy_on_write::reflink_file_sync; 6 | use error::ReflinkError; 7 | use napi::{bindgen_prelude::AsyncTask, Either, Env, JsNumber, Result, Task}; 8 | use napi_derive::napi; 9 | use pipe_trait::Pipe; 10 | 11 | pub struct AsyncReflink { 12 | src: String, 13 | dst: String, 14 | } 15 | 16 | #[napi] 17 | impl Task for AsyncReflink { 18 | type Output = std::result::Result<(), std::io::Error>; 19 | type JsValue = Either; 20 | 21 | fn compute(&mut self) -> Result { 22 | Ok(reflink_file_sync(&self.src, &self.dst)) 23 | } 24 | 25 | fn resolve(&mut self, env: Env, output: Self::Output) -> Result { 26 | match output { 27 | Ok(()) => env.create_int32(0).map(Either::A), 28 | Err(io_error) => { 29 | ReflinkError::new(io_error, self.src.to_string(), self.dst.to_string()) 30 | .pipe(Either::B) 31 | .pipe(Ok) 32 | } 33 | } 34 | } 35 | } 36 | 37 | // Async version 38 | #[napi(js_name = "reflinkFile")] 39 | pub fn reflink_task(src: String, dst: String) -> AsyncTask { 40 | AsyncTask::new(AsyncReflink { src, dst }) 41 | } 42 | 43 | // Sync version 44 | #[napi(js_name = "reflinkFileSync")] 45 | pub fn reflink_sync(env: Env, src: String, dst: String) -> Result> { 46 | match reflink_file_sync(&src, &dst) { 47 | Ok(()) => env.create_int32(0).map(Either::A), 48 | Err(io_error) => ReflinkError::new(io_error, src, dst) 49 | .pipe(Either::B) 50 | .pipe(Ok), 51 | } 52 | } 53 | 54 | #[test] 55 | pub fn test_pyc_file() { 56 | let src = "fixtures/sample.pyc"; 57 | let dst = "fixtures/sample.pyc.reflink"; 58 | 59 | let dst_path = std::path::Path::new(dst); 60 | 61 | // Remove the destination file if it already exists 62 | if dst_path.try_exists().unwrap() { 63 | std::fs::remove_file(dst).unwrap(); 64 | } 65 | 66 | // Run the reflink operation 67 | let result = reflink_file_sync(src, dst); 68 | assert!(result.is_ok()); 69 | 70 | println!("Reflinked {src:?} -> {dst:?}"); 71 | 72 | // Further validation: compare the contents of both files to make sure they are identical 73 | let src_contents = std::fs::read(src).expect("Failed to read source file"); 74 | let dst_contents = std::fs::read(dst).expect("Failed to read destination file"); 75 | 76 | assert_eq!(src_contents, dst_contents); 77 | 78 | // Remove the destination file 79 | std::fs::remove_file(dst).unwrap(); 80 | 81 | println!("File contents match, reflink operation successful") 82 | } 83 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": true, 4 | /* Visit https://aka.ms/tsconfig to read more about this file */ 5 | 6 | /* Projects */ 7 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 8 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 9 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 10 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 11 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 12 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 13 | 14 | /* Language and Environment */ 15 | "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 16 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 17 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 18 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 19 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 20 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 21 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 22 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 23 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 24 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 25 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 26 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 27 | 28 | /* Modules */ 29 | "module": "commonjs" /* Specify what module code is generated. */, 30 | // "rootDir": "./", /* Specify the root folder within your source files. */ 31 | // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ 32 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 33 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 34 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 35 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 36 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 37 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 38 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 39 | // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ 40 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 41 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 42 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 43 | // "resolveJsonModule": true, /* Enable importing .json files. */ 44 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 45 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 46 | 47 | /* JavaScript Support */ 48 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 49 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 50 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 51 | 52 | /* Emit */ 53 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 54 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 55 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 56 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 57 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 58 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 59 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 60 | // "removeComments": true, /* Disable emitting comments. */ 61 | // "noEmit": true, /* Disable emitting files from a compilation. */ 62 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 63 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ 64 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 65 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 66 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 67 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 68 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 69 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 70 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 71 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 72 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 73 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 74 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 75 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 76 | 77 | /* Interop Constraints */ 78 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 79 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 80 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 81 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, 82 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 83 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 84 | 85 | /* Type Checking */ 86 | "strict": true /* Enable all strict type-checking options. */, 87 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 88 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 89 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 90 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 91 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 92 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 93 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 94 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 95 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 96 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 97 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 98 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 99 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 100 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 101 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 102 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 103 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 104 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 105 | 106 | /* Completeness */ 107 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 108 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /vitest.config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | watchExclude: ['__reflink-tests-*'], 6 | watch: false, 7 | testTimeout: 10000, 8 | }, 9 | }); 10 | --------------------------------------------------------------------------------