pax_global_header00006660000000000000000000000064151771606670014532gustar00rootroot0000000000000052 comment=71fff27bc4272c668cf350904030c8b275563c82 lukeed-empathic-ed7dd2e/000077500000000000000000000000001517716066700153535ustar00rootroot00000000000000lukeed-empathic-ed7dd2e/.github/000077500000000000000000000000001517716066700167135ustar00rootroot00000000000000lukeed-empathic-ed7dd2e/.github/workflows/000077500000000000000000000000001517716066700207505ustar00rootroot00000000000000lukeed-empathic-ed7dd2e/.github/workflows/ci.yml000066400000000000000000000056771517716066700221050ustar00rootroot00000000000000name: CI on: pull_request: push: branches: - '**' tags: - 'v**' env: DENO_VERSION: 2.3 jobs: health: name: Health runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: denoland/setup-deno@v2 with: deno-version: ${{ env.DENO_VERSION }} - name: Lint run: deno lint - name: Format run: deno fmt --check - name: Type Check run: deno check src/*.ts node: name: Node.js v${{ matrix.nodejs }} runs-on: ubuntu-latest strategy: matrix: nodejs: [14, 16, 18, 20, 22] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: ${{ matrix.nodejs }} - uses: denoland/setup-deno@v2 with: deno-version: ${{ env.DENO_VERSION }} - name: Prepare run: | deno task fixtures deno task build --quiet deno task pretest --quiet - name: Install working-directory: npm run: npm install - name: Run Tests working-directory: npm run: npm test build: name: Build runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: denoland/setup-deno@v2 with: deno-version: ${{ env.DENO_VERSION }} - name: Check Size run: deno task build bench: name: Bench runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: denoland/setup-deno@v2 with: deno-version: ${{ env.DENO_VERSION }} - name: Make Fixtures run: deno task fixtures 12 - name: Run Benchmark run: deno bench -RE --no-check # https://jsr.io/docs/publishing-packages#publishing-from-github-actions # https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages#publishing-packages-to-the-npm-registry publish: name: Publish needs: [health, node] runs-on: ubuntu-latest if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') permissions: contents: read id-token: write # -> authentication steps: - uses: actions/checkout@v4 - uses: denoland/setup-deno@v2 with: deno-version: ${{ env.DENO_VERSION }} - uses: actions/setup-node@v4 with: node-version: 24 registry-url: 'https://registry.npmjs.org' - name: Build run: deno task build --quiet # - name: "Publish → jsr" # if: ${{ !contains(github.ref, '-next.') }} # run: deno publish --no-check -c jsr.json - name: 'Publish → npm' if: ${{ !contains(github.ref, '-next.') }} run: npm publish --provenance --access public working-directory: ./npm - name: 'Publish → npm (pre-release)' if: ${{ contains(github.ref, '-next.') }} run: npm publish --tag next --provenance --access public working-directory: ./npm lukeed-empathic-ed7dd2e/.gitignore000066400000000000000000000000611517716066700173400ustar00rootroot00000000000000.DS_Store node_modules /coverage fixtures /npm lukeed-empathic-ed7dd2e/benchmarks.md000066400000000000000000000307351517716066700200220ustar00rootroot00000000000000# Benchmarks You may run these benchmarks by installing Deno and running: ```sh $ deno task fixtures # enables access: fs read, env, system (for fs.access) $ deno bench -RES ``` You can also find the benchmark results in the [CI pipeline](https://github.com/lukeed/empathic/actions/workflows/ci.yml), as it runs **all** benchmarks on **every** push and tag. Module size tracking is also logged on every CI run. The results below are taken from my machine with **13 fixtures in each directory**. Please note that GitHub Actions run on different hardware. ``` CPU | Apple M1 Max Runtime | Deno 2.3.6 (aarch64-apple-darwin) ``` A best-effort is made, wherever possible, to ensure fair comparison. ## find ### `find.up` > Look for **one** target in N parent directories.
See [`find.any`](#findany) results for > targetting **multiple** files. _**Walk `6` parent directories before finding the `one` target**_ ``` file:///.../empathic/src/find.bench.ts benchmark time/iter (avg) iter/s (min … max) p75 p99 p995 ------------------------- ----------------------------- --------------------- -------------------------- group level-6 find-up 207.8 µs 4,812 (173.2 µs … 3.7 ms) 208.8 µs 393.1 µs 609.6 µs find-up-simple 156.0 µs 6,409 (137.5 µs … 1.1 ms) 157.8 µs 224.5 µs 241.3 µs find-up-simple (sync) 71.9 µs 13,900 ( 66.3 µs … 291.8 µs) 72.0 µs 84.2 µs 88.9 µs escalade 235.4 µs 4,248 (221.6 µs … 397.5 µs) 238.5 µs 290.9 µs 298.8 µs empathic/find.up (sync) 7.3 µs 137,800 ( 7.1 µs … 7.6 µs) 7.4 µs 7.6 µs 7.6 µs summary empathic/find.up (sync) 9.91x faster than find-up-simple (sync) 21.50x faster than find-up-simple 28.64x faster than find-up 32.44x faster than escalade ``` _**Walk `10` parent directories before finding the `one` target**_ ``` file:///.../empathic/src/find.bench.ts benchmark time/iter (avg) iter/s (min … max) p75 p99 p995 ------------------------- ----------------------------- --------------------- -------------------------- find-up 459.7 µs 2,175 (421.8 µs … 2.0 ms) 461.1 µs 547.8 µs 1.3 ms find-up-simple 374.7 µs 2,669 (340.2 µs … 1.7 ms) 383.1 µs 439.9 µs 458.6 µs find-up-simple (sync) 187.8 µs 5,324 (182.3 µs … 456.3 µs) 188.8 µs 211.2 µs 217.5 µs escalade 511.4 µs 1,955 (438.9 µs … 816.5 µs) 514.9 µs 605.2 µs 702.7 µs empathic/find.up (sync) 15.5 µs 64,410 ( 15.1 µs … 296.1 µs) 15.4 µs 18.1 µs 18.7 µs summary empathic/find.up (sync) 12.10x faster than find-up-simple (sync) 24.13x faster than find-up-simple 29.61x faster than find-up 32.94x faster than escalade ``` _**Walk `15+` parent directories, never finding the `one` target**_ ``` file:///.../empathic/src/find.bench.ts benchmark time/iter (avg) iter/s (min … max) p75 p99 p995 ------------------------- ----------------------------- --------------------- -------------------------- find-up 632.0 µs 1,582 (582.1 µs … 2.7 ms) 630.4 µs 697.7 µs 1.3 ms find-up-simple 507.5 µs 1,970 (465.8 µs … 1.7 ms) 514.1 µs 648.5 µs 733.3 µs find-up-simple (sync) 273.9 µs 3,651 (268.7 µs … 592.2 µs) 274.2 µs 291.3 µs 298.5 µs escalade 862.6 µs 1,159 (811.9 µs … 1.2 ms) 871.3 µs 1.1 ms 1.1 ms empathic/find.up (sync) 20.5 µs 48,860 ( 19.9 µs … 314.6 µs) 20.4 µs 23.0 µs 24.0 µs summary empathic/find.up (sync) 13.38x faster than find-up-simple (sync) 24.80x faster than find-up-simple 30.88x faster than find-up 42.15x faster than escalade ``` ### `find.any` > Look for **any** of the targets listed in N parent directories.
See [`find.up`](#findup) > results for targetting **one** file. _**Walk `6` parent directories before finding **any of** the targets**_ ``` file:///.../empathic/src/locate.bench.ts benchmark time/iter (avg) iter/s (min … max) p75 p99 p995 -------------------------- ----------------------------- --------------------- -------------------------- locate-path 112.8 µs 8,862 ( 83.2 µs … 3.6 ms) 113.2 µs 248.3 µs 277.5 µs locate-path (sync) 105.3 µs 9,493 ( 97.8 µs … 442.1 µs) 105.9 µs 126.2 µs 133.8 µs locate-path (order) 111.6 µs 8,959 ( 84.9 µs … 497.2 µs) 113.8 µs 152.5 µs 315.3 µs find-up 1.8 ms 545 ( 1.7 ms … 2.3 ms) 1.8 ms 2.2 ms 2.2 ms find-up (sync) 1.9 ms 524 ( 1.7 ms … 2.3 ms) 2.0 ms 2.2 ms 2.2 ms escalade 237.1 µs 4,219 (224.3 µs … 416.1 µs) 240.0 µs 287.7 µs 308.3 µs empathic/find.any (sync) 48.4 µs 20,680 ( 47.4 µs … 255.0 µs) 48.1 µs 52.5 µs 55.2 µs summary empathic/find.any (sync) 2.18x faster than locate-path (sync) 2.31x faster than locate-path (order) 2.33x faster than locate-path 4.90x faster than escalade 37.89x faster than find-up 39.41x faster than find-up (sync) ``` _**Walk `10` parent directories before finding **any of** the targets**_ ``` file:///.../empathic/src/locate.bench.ts benchmark time/iter (avg) iter/s (min … max) p75 p99 p995 -------------------------- ----------------------------- --------------------- -------------------------- locate-path 111.6 µs 8,960 ( 85.9 µs … 785.8 µs) 113.6 µs 148.4 µs 158.7 µs locate-path (sync) 112.8 µs 8,865 (103.8 µs … 676.8 µs) 114.6 µs 132.0 µs 139.1 µs locate-path (order) 111.1 µs 9,004 ( 87.0 µs … 776.6 µs) 113.5 µs 141.8 µs 151.1 µs find-up 1.9 ms 539 ( 1.7 ms … 2.7 ms) 1.9 ms 2.5 ms 2.6 ms find-up (sync) 2.1 ms 478 ( 2.0 ms … 2.8 ms) 2.1 ms 2.3 ms 2.5 ms escalade 512.7 µs 1,951 (490.3 µs … 742.4 µs) 518.1 µs 581.1 µs 642.0 µs empathic/find.any (sync) 105.1 µs 9,514 (103.3 µs … 414.6 µs) 104.6 µs 111.8 µs 116.3 µs summary empathic/find.any (sync) 1.06x faster than locate-path (order) 1.06x faster than locate-path 1.07x faster than locate-path (sync) 4.88x faster than escalade 17.64x faster than find-up 19.89x faster than find-up (sync) ``` _**Walk `15+` parent directories, never finding **any of** the targets**_ ``` file:///.../empathic/src/locate.bench.ts benchmark time/iter (avg) iter/s (min … max) p75 p99 p995 -------------------------- ----------------------------- --------------------- -------------------------- locate-path 111.0 µs 9,013 ( 87.8 µs … 797.2 µs) 112.9 µs 147.1 µs 156.2 µs locate-path (sync) 118.0 µs 8,473 (114.6 µs … 877.2 µs) 118.2 µs 128.0 µs 132.4 µs locate-path (order) 110.9 µs 9,021 ( 87.2 µs … 877.8 µs) 113.3 µs 143.3 µs 152.8 µs find-up 1.9 ms 519 ( 1.7 ms … 6.4 ms) 1.9 ms 3.0 ms 3.2 ms find-up (sync) 2.3 ms 439 ( 2.2 ms … 3.0 ms) 2.3 ms 2.7 ms 3.0 ms escalade 997.4 µs 1,003 (834.0 µs … 4.2 ms) 1.0 ms 2.3 ms 3.0 ms empathic/find.any (sync) 135.4 µs 7,384 (131.0 µs … 410.4 µs) 134.9 µs 173.9 µs 202.0 µs summary locate-path (order) 1.00x faster than locate-path 1.06x faster than locate-path (sync) 1.22x faster than empathic/find.any (sync) 9.00x faster than escalade 17.37x faster than find-up 20.53x faster than find-up (sync) ``` ## package ### `package.up` _**Find the nearest `package.json` file**_ ``` file:///.../empathic/src/package.bench.ts benchmark time/iter (avg) iter/s (min … max) p75 p99 p995 ---------------------------- ----------------------------- --------------------- -------------------------- package-up 286.7 µs 3,488 (257.5 µs … 628.8 µs) 290.9 µs 347.4 µs 362.0 µs package-up (sync) 134.8 µs 7,417 (123.4 µs … 242.3 µs) 135.0 µs 164.3 µs 170.0 µs pkg-up 360.0 µs 2,778 (319.7 µs … 3.9 ms) 359.3 µs 693.2 µs 871.6 µs pkg-up (sync) 156.0 µs 6,409 (139.2 µs … 731.4 µs) 158.1 µs 175.6 µs 179.8 µs empathic/package.up (sync) 12.5 µs 80,310 ( 12.1 µs … 216.2 µs) 12.4 µs 13.7 µs 15.2 µs summary empathic/package.up (sync) 10.83x faster than package-up (sync) 12.53x faster than pkg-up (sync) 23.02x faster than package-up 28.91x faster than pkg-up ``` _**Get the directory path of the closest `package.json` file**_ > **Note:** `empathic/package.up` passes its output to > [`path.dirname()`](https://nodejs.org/docs/latest/api/path.html#pathdirnamepath) for equality. ``` file:///.../empathic/src/package.bench.ts benchmark time/iter (avg) iter/s (min … max) p75 p99 p995 ---------------------------- ----------------------------- --------------------- -------------------------- pkg-dir 281.3 µs 3,556 (257.5 µs … 466.2 µs) 286.5 µs 317.8 µs 337.8 µs pkg-dir (sync) 137.5 µs 7,272 (128.9 µs … 296.8 µs) 138.2 µs 152.2 µs 160.8 µs empathic/package.up (sync) 12.5 µs 80,220 ( 12.1 µs … 218.5 µs) 12.4 µs 14.0 µs 15.1 µs summary empathic/package.up (sync) 11.03x faster than pkg-dir (sync) 22.56x faster than pkg-dir ``` ### `package.cache` _**Construct a `node_modules/.cache/` directory for a package**_ ``` file:///.../empathic/src/package.bench.ts benchmark time/iter (avg) iter/s (min … max) p75 p99 p995 ---------------------------- ----------------------------- --------------------- -------------------------- find-cache-dir 192.7 µs 5,190 (182.3 µs … 367.0 µs) 194.3 µs 207.9 µs 218.9 µs empathic/package.cache 17.6 µs 56,920 ( 17.1 µs … 237.6 µs) 17.5 µs 20.1 µs 20.4 µs summary empathic/package.cache 10.97x faster than find-cache-dir ``` ## resolve ### `resolve.from` > Emulate `require.resolve` from a starting directory ``` file:///.../empathic/src/resolve.bench.ts benchmark time/iter (avg) iter/s (min … max) p75 p99 p995 ----------------------- ----------------------------- --------------------- -------------------------- resolve-from 36.9 µs 27,130 ( 35.6 µs … 213.0 µs) 36.8 µs 42.3 µs 46.1 µs empathic/resolve.from 27.7 µs 36,100 ( 26.5 µs … 275.9 µs) 27.7 µs 31.5 µs 35.0 µs summary empathic/resolve.from 1.33x faster than resolve-from ``` ### `resolve.cwd` > Emulate `require.resolve` from the current working directory ``` file:///.../empathic/src/resolve.bench.ts benchmark time/iter (avg) iter/s (min … max) p75 p99 p995 ----------------------- ----------------------------- --------------------- -------------------------- resolve-cwd 36.8 µs 27,160 ( 35.9 µs … 161.5 µs) 36.8 µs 41.5 µs 44.3 µs empathic/resolve.cwd 32.2 µs 31,030 ( 31.3 µs … 205.8 µs) 32.2 µs 35.8 µs 38.5 µs summary empathic/resolve.cwd 1.14x faster than resolve-cwd ``` ## walk ``` file:///.../empathic/src/walk.bench.ts benchmark time/iter (avg) iter/s (min … max) p75 p99 p995 ---------------------- ----------------------------- --------------------- -------------------------- empathic/walk.up 8.0 µs 125,300 ( 7.5 µs … 61.5 µs) 7.8 µs 9.4 µs 33.3 µs ``` lukeed-empathic-ed7dd2e/deno.json000066400000000000000000000013131517716066700171710ustar00rootroot00000000000000{ "version": "0.0.0", "nodeModulesDir": "auto", "tasks": { "build": "deno run -A scripts/build.ts", "pretest": "deno run -A scripts/pretest.ts", "fixtures": "deno run -RW scripts/fixtures.ts" }, "imports": { "empathic/access": "./src/access.ts", "empathic/find": "./src/find.ts", "empathic/package": "./src/package.ts", "empathic/resolve": "./src/resolve.ts", "empathic/walk": "./src/walk.ts" }, "lint": { "rules": { "exclude": [ "no-var", "prefer-const", "no-cond-assign", "no-extra-boolean-cast", "no-inner-declarations" ] } }, "fmt": { "lineWidth": 100, "singleQuote": true, "useTabs": true }, "exclude": [ "coverage", "readme.md", "npm" ] } lukeed-empathic-ed7dd2e/deno.lock000066400000000000000000000410471517716066700171600ustar00rootroot00000000000000{ "version": "5", "specifiers": { "npm:@types/node@*": "22.15.15", "npm:astray@1.1.1": "1.1.1", "npm:magic-string@0.30.17": "0.30.17", "npm:oxc-minify@0.73.2": "0.73.2", "npm:oxc-parser@0.73.2": "0.73.2", "npm:oxc-transform@0.73.2": "0.73.2", "npm:uvu@0.5": "0.5.6" }, "npm": { "@emnapi/core@1.4.3": { "integrity": "sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==", "dependencies": [ "@emnapi/wasi-threads", "tslib" ] }, "@emnapi/runtime@1.4.3": { "integrity": "sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==", "dependencies": [ "tslib" ] }, "@emnapi/wasi-threads@1.0.2": { "integrity": "sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==", "dependencies": [ "tslib" ] }, "@jridgewell/sourcemap-codec@1.5.0": { "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" }, "@napi-rs/wasm-runtime@0.2.11": { "integrity": "sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==", "dependencies": [ "@emnapi/core", "@emnapi/runtime", "@tybys/wasm-util" ] }, "@oxc-minify/binding-android-arm64@0.73.2": { "integrity": "sha512-2T5s2Q3haRSjXcnfr+g9yo4jxLB5yfwVIgGKaPBjg8hVcTwElEbC7QfPfjMIDwtpsoUmkKSAGyMi5nzPpLZX4A==", "os": ["android"], "cpu": ["arm64"] }, "@oxc-minify/binding-darwin-arm64@0.73.2": { "integrity": "sha512-TbizFhFWS7IEAk0QULswANog2aTE5jzhJMZX7++pZYcdUscn0z/o++ui22/4LuHOrQum9aRCDhanxiQMF58ycw==", "os": ["darwin"], "cpu": ["arm64"] }, "@oxc-minify/binding-darwin-x64@0.73.2": { "integrity": "sha512-pIqTivHtgGYrLBg5oQWQPBg3yB49Z7iawvb1flK4jU8+QtEX/+2s6fNLDQSixvikpZLiqY+TUPPqkZBlhKt7xw==", "os": ["darwin"], "cpu": ["x64"] }, "@oxc-minify/binding-freebsd-x64@0.73.2": { "integrity": "sha512-JkOEBeeXPehmPYAShUMh2lzRl90BSRhGX5AIam7XUMtwWYc239CshG0ZOW0PyfMbf+3FpF6ogIIWKmgt+dKheQ==", "os": ["freebsd"], "cpu": ["x64"] }, "@oxc-minify/binding-linux-arm-gnueabihf@0.73.2": { "integrity": "sha512-LADCKhMSmM0si9UXKTYH6G7GywykTADArK2eYEMFdDGzIAZvILM5Vz4ejChrrzmtXGucRqrOzshlkzHsqQJaGg==", "os": ["linux"], "cpu": ["arm"] }, "@oxc-minify/binding-linux-arm-musleabihf@0.73.2": { "integrity": "sha512-+6fodtXCMyqGDHpPVjhoDOAmqysRYP8PKaq4wdhS+r06sOrIWD2PIPQcw9dN351kqZMYN3djKLuH1S0+cCYFhg==", "os": ["linux"], "cpu": ["arm"] }, "@oxc-minify/binding-linux-arm64-gnu@0.73.2": { "integrity": "sha512-crTntm9OctKZ+Fc1nHKe+PZHMe0rWzgWelBOrUUwf8tDrj/ANEvAV90lN+W8u6+8p5Fo+s7NoAO7huTi6Hz0+A==", "os": ["linux"], "cpu": ["arm64"] }, "@oxc-minify/binding-linux-arm64-musl@0.73.2": { "integrity": "sha512-inyWVkX/KYTvglSYMC1mZZmxiM9ITSKxBDsWxHGHIdoaZeLyHuAmfyGFOZV20dx4zYRRmzMGo0FJPdaNQ1R2KA==", "os": ["linux"], "cpu": ["arm64"] }, "@oxc-minify/binding-linux-riscv64-gnu@0.73.2": { "integrity": "sha512-fOKtNvq6B3qDnXSfmQ1XeCX/CuJFkQ70xQ/otJuIVpD7ZHeZ+0smacKamsMd1iNOlDrfGFKDPUbKEqSliN32kQ==", "os": ["linux"], "cpu": ["riscv64"] }, "@oxc-minify/binding-linux-s390x-gnu@0.73.2": { "integrity": "sha512-uy/w2/2DAg9jv3i4NNkcICHatff32RMiPCsV8jx9z+dQ38Zvb4EGsCuz5+3OIOab4bs5y+gxO4ki9Nbrn4b7Tg==", "os": ["linux"], "cpu": ["s390x"] }, "@oxc-minify/binding-linux-x64-gnu@0.73.2": { "integrity": "sha512-gIPgQZXE0ABfi9Q9yUl5o21E1md3RcHBe4+QMkBHpeaf03hf2RHLSGRydktKAUR2EjaPxF4UpNK8GKmzohfWqA==", "os": ["linux"], "cpu": ["x64"] }, "@oxc-minify/binding-linux-x64-musl@0.73.2": { "integrity": "sha512-cikAwZJ45d4hD5bdPc4mgzPugrMrdgGMhZaGpvBexcB9fiu+80eDWb75GlobhIj6jOxvBEAIA/1ZJdxFXqcIuA==", "os": ["linux"], "cpu": ["x64"] }, "@oxc-minify/binding-wasm32-wasi@0.73.2": { "integrity": "sha512-kArdt/L8heYLP/nhLhW6hd3JLrtJmiFqQACb1jL6gwZqjk9JssGlLWE8GMN6nmCL9ca2p1RqEkzI0Z1jz869eQ==", "dependencies": [ "@napi-rs/wasm-runtime" ], "cpu": ["wasm32"] }, "@oxc-minify/binding-win32-arm64-msvc@0.73.2": { "integrity": "sha512-Djuc5V7TpHQu1i7MoQmV+BCy1JvoKt+jiTPuNXswOPB2DrnDdVLwmN9zpXbcPVkorUSBN1Ccb04KfBsLL+1kXg==", "os": ["win32"], "cpu": ["arm64"] }, "@oxc-minify/binding-win32-x64-msvc@0.73.2": { "integrity": "sha512-nDYjhGO+d2bHrWLwOLtZqOpSz+UJxYFkCPAZg2kyXPsmgImao+c9V4IuOggayDUjqPuL5+SklkW2lLQrKYMu/w==", "os": ["win32"], "cpu": ["x64"] }, "@oxc-parser/binding-android-arm64@0.73.2": { "integrity": "sha512-YkO/7t7yshWtHCWxd5oADgXBU8Dput6UFtPULSUXgwEbim8dZvMDc7T8vCcEmtOEHOJBcJRHuK1fuKXJnwU5Aw==", "os": ["android"], "cpu": ["arm64"] }, "@oxc-parser/binding-darwin-arm64@0.73.2": { "integrity": "sha512-z5E/dcecYUvh3o6pHz8qGGnqY6ICTk0GL1vvDI40cKOig70QTGOM837z/+m2tyjjkny06QlxJKQql97LV+dJIg==", "os": ["darwin"], "cpu": ["arm64"] }, "@oxc-parser/binding-darwin-x64@0.73.2": { "integrity": "sha512-uzmLaxRyiNW/YFRI/NIkVxktP4fqvdwPvwICVDHceXj9MiRYQKvQLD/amZY3FjYcUxuZyWd88MnNfGaO4fPufw==", "os": ["darwin"], "cpu": ["x64"] }, "@oxc-parser/binding-freebsd-x64@0.73.2": { "integrity": "sha512-8i5qqv38UKo+2uloxC2SAsTrNdsoJitiUEXCm2Bu/4N/ZmIe+uM4DBTJgT9pR3eWTCszqCeipQ4LkB6CONtp7w==", "os": ["freebsd"], "cpu": ["x64"] }, "@oxc-parser/binding-linux-arm-gnueabihf@0.73.2": { "integrity": "sha512-Vrd+B+BMu9JsdG/+BNXCK5OVuSQbowxym0wz60jSeNIFFEQcZmo35B+OKDEhVrNlWH6GijodbBopq5cKv6n9og==", "os": ["linux"], "cpu": ["arm"] }, "@oxc-parser/binding-linux-arm-musleabihf@0.73.2": { "integrity": "sha512-eMQKxZTqO0udYOqHCM33cZg8wlg8pgD8CTJL4udwGkRJGKuTy6rIAgnQ4xJxHBbjcrVOBMW8MZKJopyKKIUdgg==", "os": ["linux"], "cpu": ["arm"] }, "@oxc-parser/binding-linux-arm64-gnu@0.73.2": { "integrity": "sha512-OPFujKAe8Gl59WHp7FVFgOkAe6mC9v1Xd7c8cwH+aJr/Bur5wDeNm3bjmZ4Z9bmUZGPe4tLBYtVcSFuTK2C04g==", "os": ["linux"], "cpu": ["arm64"] }, "@oxc-parser/binding-linux-arm64-musl@0.73.2": { "integrity": "sha512-5iR58UQEy7t71T8jxU1Ss9DLZQ6me/0TIumOAAO+8w48Zj3DfUfqC5FyXNELUGEATsRUsrR/+4YKpy2fppdxHQ==", "os": ["linux"], "cpu": ["arm64"] }, "@oxc-parser/binding-linux-riscv64-gnu@0.73.2": { "integrity": "sha512-k4jBa99BM2NHoQnSVZwF+MwDzH1TXU97MWtpq7RMg7IhLtnZYxlsHWxctAbmij5C41gn7eCGtCv/8shCoYbslg==", "os": ["linux"], "cpu": ["riscv64"] }, "@oxc-parser/binding-linux-s390x-gnu@0.73.2": { "integrity": "sha512-gx7wQMmAP5YbWlv4qPnzgnyJ+kQkZ8OlP0mRZ9rOvMDMu0BOP42Q0iOGxqEbStW47aSCw4JVNgNk9oPKdEABmg==", "os": ["linux"], "cpu": ["s390x"] }, "@oxc-parser/binding-linux-x64-gnu@0.73.2": { "integrity": "sha512-hmREwjiRQOnOsMzq325I1901pnStfVZh3O8vAqPetMzPlOM+RA3BofipoBERblYFYwWDmixb4yUOv/FaxFjhlQ==", "os": ["linux"], "cpu": ["x64"] }, "@oxc-parser/binding-linux-x64-musl@0.73.2": { "integrity": "sha512-jMbryKAJiXXA0OKLZpYRp7fcUyLzK1sBgJCHinVWM0JvGrM7D4V75HpGtOJUqZNUr6Y9rs14FgdKD4O0adZ9iA==", "os": ["linux"], "cpu": ["x64"] }, "@oxc-parser/binding-wasm32-wasi@0.73.2": { "integrity": "sha512-6sb52wCUT/gfh9UWyWbJ1dS5GKu4ATTcdgdKl+osUTHv7Xgv+ZPkw1VPXvNLCVgR22XKXWzPnNvyuFPMwAh3Wg==", "dependencies": [ "@napi-rs/wasm-runtime" ], "cpu": ["wasm32"] }, "@oxc-parser/binding-win32-arm64-msvc@0.73.2": { "integrity": "sha512-d0ldjCMMCDpp9q2wKyQieajR4rENjfZsr+8xoWImU+tRBLVVBACW9N3c+xtXm/XO1bn05fPk5/USBCLJs1V8eg==", "os": ["win32"], "cpu": ["arm64"] }, "@oxc-parser/binding-win32-x64-msvc@0.73.2": { "integrity": "sha512-7vOE4af8hSvgo0OOls8Qv3nqPhREbi3t/6GF3PiMK3m1CWWoLWtffJ9epKAtxmBJAfx23x9y5LzYu9xnVv+0Rw==", "os": ["win32"], "cpu": ["x64"] }, "@oxc-project/types@0.73.2": { "integrity": "sha512-kU2FjfCb9VTNg/KbOTKVi2sYrKTkNQYq1Fi1v1jCKjbUGA9wqkNDqijNBLeDwagFtDuK2EIWvTzNDYU4k/918g==" }, "@oxc-transform/binding-android-arm64@0.73.2": { "integrity": "sha512-/MBOCFyYeFh7xofbrCor7B4PIrO1Epdxqmoxdkr21jJm/EkGf9jtQp6U7B9/85sxT9zh6bjh5mKdhuUNmK0sAQ==", "os": ["android"], "cpu": ["arm64"] }, "@oxc-transform/binding-darwin-arm64@0.73.2": { "integrity": "sha512-0eH2W83LfPIaWz4ta2uu6ZZm4vS1d3PFaucBl00CZg25A9FKdVIfppdXpzlMO1MhBj4aNBG4H6vjfASqcy5mxw==", "os": ["darwin"], "cpu": ["arm64"] }, "@oxc-transform/binding-darwin-x64@0.73.2": { "integrity": "sha512-jSSKouORteFZvfjxL+j7Yp+W5UDHocbgQwUoJSJgO7HRCq2umKBa7vh68ajpRRpdPpETvitGr1eRhlg0PLSsqg==", "os": ["darwin"], "cpu": ["x64"] }, "@oxc-transform/binding-freebsd-x64@0.73.2": { "integrity": "sha512-/adQ4BAPZq6JcLBAcD9wOrHFWwc+uOvY2RQkzbeiewijhjWMYGXXH0zYi6gVDVD3kn9yjDnulfcrCPdU5l16Zg==", "os": ["freebsd"], "cpu": ["x64"] }, "@oxc-transform/binding-linux-arm-gnueabihf@0.73.2": { "integrity": "sha512-6nuDMlPOT+hLCtqUcgwryarDP8ssqz9VJqLArsMTAPfd0ewsWH2KL9Tv0E5aRPw9H8ly8DIczKe2nD7GQB9duA==", "os": ["linux"], "cpu": ["arm"] }, "@oxc-transform/binding-linux-arm-musleabihf@0.73.2": { "integrity": "sha512-k8vWoT/lE7UGZ4kkV5aYWae6/NFve7UDFZ87/h8BQk5i8emCo1DTzWBjQWpxAK0RuF14G9MxNQwEyuTh9nyR6A==", "os": ["linux"], "cpu": ["arm"] }, "@oxc-transform/binding-linux-arm64-gnu@0.73.2": { "integrity": "sha512-A3f3ZTos0JDbjjB+S3m4QGQzvBbaNJhvNOs0Et/d0RzapiadWUAAe+b7Rn8E/zX46PN2ED1DjmVuricLnRwF0g==", "os": ["linux"], "cpu": ["arm64"] }, "@oxc-transform/binding-linux-arm64-musl@0.73.2": { "integrity": "sha512-bDTfBx8qWCFqRSkRu2jlWecypzFJ/upGVZCT0VgHlGrIC/xCm78+Q4ev13xnrS/xOatxYvXZS0nTvWwLkQb7qw==", "os": ["linux"], "cpu": ["arm64"] }, "@oxc-transform/binding-linux-riscv64-gnu@0.73.2": { "integrity": "sha512-ddTc5wUSTCeUJkOxioKneli2Mm8BkkjAlLoFAPOLyqle61EbEvDnuj3Kq5eBfYs0zPj2FV/jR+w6zfN0O/9FfQ==", "os": ["linux"], "cpu": ["riscv64"] }, "@oxc-transform/binding-linux-s390x-gnu@0.73.2": { "integrity": "sha512-ZQPzot8ZvIUU86N1spr8AkwaqhtcIHMld9BB/lWXXuUU8Bx+07enngo2n52mcxHKveFa1770tXf2DrW3jiwCTw==", "os": ["linux"], "cpu": ["s390x"] }, "@oxc-transform/binding-linux-x64-gnu@0.73.2": { "integrity": "sha512-s0syo2tWOW0sWz8CLhE8QRzZf1BZbaUOy+EJHmvvutYwtiTwmGsivmPGKntSRvJ1asv+uKEF+jqc+VPV/QytBw==", "os": ["linux"], "cpu": ["x64"] }, "@oxc-transform/binding-linux-x64-musl@0.73.2": { "integrity": "sha512-S3UrjcZNAVWX89x2QwwX86IKfdKXsNdbbqKb8vlPtn3mX4YM8pP4WNTsKyKIUM2iDNxZPNeSZ86USjGzm4aRlA==", "os": ["linux"], "cpu": ["x64"] }, "@oxc-transform/binding-wasm32-wasi@0.73.2": { "integrity": "sha512-Vq9gk1MC/XkAGGoH5E5qrh4csA1NZ8O4JiirkRVPzf9EbbhB98OW/XqUggrJ7LGKh5SdVzWZslx/FG/E/T+AqQ==", "dependencies": [ "@napi-rs/wasm-runtime" ], "cpu": ["wasm32"] }, "@oxc-transform/binding-win32-arm64-msvc@0.73.2": { "integrity": "sha512-YmwAcE3HUPA2Gw9KjAKXp4yvHslL23SJYXaPsrmCdTuvO1SDRgaZ1rmg1aIpBw4Yttt2z5FeFlnb1GLgOXVtjA==", "os": ["win32"], "cpu": ["arm64"] }, "@oxc-transform/binding-win32-x64-msvc@0.73.2": { "integrity": "sha512-eRHMCCw3ehWcT8mcCqkNUfsx4G5L6rQff7o0pOl4jDNH8HajMXKQVoladyd1WGITJMxSyCWxAjS8SonniLcdHQ==", "os": ["win32"], "cpu": ["x64"] }, "@tybys/wasm-util@0.9.0": { "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", "dependencies": [ "tslib" ] }, "@types/estree@0.0.45": { "integrity": "sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g==" }, "@types/node@22.15.15": { "integrity": "sha512-R5muMcZob3/Jjchn5LcO8jdKwSCbzqmPB6ruBxMcf9kbxtniZHP327s6C37iOfuw8mbKK3cAQa7sEl7afLrQ8A==", "dependencies": [ "undici-types" ] }, "astray@1.1.1": { "integrity": "sha512-LizvbENqdc8tdvrms/YyYoTtlr43INJni4YZSFr8nNdfOgafi82Hcrfhjm0MdwLhRFBrDhRwtH/0fnntlESxsQ==", "optionalDependencies": [ "@types/estree" ] }, "dequal@2.0.3": { "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==" }, "diff@5.2.0": { "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==" }, "kleur@4.1.5": { "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==" }, "magic-string@0.30.17": { "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dependencies": [ "@jridgewell/sourcemap-codec" ] }, "mri@1.2.0": { "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==" }, "oxc-minify@0.73.2": { "integrity": "sha512-i3cmOHtK5D/Q6om5VNBXaxBKYCNhW2ouc+IuC4FrITMgTXnJMSn1/L5g5JHxlkhuXGXqL7rIOtn287dQo9g+sg==", "optionalDependencies": [ "@oxc-minify/binding-android-arm64", "@oxc-minify/binding-darwin-arm64", "@oxc-minify/binding-darwin-x64", "@oxc-minify/binding-freebsd-x64", "@oxc-minify/binding-linux-arm-gnueabihf", "@oxc-minify/binding-linux-arm-musleabihf", "@oxc-minify/binding-linux-arm64-gnu", "@oxc-minify/binding-linux-arm64-musl", "@oxc-minify/binding-linux-riscv64-gnu", "@oxc-minify/binding-linux-s390x-gnu", "@oxc-minify/binding-linux-x64-gnu", "@oxc-minify/binding-linux-x64-musl", "@oxc-minify/binding-wasm32-wasi", "@oxc-minify/binding-win32-arm64-msvc", "@oxc-minify/binding-win32-x64-msvc" ] }, "oxc-parser@0.73.2": { "integrity": "sha512-Gu4S3s2GChWsT5FgmjfdRZirR5c+ixxvm/ZH11XlV+2009kHHoYkyK1Ab9DkEoI6mL630NEyBjI28I0DscJrWQ==", "dependencies": [ "@oxc-project/types" ], "optionalDependencies": [ "@oxc-parser/binding-android-arm64", "@oxc-parser/binding-darwin-arm64", "@oxc-parser/binding-darwin-x64", "@oxc-parser/binding-freebsd-x64", "@oxc-parser/binding-linux-arm-gnueabihf", "@oxc-parser/binding-linux-arm-musleabihf", "@oxc-parser/binding-linux-arm64-gnu", "@oxc-parser/binding-linux-arm64-musl", "@oxc-parser/binding-linux-riscv64-gnu", "@oxc-parser/binding-linux-s390x-gnu", "@oxc-parser/binding-linux-x64-gnu", "@oxc-parser/binding-linux-x64-musl", "@oxc-parser/binding-wasm32-wasi", "@oxc-parser/binding-win32-arm64-msvc", "@oxc-parser/binding-win32-x64-msvc" ] }, "oxc-transform@0.73.2": { "integrity": "sha512-4FR/u5u5ZmSeVPWEVzEXaKf3zskGd2nBc9ArxzCwUZfPTpxqU/FoPDYaYOeAi08DWOiP04I8txJqqDvvRjASNw==", "optionalDependencies": [ "@oxc-transform/binding-android-arm64", "@oxc-transform/binding-darwin-arm64", "@oxc-transform/binding-darwin-x64", "@oxc-transform/binding-freebsd-x64", "@oxc-transform/binding-linux-arm-gnueabihf", "@oxc-transform/binding-linux-arm-musleabihf", "@oxc-transform/binding-linux-arm64-gnu", "@oxc-transform/binding-linux-arm64-musl", "@oxc-transform/binding-linux-riscv64-gnu", "@oxc-transform/binding-linux-s390x-gnu", "@oxc-transform/binding-linux-x64-gnu", "@oxc-transform/binding-linux-x64-musl", "@oxc-transform/binding-wasm32-wasi", "@oxc-transform/binding-win32-arm64-msvc", "@oxc-transform/binding-win32-x64-msvc" ] }, "sade@1.8.1": { "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", "dependencies": [ "mri" ] }, "tslib@2.8.1": { "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, "undici-types@6.21.0": { "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" }, "uvu@0.5.6": { "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==", "dependencies": [ "dequal", "diff", "kleur", "sade" ], "bin": true } }, "workspace": { "packageJson": { "dependencies": [ "npm:uvu@0.5" ] } } } lukeed-empathic-ed7dd2e/license000066400000000000000000000021201517716066700167130ustar00rootroot00000000000000MIT License Copyright (c) Luke Edwards (lukeed.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. lukeed-empathic-ed7dd2e/package.json000066400000000000000000000015261517716066700176450ustar00rootroot00000000000000{ "name": "empathic", "version": "2.0.1", "repository": "lukeed/empathic", "description": "A set of small and fast Node.js utilities to understand your pathing needs.", "license": "MIT", "author": { "name": "Luke Edwards", "email": "luke.edwards05@gmail.com", "url": "https://lukeed.com" }, "exports": { "./access": { "import": "./access.mjs", "require": "./access.js" }, "./find": { "import": "./find.mjs", "require": "./find.js" }, "./package": { "import": "./package.mjs", "require": "./package.js" }, "./resolve": { "import": "./resolve.mjs", "require": "./resolve.js" }, "./walk": { "import": "./walk.mjs", "require": "./walk.js" }, "./package.json": "./package.json" }, "scripts": { "test": "uvu" }, "engines": { "node": ">=14" }, "devDependencies": { "uvu": "0.5" } } lukeed-empathic-ed7dd2e/readme.md000066400000000000000000000044401517716066700171340ustar00rootroot00000000000000# empathic [![CI](https://github.com/lukeed/empathic/workflows/CI/badge.svg)](https://github.com/lukeed/empathic/actions?query=workflow%3ACI) [![licenses](https://licenses.dev/b/npm/empathic)](https://licenses.dev/npm/empathic) > A set of small and [fast](/benchmarks.md) Node.js utilities to understand your pathing needs. Multiple submodules (eg, `empathic/find`) are offered, _each of which_ are: * **fast** — 8x to 40x faster than popular alternatives * **modern** — based on newer `node:*` native APIs * **small** — ranging from 200b to 500b in size * **safe** — zero-dependency & easy to read ## Install ```sh $ npm install empathic ``` ## Usage ```ts import { resolve } from 'node:path'; import * as find from 'empathic/find'; import * as pkg from 'empathic/package'; // Assumed example structure: let cwd = resolve('path/to/acme/websites/dashboard'); // Find closest "foobar.config.js" file let file = find.up('foobar.config.js', { cwd }); //=> "/.../path/to/acme/foobar.config.js" // Find closest "package.json" file let pkgfile = pkg.up({ cwd }); //=> "/.../path/to/acme/package.json" // Construct (optionally create) "foobar" cache dir let cache = pkg.cache('foobar', { cwd, create: true }); //=> "/.../path/to/acme/node_modules/.cache/foobar" ``` ## API ### `empathic/access` > [Source](/src/access.ts) · **Size:** `259b` Check for file access/permissions. Named [`fs.accessSync`](https://nodejs.org/docs/latest/api/fs.html#fsaccesssyncpath-mode) shortcuts. ### `empathic/find` > [Source](/src/find.ts) · [Benchmark](/benchmarks.md#find) · **Size:** `569b` Find files and/or directories by walking up parent directories. ### `empathic/package` > [Source](/src/package.ts) · [Benchmark](/benchmarks.md#package) · **Size:** `505b` Convenience helpers for dealing with `package.json` files and/or `node_modules` packages. ### `empathic/resolve` > [Source](/src/resolve.ts) · [Benchmark](/benchmarks.md#resolve) · **Size:** `419b` Resolve absolute paths to package identifiers, relative paths, file URL, and/or from other root directories. ### `empathic/walk` > [Source](/src/walk.ts) · [Benchmark](/benchmarks.md#walk) · **Size:** `208b` Collect all the parent directories of a target. Controlled via `cwd` and `last` options. ## License MIT © [Luke Edwards](https://lukeed.com) lukeed-empathic-ed7dd2e/scripts/000077500000000000000000000000001517716066700170425ustar00rootroot00000000000000lukeed-empathic-ed7dd2e/scripts/build.ts000066400000000000000000000050101517716066700205050ustar00rootroot00000000000000// Publish: // $ npm version // $ git push origin main --tags // #-> CI builds w/ publish import { existsSync } from 'node:fs'; import { join, resolve } from 'node:path'; import oxc from 'npm:oxc-transform@0.73.2'; import { minify } from 'npm:oxc-minify@0.73.2'; import * as CommonJS from './commonjs.ts'; const Quiet = Deno.args.includes('--quiet'); const src = resolve('src'); const outdir = resolve('npm'); const Inputs = [ 'access.ts', 'find.ts', 'package.ts', 'resolve.ts', 'walk.ts', ]; function log(...args: unknown[]) { Quiet || console.log(...args); } function bail(label: string, errors: string[]): never { console.error('[%s] error(s)\n', label, errors.join('')); Deno.exit(1); } function copy(file: string) { if (existsSync(file)) { log('> writing "%s" file', file); return Deno.copyFile(file, join(outdir, file)); } } async function transform(filename: string) { let entry = join(src, filename); let source = await Deno.readTextFile(entry); let xform = oxc.transform(entry, source, { lang: 'ts', target: 'node16', sourceType: 'module', typescript: { onlyRemoveTypeImports: true, declaration: { stripInternal: true, }, }, }); if (xform.errors.length > 0) { bail('transform', xform.errors.map((err) => err.message)); } let rgx = /\.tsx?$/; let cjs = filename.replace(rgx, '.js'); let esm = filename.replace(rgx, '.mjs'); let dtsCjs = filename.replace(rgx, '.d.ts'); let dtsEsm = filename.replace(rgx, '.d.mts'); let outfile = join(outdir, dtsCjs); log('> writing "%s" file', dtsCjs); await Deno.writeTextFile(outfile, xform.declaration!); outfile = join(outdir, dtsEsm); log('> writing "%s" file', dtsEsm); await Deno.writeTextFile(outfile, `export * from './${cjs}';\n`); outfile = join(outdir, esm); log('> writing "%s" file', esm); await Deno.writeTextFile(outfile, xform.code); // esm -> cjs outfile = join(outdir, cjs); log('> writing "%s" file', cjs); let tmp = CommonJS.transform(xform.code); await Deno.writeTextFile(outfile, tmp); try { let min = minify(esm, xform.code, { mangle: { toplevel: true }, }); if (!min.code) throw new Error('minify failed'); log('::notice::%s (%d b)', esm, min.code.length); } catch (err) { bail('minify', [(err as Error).message]); } } if (existsSync(outdir)) { log('! removing "npm" directory'); await Deno.remove(outdir, { recursive: true }); } await Deno.mkdir(outdir); for (let name of Inputs) { await transform(name); } await copy('package.json'); await copy('readme.md'); await copy('license'); lukeed-empathic-ed7dd2e/scripts/commonjs.ts000066400000000000000000000044431517716066700212440ustar00rootroot00000000000000import MagicString from 'npm:magic-string@0.30.17'; import { type Function, parseSync } from 'npm:oxc-parser@0.73.2'; import { walk } from 'npm:astray@1.1.1'; type Location = { start: number; end: number; }; export function transform(esm: string) { let s = new MagicString(esm); let ast = parseSync('file.js', esm).program; walk(ast.body, { ImportDeclaration(n) { let { start, end } = n as unknown as Location; let src = n.source as unknown as { type: 'StringLiteral'; value: string; start: number; end: number; }; let from = src.value; // NOTE: remove "node:" for Node < 14 // https://nodejs.org/api/esm.html#node-imports // if (from.startsWith('node:')) { // from = from.substring(5); // } let $locals: string[] = []; let $default: string | undefined; let i = 0, arr = n.specifiers; let tmp: typeof arr[number]; for (; i < arr.length; i++) { tmp = arr[i]; switch (tmp.type) { case 'ImportDefaultSpecifier': case 'ImportNamespaceSpecifier': { if ($default) throw new Error('Double `default` exports!'); $default = tmp.local.name; break; } case 'ImportSpecifier': { let { imported, local } = tmp; if (imported.name !== local.name) { $locals.push(`${imported.name}: ${local.name}`); } else { $locals.push(local.name); } break; } } } let stmt = 'const '; if ($default) { stmt += $default; } if ($locals.length > 0) { if ($default) stmt += ', '; stmt += '{ ' + $locals.join(', ') + ' }'; } let qq = s.snip(src.start, src.start + 1); stmt += ` = require(${qq + from + qq});`; s.overwrite(start, end, stmt); }, ExportDefaultDeclaration(n) { let start = (n as unknown as Location).start; s.overwrite(start, start + 'export default'.length, 'module.exports ='); }, ExportNamedDeclaration(n) { let start = (n as unknown as Location).start; let type = n.declaration?.type; let key: string | undefined; if (type === 'FunctionDeclaration') { key = (n.declaration as Function).id?.name; } else { console.log('EXPORT NAMED TYPE?', n.declaration); } if (key) { s.remove(start, start + 'export '.length); s.append(`\nexports.${key} = ${key};`); } }, }); return s.toString(); } lukeed-empathic-ed7dd2e/scripts/fixtures.ts000066400000000000000000000020041517716066700212570ustar00rootroot00000000000000import * as fs from 'node:fs/promises'; import { join, resolve } from 'node:path'; import { existsSync } from 'node:fs'; import * as walk from '../src/walk.ts'; const COUNT = +(Deno.args[0] ?? '6'); const fixtures = resolve('fixtures'); const start = join(fixtures, 'a/b/c/d/e/f/g/h/i/j'); if (!existsSync(start)) { await Deno.mkdir(start, { recursive: true, }); await Deno.create( join(start, 'start.txt'), ); // targets for bench/tests await Deno.create( join(fixtures, 'a/b/c/d/e/f/file.txt'), ); await Deno.create( join(fixtures, 'a/b/package.json'), ); await Deno.create( join(fixtures, 'a/b/c/resolved.js'), ); } for (let dir of walk.up(start)) { if (dir === fixtures) break; if (COUNT > 0) { let name = Math.random().toString(16).slice(4); await Promise.all( Array.from({ length: COUNT }, (_, idx) => { return Deno.create( join(dir, name + idx + '.txt'), ); }), ); } let arr = await fs.readdir(dir); console.log('> "%s" has %d file(s)', dir, arr.length); } lukeed-empathic-ed7dd2e/scripts/pretest.ts000066400000000000000000000023111517716066700210750ustar00rootroot00000000000000import { existsSync } from 'node:fs'; import { join, resolve } from 'node:path'; import { cp, readdir } from 'node:fs/promises'; import oxc from 'npm:oxc-transform@0.73.2'; // Transform all the test files // NOTE: must be w/ `build` for tests to work~! const Quiet = Deno.args.includes('--quiet'); const src = resolve('src'); const outdir = resolve('npm'); if (!existsSync(outdir)) { await Deno.mkdir(outdir); } // tests assume "fixtures" in CWD // -> there for bench & Deno runner if (existsSync(join(outdir, 'fixtures'))) { await Deno.remove(join(outdir, 'fixtures'), { recursive: true }); } await cp('fixtures', outdir); for (let item of await readdir(src)) { if (item.endsWith('.test.ts')) { await transform(item); } } async function transform(filename: string) { let entry = join(src, filename); let source = await Deno.readTextFile(entry); let esm = oxc.transform(entry, source, { target: 'node16' }); if (esm.errors.length > 0) { console.error('[oxc] error(s)\n', esm.errors.join('')); Deno.exit(1); } let target = filename.replace(/\.tsx?$/, '.mjs'); let outfile = join(outdir, target); Quiet || console.log('> writing "%s" file', target); await Deno.writeTextFile(outfile, esm.code); } lukeed-empathic-ed7dd2e/scripts/size.ts000066400000000000000000000020331517716066700203620ustar00rootroot00000000000000async function HEAD(ident: Identifier) { let [name, version] = ident.split('@'); let r = await fetch(`https://registry.npmjs.org/${name}/-/${name}-${version}.tgz`, { method: 'POST', }); return r.headers.get('content-length') || '???'; } type Identifier = `${string}@${string}`; type Package = { name: Identifier; needs?: Tree; }; type Tree = Package[]; let tree: Tree = [{ name: 'chalk@2.4.2', needs: [{ name: 'ansi-styles@3.2.1', needs: [{ name: 'color-convert@1.9.3', needs: [{ name: 'color-name@1.1.3', }], }], }, { name: 'escape-string-regexp@1.0.5', }, { name: 'supports-color@5.5.0', needs: [{ name: 'has-flag@3.0.0', }], }], }]; async function walk(tree: Tree): Promise { if (!tree.length) return; // Base case: if the tree is empty, do nothing await Promise.all( tree.map(async (pkg) => { let needs = pkg.needs ? walk(pkg.needs) : Promise.resolve(); let [main, kids] = await Promise.all([HEAD(pkg.name), needs]); console.log({ main, kids }); }), ); } await walk(tree); lukeed-empathic-ed7dd2e/src/000077500000000000000000000000001517716066700161425ustar00rootroot00000000000000lukeed-empathic-ed7dd2e/src/access.ts000066400000000000000000000015431517716066700177560ustar00rootroot00000000000000// re-export existsSync? import { accessSync, constants } from 'node:fs'; import type { PathLike } from 'node:fs'; /** * Does the current process have {@link mode} access? * By default, checks if the path is visible to the proccess. * * @param mode A `fs.constants` value; default `F_OK` */ export function ok(path: PathLike, mode?: number): boolean { try { accessSync(path, mode); return true; } catch { return false; } } /** * Can the current process write to this path? */ export function writable(path: PathLike): boolean { return ok(path, constants.W_OK); } /** * Can the current process read this path? */ export function readable(path: PathLike): boolean { return ok(path, constants.R_OK); } /** * Can the current process execute this path? */ export function executable(path: PathLike): boolean { return ok(path, constants.X_OK); } lukeed-empathic-ed7dd2e/src/find.bench.ts000066400000000000000000000043651517716066700205200ustar00rootroot00000000000000import { join, resolve } from 'node:path'; import { readdir } from 'node:fs/promises'; import escalade from 'npm:escalade@3.2.0'; import * as simple from 'npm:find-up-simple@1.0.0'; import { findUp } from 'npm:find-up@7.0.0'; import * as find from './find.ts'; import * as walk from './walk.ts'; let fixtures = resolve('fixtures'); let start = join(fixtures, 'a/b/c/d/e/f/g/h/i/j'); let level6 = 'file.txt'; let level10 = 'deno.json'; let other = Math.random().toString(16).slice(4); // like find.up, but readdir->check async function alt(name: string, options?: find.Options) { let dir: string, arr: string[]; let start = options && options.cwd || ''; for (dir of walk.up(start, options)) { arr = await readdir(dir); if (!!~arr.indexOf(name)) { return join(dir, name); } } } const Candidates = { 'find-up'(target: string) { return findUp(target, { cwd: start }); }, 'find-up-simple'(target: string) { return simple.findUp(target, { cwd: start }); }, 'find-up-simple-sync'(target: string) { return simple.findUpSync(target, { cwd: start }); }, 'escalade'(target: string) { return escalade(start, (_, items) => { return items.includes(target) && target; }); }, 'find.up'(target: string) { return find.up(target, { cwd: start }); }, 'find.up (alt)'(target: string) { return alt(target, { cwd: start }); }, }; const Benches = { 'level-6': level6, 'level-10': level10, 'missing': other, }; for (let name in Benches) { let input = Benches[name as keyof typeof Benches]; Deno.bench({ group: name, name: 'find-up', async fn() { let _ = await Candidates['find-up'](input); }, }); Deno.bench({ group: name, name: 'find-up-simple', async fn() { let _ = await Candidates['find-up-simple'](input); }, }); Deno.bench({ group: name, name: 'find-up-simple (sync)', fn() { let _ = Candidates['find-up-simple-sync'](input); }, }); Deno.bench({ group: name, name: 'escalade', async fn() { let _ = await Candidates['escalade'](input); }, }); Deno.bench({ group: name, name: 'find.up (alt)', async fn() { let _ = await Candidates['find.up (alt)'](input); }, }); Deno.bench({ group: name, name: 'empathic/find.up (sync)', fn() { let _ = Candidates['find.up'](input); }, }); } lukeed-empathic-ed7dd2e/src/find.test.ts000066400000000000000000000116761517716066700204230ustar00rootroot00000000000000import * as assert from 'uvu/assert'; import { suite, type Test } from 'uvu'; import { join, resolve } from 'node:path'; import * as find from 'empathic/find'; type Builder = (it: Test) => unknown; function describe(name: string, builder: Builder) { let it = suite(name); builder(it); it.run(); } const fixtures = resolve('fixtures'); describe('find.up', (it) => { let target = join(fixtures, 'a/b/c/d/e/f/file.txt'); it('should be a function', () => { assert.type(find.up, 'function'); }); it('should default looking in current (cwd) directory', () => { let output = find.up('license'); assert.is(output, resolve('license')); }); it('should use `options.cwd` directory', () => { let output = find.up('file.txt', { cwd: join(fixtures, 'a/b/c/d/e/f/g/h/i/j'), }); assert.is(output, target); }); it('should stop after `options.last` directory', () => { let output = find.up('file.txt', { cwd: join(fixtures, 'a/b/c/d/e/f/g/h/i/j'), last: join(fixtures, 'a/b/c/d/e/f/g'), }); assert.is(output, undefined); }); it('should still search `options.last` directory', () => { let output = find.up('file.txt', { cwd: join(fixtures, 'a/b/c/d/e/f/g/h/i/j'), last: join(fixtures, 'a/b/c/d/e/f'), // < file.txt is here }); assert.is(output, target); }); }); describe('find.any', (it) => { it('should be a function', () => { assert.type(find.any, 'function'); }); it('should looking in current (cwd) directory', () => { let output = find.any(['license']); assert.is(output, resolve('license')); }); it('should respect the input order', () => { // license comes first in file system, but not here let output = find.any(['readme.md', 'license', 'deno.json']); assert.is(output, resolve('readme.md')); }); it('should resolve from `options.cwd` directory', () => { let input = ['start.txt', 'file.txt']; let start = join(fixtures, 'a/b/c/d/e/f/g/h/i/j'); let output = find.any(input, { cwd: start }); assert.is(output, join(start, 'start.txt')); start = join(start, '..'); output = find.any(input, { cwd: start }); assert.is(output, join(fixtures, 'a/b/c/d/e/f/file.txt')); }); it('should still search `options.last` directory', () => { let output = find.any(['file.txt'], { cwd: join(fixtures, 'a/b/c/d/e/f/g/h/i/j'), last: join(fixtures, 'a/b/c/d/e/f'), // < file.txt is here }); assert.is(output, join(fixtures, 'a/b/c/d/e/f/file.txt')); }); it('should stop after `options.last` directory', () => { let output = find.any(['file.txt'], { cwd: join(fixtures, 'a/b/c/d/e/f/g/h/i/j'), last: join(fixtures, 'a/b/c/d/e/f/g'), }); assert.is(output, undefined); }); }); describe('find.file', (it) => { it('should be a function', () => { assert.type(find.file, 'function'); }); it('should looking in current (cwd) directory', () => { let output = find.file('license'); assert.is(output, resolve('license')); }); it('should use `options.cwd` directory', () => { let output = find.file('file.txt', { cwd: join(fixtures, 'a/b/c/d/e/f/g/h/i/j'), }); assert.is(output, join(fixtures, 'a/b/c/d/e/f/file.txt')); }); it('should stop after `options.last` directory', () => { let output = find.file('file.txt', { cwd: join(fixtures, 'a/b/c/d/e/f/g/h/i/j'), last: join(fixtures, 'a/b/c/d/e/f/g'), }); assert.is(output, undefined); }); it('should still search `options.last` directory', () => { let output = find.file('file.txt', { cwd: join(fixtures, 'a/b/c/d/e/f/g/h/i/j'), last: join(fixtures, 'a/b/c/d/e/f'), // < file.txt is here }); assert.is(output, join(fixtures, 'a/b/c/d/e/f/file.txt')); }); it('should ignore directory with matching name', () => { let output = find.file('e', { cwd: join(fixtures, 'a/b/c/d/e/f/g/h/i/j'), }); assert.is(output, undefined); }); }); describe('find.dir', (it) => { it('should be a function', () => { assert.type(find.dir, 'function'); }); it('should looking in current (cwd) directory', () => { let output = find.dir('fixtures'); assert.is(output, resolve('fixtures')); }); it('should use `options.cwd` directory', () => { let output = find.dir('f', { cwd: join(fixtures, 'a/b/c/d/e/f/g/h/i/j'), }); assert.is(output, join(fixtures, 'a/b/c/d/e/f')); }); // there is NO "g" directory inside the "fixtures/.../g" dir it('should stop after `options.last` directory', () => { let output = find.dir('g', { cwd: join(fixtures, 'a/b/c/d/e/f/g/h/i/j'), last: join(fixtures, 'a/b/c/d/e/f/g'), }); assert.is(output, undefined); }); // there IS a "g" directory inside the "fixtures/.../f" dir it('should still search `options.last` directory', () => { let output = find.dir('g', { cwd: join(fixtures, 'a/b/c/d/e/f/g/h/i/j'), last: join(fixtures, 'a/b/c/d/e/f'), }); assert.is(output, join(fixtures, 'a/b/c/d/e/f/g')); }); it('should ignore file with matching name', () => { let output = find.dir('file.txt', { cwd: join(fixtures, 'a/b/c/d/e/f/g/h/i/j'), }); assert.is(output, undefined); }); }); lukeed-empathic-ed7dd2e/src/find.ts000066400000000000000000000045271517716066700174420ustar00rootroot00000000000000import { join } from 'node:path'; import { existsSync, statSync } from 'node:fs'; import * as walk from 'empathic/walk'; import type { Options } from 'empathic/walk'; export type { Options }; /** * Find an item by name, walking parent directories until found. * * @param name The item name to find. * @returns The absolute path to the item, if found. */ export function up(name: string, options?: Options): string | undefined { let dir: string, tmp: string; let start = options && options.cwd || ''; for (dir of walk.up(start, options)) { tmp = join(dir, name); if (existsSync(tmp)) return tmp; } } /** * Get the first path that matches any of the names provided. * * > [NOTE] * > The order of {@link names} is respected. * * @param names The item names to find. * @returns The absolute path of the first item found, if any. */ export function any(names: string[], options?: Options): string | undefined { let dir: string, start = options && options.cwd || ''; let j = 0, len = names.length, tmp: string; for (dir of walk.up(start, options)) { for (j = 0; j < len; j++) { tmp = join(dir, names[j]); if (existsSync(tmp)) return tmp; } } } /** * Find a file by name, walking parent directories until found. * * > [NOTE] * > This function only returns a value for file matches. * > A directory match with the same name will be ignored. * * @param name The file name to find. * @returns The absolute path to the file, if found. */ export function file(name: string, options?: Options): string | undefined { let dir: string, tmp: string; let start = options && options.cwd || ''; for (dir of walk.up(start, options)) { try { tmp = join(dir, name); if (statSync(tmp).isFile()) return tmp; } catch { // ignore } } } /** * Find a directory by name, walking parent directories until found. * * > [NOTE] * > This function only returns a value for directory matches. * > A file match with the same name will be ignored. * * @param name The directory name to find. * @returns The absolute path to the file, if found. */ export function dir(name: string, options?: Options): string | undefined { let dir: string, tmp: string; let start = options && options.cwd || ''; for (dir of walk.up(start, options)) { try { tmp = join(dir, name); if (statSync(tmp).isDirectory()) return tmp; } catch { // ignore } } } lukeed-empathic-ed7dd2e/src/locate.bench.ts000066400000000000000000000052511517716066700210420ustar00rootroot00000000000000import { join, resolve } from 'node:path'; import escalade from 'npm:escalade@3.2.0'; import { locatePath, locatePathSync } from 'npm:locate-path@7.2'; import { findUpMultiple, findUpMultipleSync } from 'npm:find-up@7.0.0'; import * as find from './find.ts'; let fixtures = resolve('fixtures'); let start = join(fixtures, 'a/b/c/d/e/f/g/h/i/j'); let junk = ['a.js', 'a.mjs', 'a.cjs', 'a.ts', 'a.mts', 'a.cts']; let level6 = [...junk, 'file.txt']; let level10 = [...junk, 'deno.json']; let other = [...junk, Math.random().toString(16).slice(4)]; const Candidates = { 'locate-path'(target: string[]) { return locatePath([...target], { cwd: start, type: 'file', preserveOrder: false, allowSymlinks: false, }); }, 'locate-path (order)'(target: string[]) { return locatePath([...target], { cwd: start, type: 'file', preserveOrder: true, allowSymlinks: false, }); }, 'locate-path (sync)'(target: string[]) { return locatePathSync([...target], { cwd: start, type: 'file', allowSymlinks: false, }); }, 'find-up'(target: string[]) { return findUpMultiple([...target], { cwd: start, type: 'file', allowSymlinks: false, }); }, 'find-up (sync)'(target: string[]) { return findUpMultipleSync([...target], { cwd: start, type: 'file', allowSymlinks: false, }); }, 'escalade'(target: string[]) { let set = new Set(target); return escalade(start, (_, items) => { for (let i = 0; i < items.length; i++) { if (set.has(items[i])) return items[i]; } }); }, 'find.any (sync)'(target: string[]) { return find.any([...target], { cwd: start, }); }, }; let Benches = { 'locate-6': level6, 'locate-10': level10, 'missing-15': other, }; for (let k in Benches) { let input = Benches[k as keyof typeof Benches]; Deno.bench({ group: k, name: 'locate-path', async fn() { let _ = await Candidates['locate-path']([...input]); }, }); Deno.bench({ group: k, name: 'locate-path (sync)', fn() { let _ = Candidates['locate-path (sync)']([...input]); }, }); Deno.bench({ group: k, name: 'locate-path (order)', async fn() { let _ = await Candidates['locate-path (order)']([...input]); }, }); Deno.bench({ group: k, name: 'find-up', async fn() { let _ = await Candidates['find-up']([...input]); }, }); Deno.bench({ group: k, name: 'find-up (sync)', fn() { let _ = Candidates['find-up (sync)']([...input]); }, }); Deno.bench({ group: k, name: 'escalade', async fn() { let _ = await Candidates['escalade']([...input]); }, }); Deno.bench({ group: k, name: 'empathic/find.any (sync)', fn() { let _ = Candidates['find.any (sync)']([...input]); }, }); } lukeed-empathic-ed7dd2e/src/package.bench.ts000066400000000000000000000033361517716066700211700ustar00rootroot00000000000000import { dirname, resolve } from 'node:path'; import * as pkg from './package.ts'; import { pkgUp, pkgUpSync } from 'npm:pkg-up@5.0'; import { packageUp, packageUpSync } from 'npm:package-up@5.0'; import { packageDirectory, packageDirectorySync } from 'npm:pkg-dir@8.0'; import findCacheDirectory from 'npm:find-cache-dir@5.0'; let start = resolve('fixtures/a/b/c/d/e/f/g/h/i/j'); Deno.bench({ group: 'pkg-up', name: 'package-up', async fn() { let _ = await packageUp({ cwd: start, }); }, }); Deno.bench({ group: 'pkg-up', name: 'package-up (sync)', fn() { let _ = packageUpSync({ cwd: start, }); }, }); Deno.bench({ group: 'pkg-up', name: 'pkg-up', async fn() { let _ = await pkgUp({ cwd: start, }); }, }); Deno.bench({ group: 'pkg-up', name: 'pkg-up (sync)', fn() { let _ = pkgUpSync({ cwd: start, }); }, }); Deno.bench({ group: 'pkg-up', name: 'empathic/package.up (sync)', fn() { let _ = pkg.up({ cwd: start, }); }, }); // --- // --- Deno.bench({ group: 'pkg-dir', name: 'pkg-dir', async fn() { let _ = await packageDirectory({ cwd: start, }); }, }); Deno.bench({ group: 'pkg-dir', name: 'pkg-dir (sync)', fn() { let _ = packageDirectorySync({ cwd: start, }); }, }); Deno.bench({ group: 'pkg-dir', name: 'empathic/package.up (sync)', fn() { let tmp = pkg.up({ cwd: start }); if (tmp) tmp = dirname(tmp); }, }); // --- // --- Deno.bench({ group: 'find-cache-dir', name: 'find-cache-dir', fn() { let _ = findCacheDirectory({ name: 'foobar', create: false, cwd: start, }); }, }); Deno.bench({ group: 'find-cache-dir', name: 'empathic/package.cache ', fn() { let _ = pkg.cache('foobar', { create: false, cwd: start, }); }, }); lukeed-empathic-ed7dd2e/src/package.test.ts000066400000000000000000000054611517716066700210710ustar00rootroot00000000000000import * as assert from 'uvu/assert'; import { suite, type Test } from 'uvu'; import { env } from 'node:process'; import { join, resolve } from 'node:path'; import * as pkg from 'empathic/package'; type Builder = (it: Test) => unknown; function describe(name: string, builder: Builder) { let it = suite(name); builder(it); it.run(); } const fixtures = resolve('fixtures'); // see scripts/fixtures.ts const pkgfile = join(fixtures, 'a/b/package.json'); const start = join(fixtures, 'a/b/c/d/e/f/g/h/i/j'); describe('package.up', (it) => { it('should be a function', () => { assert.type(pkg.up, 'function'); }); it('should find the nearest "package.json" file', () => { let output = pkg.up(); assert.is(output, resolve('package.json')); // root }); it('should use `options.cwd` to resolve nearest "package.json" file', () => { let output = pkg.up({ cwd: start }); // see scripts/fixture.ts assert.is(output, pkgfile); }); it('should stop resolving after `options.last` directory', () => { let output = pkg.up({ cwd: resolve('fixtures/a/b/c/d/e/f/g/h/i/j'), last: resolve('fixtures/a/b/c/d/e/f'), }); // see scripts/fixture.ts assert.is(output, undefined); }); it('should still search `options.last` directory', () => { let output = pkg.up({ cwd: resolve('fixtures/a/b/c/d/e/f/g/h/i/j'), last: resolve('fixtures/a/b'), }); // see scripts/fixture.ts assert.is(output, pkgfile); }); }); describe('package.cache', (it) => { it('should be a function', () => { assert.type(pkg.cache, 'function'); }); it('should construct path from nearest "package.json" file', () => { let output = pkg.cache('foobar'); assert.is(output, resolve('node_modules/.cache/foobar')); // root }); it('should use `options.cwd` for resolution', () => { let output = pkg.cache('foobar', { cwd: start }); let expect = resolve(pkgfile, '../node_modules/.cache/foobar'); assert.is(output, expect); }); it('should still search `options.last` directory', () => { let output = pkg.cache('foobar', { cwd: start, last: resolve('fixtures/a/b'), }); let expect = resolve(pkgfile, '../node_modules/.cache/foobar'); assert.is(output, expect); }); it('should stop after `options.last` directory', () => { let output = pkg.cache('foobar', { cwd: start, last: resolve('fixtures/a/b/c'), }); assert.is(output, undefined); }); it('should ignore invalid `env.CACHE_DIR` values', () => { env.CACHE_DIR = 'true'; let output = pkg.cache('foobar'); let expect = resolve('node_modules/.cache/foobar'); // root delete env.CACHE_DIR; assert.is(output, expect); }); it('should use env.CACHE_DIR for base', () => { env.CACHE_DIR = fixtures; let output = pkg.cache('foobar'); let expect = resolve(fixtures, 'foobar'); delete env.CACHE_DIR; assert.is(output, expect); }); }); lukeed-empathic-ed7dd2e/src/package.ts000066400000000000000000000033711517716066700201110ustar00rootroot00000000000000import { env } from 'node:process'; import { dirname, join } from 'node:path'; import { existsSync, mkdirSync } from 'node:fs'; import { writable } from 'empathic/access'; import * as find from 'empathic/find'; /** * Find the closest "package.json" file while walking parent directories. * @returns The absolute path to a "package.json", if found. */ export function up(options?: find.Options): string | undefined { return find.up('package.json', options); } /** * Construct a path to a `node_modules/.cache/` directory. * * This may return `undefined` if: * 1. no "package.json" could be found * 2. the nearest "node_modules" directory is not writable * 3. the "node_modules" parent directory is not writable * * > [NOTE] * > You may define a `CACHE_DIR` environment variable, which will be * > used (as defined) instead of traversing the filesystem for the * > closest "package.json" and inferring a "node_modules" location. * * @see find-cache-dir for more information. * * @param name The name of your module/cache. * @returns The absolute path of the cache directory, if found. */ export function cache( name: string, options?: find.Options & { create?: boolean }, ): string | undefined { options = options || {}; let dir = env.CACHE_DIR; if (!dir || /^(1|0|true|false)$/.test(dir)) { let pkg = up(options); if (dir = pkg && dirname(pkg)) { let mods = join(dir, 'node_modules'); let exists = existsSync(mods); // exit cuz exists but not writable // or cuz missing but parent not writable if (!writable(exists ? mods : dir)) return; dir = join(mods, '.cache'); } } if (dir) { dir = join(dir, name); if (options.create && !existsSync(dir)) { mkdirSync(dir, { recursive: true }); } return dir; } } lukeed-empathic-ed7dd2e/src/resolve.bench.ts000066400000000000000000000013161517716066700212500ustar00rootroot00000000000000import * as path from 'node:path'; import * as resolve from './resolve.ts'; import rFrom from 'npm:resolve-from@5.0'; import rCWD from 'npm:resolve-cwd@3.0'; let start = path.resolve('fixtures/a/b/c'); Deno.bench({ group: 'resolve-from', name: 'resolve-from', fn() { let _ = rFrom(start, './resolved.js'); }, }); Deno.bench({ group: 'resolve-from', name: 'empathic/resolve.from', fn() { let _ = resolve.from(start, './resolved.js'); }, }); Deno.bench({ group: 'resolve-cwd', name: 'resolve-cwd', fn() { let _ = rCWD('./fixtures/a/b/c/resolved.js'); }, }); Deno.bench({ group: 'resolve-cwd', name: 'empathic/resolve.cwd', fn() { let _ = resolve.cwd('./fixtures/a/b/c/resolved.js'); }, }); lukeed-empathic-ed7dd2e/src/resolve.test.ts000066400000000000000000000100701517716066700211450ustar00rootroot00000000000000import * as assert from 'uvu/assert'; import { suite, type Test } from 'uvu'; import * as path from 'node:path'; import * as fs from 'node:fs/promises'; import { pathToFileURL } from 'node:url'; import * as resolve from 'empathic/resolve'; type RequireError = Error & { code: string; requireStack: string[]; }; type Builder = (it: Test) => unknown; function describe(name: string, builder: Builder) { let it = suite(name); builder(it); it.run(); } describe('resolve.absolute', (it) => { it('should be a function', () => { assert.type(resolve.absolute, 'function'); }); it('should respect absolute inputs', () => { let input = path.resolve('fixtures/a/b/c'); let output = resolve.absolute(input); assert.is(output, input); }); it('should resolve non-absolute inputs', () => { let output = resolve.absolute('fixtures/a/b/c'); assert.is(output, path.resolve('fixtures/a/b/c')); }); it.skip('should handle file:/// inputs', () => { let real = path.resolve('fixtures'); let input = pathToFileURL(real).toString(); let output = resolve.absolute(input); assert.is(output, real); }); }); describe('resolve.from', (it) => { it('should be a function', () => { assert.is(typeof resolve.from, 'function'); }); it('should throw MODULE_NOT_FOUND if identifer not found', () => { try { let _ = resolve.from('fixtures', 'foobar'); assert.unreachable('should have thrown'); } catch (err) { assert.instance(err, Error); let { code, message, requireStack } = err as RequireError; assert.is(code, 'MODULE_NOT_FOUND'); assert.match(message, "Cannot find module 'foobar'"); // NOTE: the "noop.js" is added internally (lol) because its a dir assert.equal(requireStack, [path.resolve('fixtures/noop.js')]); } }); it('should NOT throw if `silent` enabled', () => { let output = resolve.from('foo', 'bar', true); assert.is(output, undefined); }); it('should resolve relative paths', async () => { let target = path.resolve('fixtures/foo.js'); try { await fs.writeFile(target, ''); let output = resolve.from('fixtures', './foo'); assert.is(output, target); } finally { await fs.unlink(target); } }); it('should resolve node_module identifiers', async () => { let moddir = path.resolve('fixtures/node_modules'); let foobar = path.join(moddir, 'foobar'); let pkgfile = path.join(foobar, 'package.json'); let target = path.join(foobar, 'index.js'); try { await fs.mkdir(foobar, { recursive: true }); await fs.writeFile(target, ''); await fs.writeFile(pkgfile, '{}'); let output = resolve.from('fixtures', 'foobar'); assert.is(output, target); } finally { await fs.rm(moddir, { recursive: true }); } }); }); describe('resolve.cwd', (it) => { it('should be a function', () => { assert.type(resolve.cwd, 'function'); }); it('should throw if identifier does not exist', () => { try { let _ = resolve.cwd('foobar'); assert.unreachable('should have thrown'); } catch (err) { let { code, message, requireStack } = err as RequireError; assert.instance(err, Error); assert.is(code, 'MODULE_NOT_FOUND'); assert.match(message, "Cannot find module 'foobar'"); // NOTE: the "noop.js" is added internally (lol) because its a dir assert.equal(requireStack, [path.resolve('noop.js')]); } }); it('should NOT throw if `silent` enabled', () => { let output = resolve.cwd('foobar', true); assert.is(output, undefined); }); it('should resolve relative paths', () => { let target = path.resolve('license'); let output = resolve.cwd('./license'); assert.is(output, target); }); it('should resolve node_module identifiers', async () => { let moddir = path.resolve('node_modules'); let foobar = path.join(moddir, 'foobar'); let pkgfile = path.join(foobar, 'package.json'); let target = path.join(foobar, 'index.js'); try { await fs.mkdir(foobar, { recursive: true }); await fs.writeFile(target, ''); await fs.writeFile(pkgfile, '{}'); let output = resolve.cwd('foobar'); assert.is(output, target); } finally { await fs.rm(foobar, { recursive: true }); } }); }); lukeed-empathic-ed7dd2e/src/resolve.ts000066400000000000000000000036001517716066700201700ustar00rootroot00000000000000import { createRequire } from 'node:module'; import { isAbsolute, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; /** * Resolve an absolute path from {@link root}, but only * if {@link input} isn't already absolute. * * @param input The path to resolve. * @param root The base path; default = process.cwd() * @returns The resolved absolute path. */ export function absolute(input: string, root?: string): string { return isAbsolute(input) ? input : resolve(root || '.', input); } /** * Resolve a module path from a given root directory. * * Emulates [`require.resolve`](https://nodejs.org/docs/latest/api/modules.html#requireresolverequest-options), so module identifiers are allowed. * * @see resolve-from */ export function from(root: URL | string, ident: string, silent: true): string | undefined; export function from(root: URL | string, ident: string, silent?: false): string; export function from(root: URL | string, ident: string, silent?: boolean): string | undefined; export function from(root: URL | string, ident: string, silent?: boolean) { try { // NOTE: dirs need a trailing "/" OR filename. With "/" route, // Node adds "noop.js" as main file, so just do "noop.js" anyway. let r = (root instanceof URL || root.startsWith('file://')) ? join(fileURLToPath(root), 'noop.js') : join(absolute(root), 'noop.js'); return createRequire(r).resolve(ident); } catch (err) { if (!silent) throw err; } } /** * Resolve a module path from the current working directory. * * Alias for {@link from} using the CWD as its root. * * @see resolve-cwd */ export function cwd(ident: string, silent: true): string | undefined; export function cwd(ident: string, silent?: false): string; export function cwd(ident: string, silent?: boolean): string | undefined; export function cwd(ident: string, silent?: boolean) { return from(resolve(), ident, silent); } lukeed-empathic-ed7dd2e/src/walk.bench.ts000066400000000000000000000044051517716066700205310ustar00rootroot00000000000000import * as walk from './walk.ts'; import { dirname, isAbsolute, resolve, sep } from 'node:path'; import { absolute } from './resolve.ts'; // let start = '.'; // let start = resolve('fixtures/a/b/c/d/e/f/g/h/i/j/start.txt'); let start = 'fixtures/a/b/c/d/e/f/g/h/i/j/start.txt'; function alt1(base: string) { let prev = absolute(base); let tmp = dirname(prev); let arr: string[] = []; while (true) { arr.push(tmp); tmp = dirname(prev = tmp); if (tmp === prev) return arr; } } Deno.bench('empathic/walk.up (no options)', () => { let total = 0; // let items = walk.up(start); // console.log('> items', items); for (let _ of alt1(start)) { total += 1; } }); Deno.bench('empathic/walk.up', () => { let total = 0; for (let _ of walk.up(start)) { total += 1; } }); Deno.bench('split1', () => { let total = 0; for (let _ of split1(start)) { total += 1; } }); Deno.bench('split2', () => { let total = 0; for (let _ of split2(start)) { total += 1; } }); Deno.bench('split3', () => { let total = 0; for (let _ of split3(start)) { total += 1; } }); Deno.bench('split4', () => { let total = 0; for (let _ of split4(start)) { total += 1; } }); function split1(input: string) { let arr = (isAbsolute(input) ? input : resolve(input)).split(sep); let i = 0, len = arr.length, output = Array(len); for (; i < len; i++) { output[i] = arr.slice(0, len - i).join(sep); } return output; } function split2(input: string) { let output: string[] = []; let base = isAbsolute(input) ? input : resolve(input); let match: RegExpExecArray | null; let rgx = new RegExp('[' + sep + ']+', 'g'); while (match = rgx.exec(base)) { output.push( base.slice(0, match.index) || '/', ); } return output.reverse(); } function split3(input: string) { let base = isAbsolute(input) ? input : resolve(input); let len = base.length, output: string[] = []; while (len-- > 0) { if (base.charCodeAt(len) === 47) { output.push( base.slice(0, len) || '/', ); } } return output; } function split4(input: string) { let base = isAbsolute(input) ? input : resolve(input); let len = base.length, output: string[] = []; while (len-- > 0) { if (base.charAt(len) === sep) { output.push( base.slice(0, len) || sep, ); } } return output; } lukeed-empathic-ed7dd2e/src/walk.test.ts000066400000000000000000000046641517716066700204400ustar00rootroot00000000000000import * as assert from 'uvu/assert'; import { suite, type Test } from 'uvu'; import { join, resolve } from 'node:path'; import * as walk from 'empathic/walk'; type Builder = (it: Test) => unknown; function describe(name: string, builder: Builder) { let it = suite(name); builder(it); it.run(); } describe('walk.up', (it) => { const fixtures = resolve('fixtures'); it('should be a function', () => { assert.type(walk.up, 'function'); }); let parents = walk.up( join(fixtures, 'a/b/c'), ); it('should return an Array of parent directories', () => { assert.instance(parents, Array); }); it('should resolve input from CWD if not absolute', () => { let output = walk.up('fixtures/a/b/c'); assert.equal(output, parents); }); it('should start with the initial directory', () => { assert.is(parents[0], resolve('fixtures/a/b/c')); }); it('should return all parents until "/" root (default)', () => { assert.is(parents[0], resolve('fixtures/a/b/c')); assert.is(parents[1], resolve('fixtures/a/b')); assert.is(parents[2], resolve('fixtures/a')); assert.is(parents[3], resolve('fixtures')); assert.is(parents[4], resolve('.')); // total chain length unknown assert.is(parents[parents.length - 1], resolve('/')); }); it('should resolve from `options.cwd` if input is not absolute', () => { let output = walk.up('a/b/c', { cwd: fixtures, }); assert.equal(output, parents); }); it('should stop after `options.last` directory', () => { let output = walk.up('fixtures/a/b/c', { last: fixtures, }); assert.ok(parents.length > output.length); }); it('should include `options.last` directory', () => { let output = walk.up('fixtures/a/b/c', { last: fixtures, }); assert.is(output[output.length - 1], fixtures); }); it('should only have 1 entry if started at `options.last` directory', () => { let start = resolve('fixtures/a/b/c'); let output = walk.up(start, { last: start }); assert.is(output.length, 1); }); // find-up/locate-paths cycle in infinite loop it('should still exit at root if `options.last` is a subdir of start', () => { let start = resolve('fixtures/a/b/c'); let last = join(start, 'd/e/f'); let output = walk.up(start, { last }); assert.is(output[0], resolve('fixtures/a/b/c')); assert.is(output[1], resolve('fixtures/a/b')); assert.is(output[2], resolve('fixtures/a')); assert.is(output[output.length - 1], resolve('/')); assert.equal(output, parents); }); }); lukeed-empathic-ed7dd2e/src/walk.ts000066400000000000000000000015241517716066700174520ustar00rootroot00000000000000import { dirname } from 'node:path'; import { absolute } from 'empathic/resolve'; export type Options = { /** * The CWD for the operation. * @default "." (process.cwd) */ cwd?: string; /** * The last directory to traverse. * * > [NOTE] * > This directory is INCLUSIVE. * * @default "/" */ last?: string; }; /** * Get all parent directories of {@link base}. * Stops after {@link Options['last']} is processed. * * @returns An array of absolute paths of all parent directories. */ export function up(base: string, options?: Options): string[] { let { last, cwd } = options || {}; let tmp = absolute(base, cwd); let root = absolute(last || '/', cwd); let prev: string | undefined, arr: string[] = []; while (prev !== root) { arr.push(tmp); tmp = dirname(prev = tmp); if (tmp === prev) break; } return arr; }