From 9a64652b2fbc51cae8a037cd9408941fe5cb671e Mon Sep 17 00:00:00 2001 From: David Sherret Date: Mon, 26 Aug 2024 22:00:09 -0400 Subject: [PATCH 1/9] refactor: replace path class with @david/path --- README.md | 4 +- deno.json | 18 +- deno.lock | 334 +++++------ mod.test.ts | 2 +- mod.ts | 19 +- scripts/build_npm.ts | 21 +- src/command.ts | 6 +- src/path.test.ts | 968 -------------------------------- src/path.ts | 1267 ------------------------------------------ src/pipes.ts | 2 +- src/request.test.ts | 12 +- src/request.ts | 2 +- src/with_temp_dir.ts | 6 +- 13 files changed, 189 insertions(+), 2472 deletions(-) delete mode 100644 src/path.test.ts diff --git a/README.md b/README.md index c87d1ee..fdba100 100644 --- a/README.md +++ b/README.md @@ -612,7 +612,7 @@ pb.with(() => { ## Path API -The path API offers an immutable [`Path`](https://jsr.io/@david/dax/doc/~/Path) class, which is a similar concept to Rust's `PathBuf` struct. +The path API offers an immutable [`Path`](https://jsr.io/@david/path/doc/~/Path) class via [`jsr:@david/path`](https://jsr.io/@david/path), which is a similar concept to Rust's `PathBuf` struct. ```ts // create a `Path` @@ -659,7 +659,7 @@ const pathStringFileUrl = $.path("file:///tmp"); // converts to /tmp const pathImportMeta = $.path(import.meta); // the path for the current module ``` -There are a lot of helper methods here, so check the [documentation on Path](https://jsr.io/@david/dax/doc/~/Path) for more details. +There are a lot of helper methods here, so check the [documentation on Path](https://jsr.io/@david/path/doc/~/Path) for more details. ## Helper functions diff --git a/deno.json b/deno.json index 39bbd3c..0134e1d 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,5 @@ { "name": "@david/dax", - "version": "0.0.0", "nodeModulesDir": false, "tasks": { "test": "deno test -A", @@ -32,14 +31,15 @@ ], "exports": "./mod.ts", "imports": { - "@deno/dnt": "jsr:@deno/dnt@^0.41.1", - "@std/assert": "jsr:@std/assert@^0.221.0", - "@std/fmt": "jsr:@std/fmt@^0.221.0", - "@std/fs": "jsr:@std/fs@0.221.0", - "@std/io": "jsr:@std/io@0.221.0", - "@std/path": "jsr:@std/path@0.221.0", - "@std/streams": "jsr:@std/streams@0.221.0", - "which": "jsr:@david/which@^0.4.1", + "@deno/dnt": "jsr:@deno/dnt@~0.41.3", + "@david/path": "jsr:@david/path@0.2", + "@std/assert": "jsr:@std/assert@1", + "@std/fmt": "jsr:@std/fmt@1", + "@std/fs": "jsr:@std/fs@1", + "@std/io": "jsr:@std/io@0.221", + "@std/path": "jsr:@std/path@1", + "@std/streams": "jsr:@std/streams@0.221", + "which": "jsr:@david/which@~0.4.1", "which_runtime": "jsr:@david/which-runtime@0.2" } } diff --git a/deno.lock b/deno.lock index a0aeb42..2f720b4 100644 --- a/deno.lock +++ b/deno.lock @@ -2,92 +2,127 @@ "version": "3", "packages": { "specifiers": { + "jsr:@david/code-block-writer@^13.0.2": "jsr:@david/code-block-writer@13.0.2", + "jsr:@david/path@0.2": "jsr:@david/path@0.2.0", "jsr:@david/which-runtime@0.2": "jsr:@david/which-runtime@0.2.0", - "jsr:@david/which@^0.4.1": "jsr:@david/which@0.4.1", - "jsr:@deno/cache-dir@^0.8.0": "jsr:@deno/cache-dir@0.8.0", - "jsr:@deno/dnt@^0.41.1": "jsr:@deno/dnt@0.41.1", - "jsr:@std/assert@^0.218.2": "jsr:@std/assert@0.218.2", + "jsr:@david/which@~0.4.1": "jsr:@david/which@0.4.1", + "jsr:@deno/cache-dir@^0.10.3": "jsr:@deno/cache-dir@0.10.3", + "jsr:@deno/dnt@~0.41.3": "jsr:@deno/dnt@0.41.3", + "jsr:@deno/graph@^0.73.1": "jsr:@deno/graph@0.73.1", + "jsr:@std/assert@1": "jsr:@std/assert@1.0.3", "jsr:@std/assert@^0.221.0": "jsr:@std/assert@0.221.0", - "jsr:@std/bytes@^0.218.2": "jsr:@std/bytes@0.218.2", + "jsr:@std/assert@^0.223.0": "jsr:@std/assert@0.223.0", + "jsr:@std/assert@^0.226.0": "jsr:@std/assert@0.226.0", "jsr:@std/bytes@^0.221.0": "jsr:@std/bytes@0.221.0", - "jsr:@std/fmt@^0.218.2": "jsr:@std/fmt@0.218.2", - "jsr:@std/fmt@^0.221.0": "jsr:@std/fmt@0.221.0", - "jsr:@std/fs@0.221.0": "jsr:@std/fs@0.221.0", - "jsr:@std/fs@^0.218.2": "jsr:@std/fs@0.218.2", - "jsr:@std/io@0.221.0": "jsr:@std/io@0.221.0", - "jsr:@std/io@^0.218.2": "jsr:@std/io@0.218.2", + "jsr:@std/bytes@^0.223.0": "jsr:@std/bytes@0.223.0", + "jsr:@std/fmt@1": "jsr:@std/fmt@1.0.1", + "jsr:@std/fmt@^0.223": "jsr:@std/fmt@0.223.0", + "jsr:@std/fs@1": "jsr:@std/fs@1.0.2", + "jsr:@std/fs@^0.223": "jsr:@std/fs@0.223.0", + "jsr:@std/fs@^0.229.3": "jsr:@std/fs@0.229.3", + "jsr:@std/fs@^1": "jsr:@std/fs@1.0.2", + "jsr:@std/internal@^1.0.2": "jsr:@std/internal@1.0.2", + "jsr:@std/io@0.221": "jsr:@std/io@0.221.0", "jsr:@std/io@^0.221.0": "jsr:@std/io@0.221.0", - "jsr:@std/path@0.221.0": "jsr:@std/path@0.221.0", - "jsr:@std/path@^0.218.2": "jsr:@std/path@0.218.2", - "jsr:@std/path@^0.221.0": "jsr:@std/path@0.221.0", - "jsr:@std/streams@0.221.0": "jsr:@std/streams@0.221.0", - "npm:@ts-morph/bootstrap@0.22": "npm:@ts-morph/bootstrap@0.22.0", - "npm:code-block-writer@^13.0.1": "npm:code-block-writer@13.0.1", + "jsr:@std/io@^0.223": "jsr:@std/io@0.223.0", + "jsr:@std/path@1": "jsr:@std/path@1.0.3", + "jsr:@std/path@1.0.0-rc.1": "jsr:@std/path@1.0.0-rc.1", + "jsr:@std/path@^0.223": "jsr:@std/path@0.223.0", + "jsr:@std/path@^0.225.2": "jsr:@std/path@0.225.2", + "jsr:@std/path@^1": "jsr:@std/path@1.0.3", + "jsr:@std/path@^1.0.3": "jsr:@std/path@1.0.3", + "jsr:@std/streams@0.221": "jsr:@std/streams@0.221.0", + "jsr:@ts-morph/bootstrap@^0.24.0": "jsr:@ts-morph/bootstrap@0.24.0", + "jsr:@ts-morph/common@^0.24.0": "jsr:@ts-morph/common@0.24.0", + "npm:@types/node": "npm:@types/node@18.16.19", "npm:esbuild@0.20.0": "npm:esbuild@0.20.0" }, "jsr": { + "@david/code-block-writer@13.0.2": { + "integrity": "14dd3baaafa3a2dea8bf7dfbcddeccaa13e583da2d21d666c01dc6d681cd74ad" + }, + "@david/path@0.2.0": { + "integrity": "f2d7aa7f02ce5a55e27c09f9f1381794acb09d328f8d3c8a2e3ab3ffc294dccd", + "dependencies": [ + "jsr:@std/fs@^1", + "jsr:@std/path@^1" + ] + }, "@david/which-runtime@0.2.0": { "integrity": "69c59142babcff447efff6a6460316131b702ce1713f9f5ddbbb909136d6d7cd" }, "@david/which@0.4.1": { "integrity": "896a682b111f92ab866cc70c5b4afab2f5899d2f9bde31ed00203b9c250f225e" }, - "@deno/cache-dir@0.8.0": { - "integrity": "e87e80a404958f6350d903e6238b72afb92468378b0b32111f7a1e4916ac7fe7", + "@deno/cache-dir@0.10.3": { + "integrity": "eb022f84ecc49c91d9d98131c6e6b118ff63a29e343624d058646b9d50404776", "dependencies": [ - "jsr:@std/fs@^0.218.2", - "jsr:@std/io@^0.218.2" + "jsr:@deno/graph@^0.73.1", + "jsr:@std/fmt@^0.223", + "jsr:@std/fs@^0.223", + "jsr:@std/io@^0.223", + "jsr:@std/path@^0.223" ] }, - "@deno/dnt@0.41.1": { - "integrity": "8746a773e031ae19ef43d0eece850217b76cf1d0118fdd8e059652d7023d4aff", + "@deno/dnt@0.41.3": { + "integrity": "b2ef2c8a5111eef86cb5bfcae103d6a2938e8e649e2461634a7befb7fc59d6d2", "dependencies": [ - "jsr:@deno/cache-dir@^0.8.0", - "jsr:@std/fmt@^0.218.2", - "jsr:@std/fs@^0.218.2", - "jsr:@std/path@^0.218.2", - "npm:@ts-morph/bootstrap@0.22", - "npm:code-block-writer@^13.0.1" + "jsr:@david/code-block-writer@^13.0.2", + "jsr:@deno/cache-dir@^0.10.3", + "jsr:@std/fmt@1", + "jsr:@std/fs@1", + "jsr:@std/path@1", + "jsr:@ts-morph/bootstrap@^0.24.0" ] }, - "@std/assert@0.218.2": { - "integrity": "7f0a5a1a8cf86607cd6c2c030584096e1ffad27fc9271429a8cb48cfbdee5eaf" + "@deno/graph@0.73.1": { + "integrity": "cd69639d2709d479037d5ce191a422eabe8d71bb68b0098344f6b07411c84d41" }, "@std/assert@0.221.0": { "integrity": "a5f1aa6e7909dbea271754fd4ab3f4e687aeff4873b4cef9a320af813adb489a" }, - "@std/bytes@0.218.2": { - "integrity": "91fe54b232dcca73856b79a817247f4a651dbb60d51baafafb6408c137241670" + "@std/assert@0.223.0": { + "integrity": "eb8d6d879d76e1cc431205bd346ed4d88dc051c6366365b1af47034b0670be24" + }, + "@std/assert@0.226.0": { + "integrity": "0dfb5f7c7723c18cec118e080fec76ce15b4c31154b15ad2bd74822603ef75b3" + }, + "@std/assert@1.0.3": { + "integrity": "b0d03ce1ced880df67132eea140623010d415848df66f6aa5df76507ca7c26d8", + "dependencies": [ + "jsr:@std/internal@^1.0.2" + ] }, "@std/bytes@0.221.0": { "integrity": "64a047011cf833890a4a2ab7293ac55a1b4f5a050624ebc6a0159c357de91966" }, - "@std/fmt@0.218.2": { - "integrity": "99526449d2505aa758b6cbef81e7dd471d8b28ec0dcb1491d122b284c548788a" + "@std/bytes@0.223.0": { + "integrity": "84b75052cd8680942c397c2631318772b295019098f40aac5c36cead4cba51a8" }, - "@std/fmt@0.221.0": { - "integrity": "379fed69bdd9731110f26b9085aeb740606b20428ce6af31ef6bd45ef8efa62a" + "@std/fmt@0.223.0": { + "integrity": "6deb37794127dfc7d7bded2586b9fc6f5d50e62a8134846608baf71ffc1a5208" }, - "@std/fs@0.218.2": { - "integrity": "dd9431453f7282e8c577cc22c9e6d036055a9a980b5549f887d6012969fabcca", - "dependencies": [ - "jsr:@std/assert@^0.218.2", - "jsr:@std/path@^0.218.2" - ] + "@std/fmt@1.0.1": { + "integrity": "ef76c37faa7720faa8c20fd8cc74583f9b1e356dfd630c8714baa716a45856ab" + }, + "@std/fs@0.223.0": { + "integrity": "3b4b0550b2c524cbaaa5a9170c90e96cbb7354e837ad1bdaf15fc9df1ae9c31c" }, - "@std/fs@0.221.0": { - "integrity": "028044450299de8ed5a716ade4e6d524399f035513b85913794f4e81f07da286", + "@std/fs@0.229.3": { + "integrity": "783bca21f24da92e04c3893c9e79653227ab016c48e96b3078377ebd5222e6eb", "dependencies": [ - "jsr:@std/assert@^0.221.0", - "jsr:@std/path@^0.221.0" + "jsr:@std/path@1.0.0-rc.1" ] }, - "@std/io@0.218.2": { - "integrity": "c64fbfa087b7c9d4d386c5672f291f607d88cb7d44fc299c20c713e345f2785f", + "@std/fs@1.0.2": { + "integrity": "af57555c7a224a6f147d5cced5404692974f7a628ced8eda67e0d22d92d474ec", "dependencies": [ - "jsr:@std/bytes@^0.218.2" + "jsr:@std/path@^1.0.3" ] }, + "@std/internal@1.0.2": { + "integrity": "f4cabe2021352e8bfc24e6569700df87bf070914fc38d4b23eddd20108ac4495" + }, "@std/io@0.221.0": { "integrity": "faf7f8700d46ab527fa05cc6167f4b97701a06c413024431c6b4d207caa010da", "dependencies": [ @@ -95,23 +130,49 @@ "jsr:@std/bytes@^0.221.0" ] }, - "@std/path@0.218.2": { - "integrity": "b568fd923d9e53ad76d17c513e7310bda8e755a3e825e6289a0ce536404e2662", + "@std/io@0.223.0": { + "integrity": "2d8c3c2ab3a515619b90da2c6ff5ea7b75a94383259ef4d02116b228393f84f1", + "dependencies": [ + "jsr:@std/assert@^0.223.0", + "jsr:@std/bytes@^0.223.0" + ] + }, + "@std/path@0.223.0": { + "integrity": "593963402d7e6597f5a6e620931661053572c982fc014000459edc1f93cc3989", "dependencies": [ - "jsr:@std/assert@^0.218.2" + "jsr:@std/assert@^0.223.0" ] }, - "@std/path@0.221.0": { - "integrity": "0a36f6b17314ef653a3a1649740cc8db51b25a133ecfe838f20b79a56ebe0095", + "@std/path@0.225.2": { + "integrity": "0f2db41d36b50ef048dcb0399aac720a5348638dd3cb5bf80685bf2a745aa506", "dependencies": [ - "jsr:@std/assert@^0.221.0" + "jsr:@std/assert@^0.226.0" ] }, + "@std/path@1.0.0-rc.1": { + "integrity": "b8c00ae2f19106a6bb7cbf1ab9be52aa70de1605daeb2dbdc4f87a7cbaf10ff6" + }, + "@std/path@1.0.3": { + "integrity": "cd89d014ce7eb3742f2147b990f6753ee51d95276bfc211bc50c860c1bc7df6f" + }, "@std/streams@0.221.0": { "integrity": "47f2f74634b47449277c0ee79fe878da4424b66bd8975c032e3afdca88986e61", "dependencies": [ "jsr:@std/io@^0.221.0" ] + }, + "@ts-morph/bootstrap@0.24.0": { + "integrity": "a826a2ef7fa8a7c3f1042df2c034d20744d94da2ee32bf29275bcd4dffd3c060", + "dependencies": [ + "jsr:@ts-morph/common@^0.24.0" + ] + }, + "@ts-morph/common@0.24.0": { + "integrity": "12b625b8e562446ba658cdbe9ad77774b4bd96b992ae8bd34c60dbf24d06c1f3", + "dependencies": [ + "jsr:@std/fs@^0.229.3", + "jsr:@std/path@^0.225.2" + ] } }, "npm": { @@ -207,57 +268,8 @@ "integrity": "sha512-NgJnesu1RtWihtTtXGFMU5YSE6JyyHPMxCwBZK7a6/8d31GuSo9l0Ss7w1Jw5QnKUawG6UEehs883kcXf5fYwg==", "dependencies": {} }, - "@nodelib/fs.scandir@2.1.5": { - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dependencies": { - "@nodelib/fs.stat": "@nodelib/fs.stat@2.0.5", - "run-parallel": "run-parallel@1.2.0" - } - }, - "@nodelib/fs.stat@2.0.5": { - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dependencies": {} - }, - "@nodelib/fs.walk@1.2.8": { - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dependencies": { - "@nodelib/fs.scandir": "@nodelib/fs.scandir@2.1.5", - "fastq": "fastq@1.17.1" - } - }, - "@ts-morph/bootstrap@0.22.0": { - "integrity": "sha512-MI5q7pid4swAlE2lcHwHRa6rcjoIMyT6fy8uuZm8BGg7DHGi/H5bQ0GMZzbk3N0r/LfStMdOYPkl+3IwvfIQ2g==", - "dependencies": { - "@ts-morph/common": "@ts-morph/common@0.22.0" - } - }, - "@ts-morph/common@0.22.0": { - "integrity": "sha512-HqNBuV/oIlMKdkLshXd1zKBqNQCsuPEsgQOkfFQ/eUKjRlwndXW1AjN9LVkBEIukm00gGXSRmfkl0Wv5VXLnlw==", - "dependencies": { - "fast-glob": "fast-glob@3.3.2", - "minimatch": "minimatch@9.0.3", - "mkdirp": "mkdirp@3.0.1", - "path-browserify": "path-browserify@1.0.1" - } - }, - "balanced-match@1.0.2": { - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dependencies": {} - }, - "brace-expansion@2.0.1": { - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "balanced-match@1.0.2" - } - }, - "braces@3.0.2": { - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dependencies": { - "fill-range": "fill-range@7.0.1" - } - }, - "code-block-writer@13.0.1": { - "integrity": "sha512-c5or4P6erEA69TxaxTNcHUNcIn+oyxSRTOWV+pSYF+z4epXqNvwvJ70XPGjPNgue83oAFAPBRQYwpAJ/Hpe/Sg==", + "@types/node@18.16.19": { + "integrity": "sha512-IXl7o+R9iti9eBW4Wg2hx1xQDig183jj7YLn8F7udNceyfkbn1ZxmzZXuak20gR40D7pIkIY1kYGx5VIGbaHKA==", "dependencies": {} }, "esbuild@0.20.0": { @@ -287,112 +299,22 @@ "@esbuild/win32-ia32": "@esbuild/win32-ia32@0.20.0", "@esbuild/win32-x64": "@esbuild/win32-x64@0.20.0" } - }, - "fast-glob@3.3.2": { - "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", - "dependencies": { - "@nodelib/fs.stat": "@nodelib/fs.stat@2.0.5", - "@nodelib/fs.walk": "@nodelib/fs.walk@1.2.8", - "glob-parent": "glob-parent@5.1.2", - "merge2": "merge2@1.4.1", - "micromatch": "micromatch@4.0.5" - } - }, - "fastq@1.17.1": { - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", - "dependencies": { - "reusify": "reusify@1.0.4" - } - }, - "fill-range@7.0.1": { - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dependencies": { - "to-regex-range": "to-regex-range@5.0.1" - } - }, - "glob-parent@5.1.2": { - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dependencies": { - "is-glob": "is-glob@4.0.3" - } - }, - "is-extglob@2.1.1": { - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dependencies": {} - }, - "is-glob@4.0.3": { - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dependencies": { - "is-extglob": "is-extglob@2.1.1" - } - }, - "is-number@7.0.0": { - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dependencies": {} - }, - "merge2@1.4.1": { - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dependencies": {} - }, - "micromatch@4.0.5": { - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dependencies": { - "braces": "braces@3.0.2", - "picomatch": "picomatch@2.3.1" - } - }, - "minimatch@9.0.3": { - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", - "dependencies": { - "brace-expansion": "brace-expansion@2.0.1" - } - }, - "mkdirp@3.0.1": { - "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", - "dependencies": {} - }, - "path-browserify@1.0.1": { - "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", - "dependencies": {} - }, - "picomatch@2.3.1": { - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dependencies": {} - }, - "queue-microtask@1.2.3": { - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dependencies": {} - }, - "reusify@1.0.4": { - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dependencies": {} - }, - "run-parallel@1.2.0": { - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dependencies": { - "queue-microtask": "queue-microtask@1.2.3" - } - }, - "to-regex-range@5.0.1": { - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dependencies": { - "is-number": "is-number@7.0.0" - } } } }, "remote": {}, "workspace": { "dependencies": [ + "jsr:@david/path@0.2", "jsr:@david/which-runtime@0.2", - "jsr:@david/which@^0.4.1", - "jsr:@deno/dnt@^0.41.1", - "jsr:@std/assert@^0.221.0", - "jsr:@std/fmt@^0.221.0", - "jsr:@std/fs@0.221.0", - "jsr:@std/io@0.221.0", - "jsr:@std/path@0.221.0", - "jsr:@std/streams@0.221.0" + "jsr:@david/which@~0.4.1", + "jsr:@deno/dnt@~0.41.3", + "jsr:@std/assert@1", + "jsr:@std/fmt@1", + "jsr:@std/fs@1", + "jsr:@std/io@0.221", + "jsr:@std/path@1", + "jsr:@std/streams@0.221" ] } } diff --git a/mod.test.ts b/mod.test.ts index 4879b17..4ccc1e2 100644 --- a/mod.test.ts +++ b/mod.test.ts @@ -1188,7 +1188,7 @@ Deno.test("command .lines('stderr')", async () => { Deno.test("command .lines('combined')", async () => { const result = await $`deno eval "console.log(1); console.error(2)"`.lines("combined"); - assertEquals(result, ["1", "2"]); + assertEquals(result.sort(), ["1", "2"]); }); Deno.test("piping in command", async () => { diff --git a/mod.ts b/mod.ts index 79ef0a5..659f5cf 100644 --- a/mod.ts +++ b/mod.ts @@ -37,14 +37,15 @@ import { type SelectOptions, } from "./src/console/mod.ts"; import { wasmInstance } from "./src/lib/mod.ts"; -import { createPath, Path } from "./src/path.ts"; + +import { Path } from "@david/path"; import { RequestBuilder, withProgressBarFactorySymbol } from "./src/request.ts"; import { outdent } from "./src/vendor/outdent.ts"; import { denoWhichRealEnv } from "./src/shell.ts"; +export { type DirEntry, FsFileWrapper, Path, type SymlinkOptions } from "@david/path"; export type { Delay, DelayIterator } from "./src/common.ts"; export { TimeoutError } from "./src/common.ts"; -export { FsFileWrapper, Path } from "./src/path.ts"; /** @deprecated Import `Path` instead. */ const PathRef = Path; // bug in deno: https://github.com/denoland/deno_lint/pull/1262 @@ -69,7 +70,6 @@ export type { PromptOptions, SelectOptions, } from "./src/console/mod.ts"; -export type { ExpandGlobOptions, PathSymlinkOptions, SymlinkOptions, WalkEntry, WalkOptions } from "./src/path.ts"; export type { Closer, Reader, ShellPipeReaderKind, ShellPipeWriterKind, WriterSync } from "./src/pipes.ts"; export { RequestBuilder, RequestResponse } from "./src/request.ts"; // these are used when registering commands @@ -553,8 +553,8 @@ async function withRetries( function cd(path: string | URL | ImportMeta | Path) { if (typeof path === "string" || path instanceof URL) { path = new Path(path); - } else if (!(path instanceof Path)) { - path = new Path(path satisfies ImportMeta).parentOrThrow(); + } else if (!(path instanceof Path) && typeof path?.url === "string") { + path = new Path(path.url).parentOrThrow(); } Deno.chdir(path.toString()); } @@ -886,3 +886,12 @@ export const $: $Type = build$FromState(buildInitial$State({ isGlobal: true, })); export default $; + +/** @internal */ +function createPath(path: string | URL | Path): Path { + if (path instanceof Path) { + return path; + } else { + return new Path(path); + } +} diff --git a/scripts/build_npm.ts b/scripts/build_npm.ts index 80dfb92..606f05c 100644 --- a/scripts/build_npm.ts +++ b/scripts/build_npm.ts @@ -1,7 +1,8 @@ import { build, emptyDir } from "@deno/dnt"; +import { walkSync } from "@std/fs/walk"; import $ from "../mod.ts"; -$.cd($.path(import.meta).parentOrThrow().parentOrThrow()); +$.cd($.path(import.meta.url).parentOrThrow().parentOrThrow()); await emptyDir("./npm"); @@ -20,6 +21,10 @@ await build({ "WritableStream", "TextDecoderStream", "TransformStream", + { + name: "StreamPipeOptions", + typeOnly: true, + }, { name: "ReadableStreamDefaultReader", typeOnly: true, @@ -60,9 +65,13 @@ await build({ }], }], }, + filterDiagnostic(diagnostic) { + return !diagnostic.file?.fileName.includes("@david/path/0.2.0/mod.ts") ?? true; + }, compilerOptions: { stripInternal: false, skipLibCheck: false, + lib: ["ESNext"], target: "ES2022", }, mappings: { @@ -86,7 +95,7 @@ await build({ "undici-types": "^5.26", }, devDependencies: { - "@types/node": "^20.11.9", + "@types/node": "^22.5.0", }, }, postBuild() { @@ -102,11 +111,11 @@ await $`deno run -A npm:esbuild@0.20.0 --bundle --platform=node --packages=exter const npmPath = $.path("npm"); // remove all the javascript files in the script folder -for (const entry of npmPath.join("script").walkSync({ includeDirs: false, exts: ["js"] })) { - entry.path.removeSync(); +for (const entry of walkSync(npmPath.join("script").toString(), { includeDirs: false, exts: ["js"] })) { + $.path(entry.path).removeSync(); } -for (const entry of npmPath.join("esm").walkSync({ includeDirs: false, exts: ["js"] })) { - entry.path.removeSync(); +for (const entry of walkSync(npmPath.join("esm").toString(), { includeDirs: false, exts: ["js"] })) { + $.path(entry.path).removeSync(); } // move the bundle to the script folder diff --git a/src/command.ts b/src/command.ts index af60c76..e043f0a 100644 --- a/src/command.ts +++ b/src/command.ts @@ -1,3 +1,4 @@ +import { FsFileWrapper, Path } from "@david/path"; import * as colors from "@std/fmt/colors"; import { Buffer } from "@std/io/buffer"; import * as path from "@std/path"; @@ -23,7 +24,6 @@ import { Box, delayToMs, errorToString, LoggerTreeBox } from "./common.ts"; import type { Delay } from "./common.ts"; import { symbols } from "./common.ts"; import { isShowingProgressBars } from "./console/progress/interval.ts"; -import { Path } from "./path.ts"; import { CapturingBufferWriter, CapturingBufferWriterSync, @@ -1328,6 +1328,8 @@ function templateInner( } return stream; }); + } else if (expr instanceof FsFileWrapper) { + handleReadableStream(() => expr.readable); } else if (expr instanceof Uint8Array) { handleReadableStream(() => { return new ReadableStream({ @@ -1384,6 +1386,8 @@ function templateInner( }, }); }); + } else if (expr instanceof FsFileWrapper) { + handleWritableStream(() => expr.writable); } else if ((expr as any)?.[symbols.writable]) { handleWritableStream(() => { const stream = (expr as any)[symbols.writable]?.(); diff --git a/src/path.test.ts b/src/path.test.ts deleted file mode 100644 index 5bc2c3f..0000000 --- a/src/path.test.ts +++ /dev/null @@ -1,968 +0,0 @@ -import { assert, assertEquals, assertRejects, assertThrows } from "@std/assert"; -import * as stdPath from "@std/path"; -import { isNode } from "which_runtime"; -import { createPath, Path } from "./path.ts"; -import { withTempDir } from "./with_temp_dir.ts"; - -Deno.test("create from path ref", () => { - const path = createPath("src"); - const path2 = createPath(path); - const path3 = new Path(path); - assertEquals(path, path2); - assertEquals(path.toString(), path3.toString()); -}); - -Deno.test("custom inspect", () => { - const path = createPath("src"); - assertEquals(Deno.inspect(path), 'Path("src")'); -}); - -Deno.test("equals", () => { - const path = createPath("src"); - assert(path.equals(createPath("src"))); - assert(!path.equals(createPath("src2"))); - assert(path.equals(createPath("src").resolve())); -}); - -Deno.test("join", () => { - const path = createPath("src"); - const newPath = path.join("other", "test"); - assertEquals(path.toString(), "src"); - assertEquals(newPath.toString(), stdPath.join("src", "other", "test")); -}); - -Deno.test("resolve", () => { - const path = createPath("src").resolve(); - assertEquals(path.toString(), stdPath.resolve("src")); -}); - -Deno.test("normalize", () => { - const path = createPath("src").normalize(); - assertEquals(path.toString(), stdPath.normalize("src")); -}); - -Deno.test("isDir", async () => { - await withTempDir((dir) => { - assert(dir.isDirSync()); - const file = dir.join("mod.ts"); - file.writeTextSync(""); - assert(!file.isDirSync()); - assert(!dir.join("nonExistent").isDirSync()); - }); -}); - -Deno.test("isFile", async () => { - await withTempDir((dir) => { - const file = dir.join("mod.ts"); - file.writeTextSync(""); - assert(!dir.isFileSync()); - assert(file.isFileSync()); - assert(!dir.join("nonExistent").isFileSync()); - }); -}); - -Deno.test("isSymlink", async () => { - await withTempDir(() => { - const file = createPath("file.txt").writeTextSync(""); - const symlinkFile = createPath("test.txt"); - symlinkFile.symlinkToSync(file, { kind: "absolute" }); - assert(symlinkFile.isSymlinkSync()); - assert(!file.isSymlinkSync()); - }); -}); - -Deno.test("isAbsolute", () => { - assert(!createPath("src").isAbsolute()); - assert(createPath("src").resolve().isAbsolute()); -}); - -Deno.test("isRelative", () => { - assert(createPath("src").isRelative()); - assert(!createPath("src").resolve().isRelative()); -}); - -Deno.test("parent", () => { - const parent = createPath("src").parent()!; - assertEquals(parent.toString(), Deno.cwd()); - const lastParent = Array.from(parent.ancestors()).at(-1)!; - assertEquals(lastParent.parent(), undefined); -}); - -Deno.test("parentOrThrow", () => { - const parent = createPath("src").parentOrThrow(); - assertEquals(parent.toString(), Deno.cwd()); - const lastParent = Array.from(parent.ancestors()).at(-1)!; - assertThrows(() => lastParent.parentOrThrow(), Error); -}); - -Deno.test("ancestors", () => { - const srcDir = createPath("src").resolve(); - let lastDir = srcDir; - for (const ancestor of srcDir.ancestors()) { - assert(ancestor.toString().length < lastDir.toString().length); - lastDir = ancestor; - } -}); - -Deno.test("components", () => { - { - const srcDir = createPath("src").resolve(); - const components = Array.from(srcDir.components()); - for (const component of components) { - assert(!component.includes("/")); - assert(!component.includes("\\")); - } - let expectedLength = srcDir.toString().split(stdPath.SEPARATOR).length; - if (Deno.build.os !== "windows") { - expectedLength--; // for leading slash - } - assertEquals(components.length, expectedLength); - } - { - const srcDir = createPath("../src"); - const components = Array.from(srcDir.components()); - assertEquals(components, ["..", "src"]); - } - { - const srcDir = createPath("./src"); - const components = Array.from(srcDir.components()); - assertEquals(components, ["src"]); - } - // trailing slash - { - const srcDir = createPath("./src/"); - const components = Array.from(srcDir.components()); - assertEquals(components, ["src"]); - } - - // current dir - { - const srcDir = createPath("."); - const components = Array.from(srcDir.components()); - // todo: is this correct? - assertEquals(components, ["."]); - } - - // empty dir - { - const srcDir = createPath("src/test//asdf"); - const components = Array.from(srcDir.components()); - assertEquals(components, ["src", "test", "asdf"]); // does the same as rust - } - - // windows prefix - { - const prefixed = createPath("\\\\?\\testing\\this\\out"); - const components = Array.from(prefixed.components()); - // todo: is this correct? - assertEquals(components, ["\\\\?\\testing", "this", "out"]); - } -}); - -Deno.test("startsWith", () => { - { - const srcDir = createPath("src").resolve(); - const thisDir = createPath(".").resolve(); - assert(srcDir.startsWith(thisDir)); - assert(!thisDir.startsWith(srcDir)); - } -}); - -Deno.test("endsWith", () => { - { - const srcDir = createPath("src").resolve(); - const thisDir = createPath(".").resolve(); - assert(!srcDir.endsWith(thisDir)); - assert(!thisDir.endsWith(srcDir)); - assert(srcDir.endsWith("src")); - } - // nested - { - const nestedDir = createPath("src/nested"); - assert(nestedDir.endsWith("src/nested")); - } - // trailing slash - { - const nestedDir = createPath("src/nested/"); - assert(nestedDir.endsWith("src/nested")); - assert(nestedDir.endsWith("src/nested/")); - } - // the same, then not - { - const nestedDir = createPath("src/nested").resolve(); - assert(!nestedDir.endsWith("test/src/nested")); - } -}); - -Deno.test("resolve", () => { - // there are more tests elsewhere - const srcDir = createPath("src").resolve(); - const assetsDir = srcDir.resolve("assets"); - assert(assetsDir.toString().endsWith("assets")); - - // should be the same object since it is known to be resolved - assert(assetsDir === assetsDir.resolve()); -}); - -Deno.test("known resolved", () => { - // there are more tests elsewhere - const srcDir = createPath("src").resolve(); - const newPath = createPath(srcDir.toString()); - - // should be the same object since the existing path was resolved - assert(newPath === newPath.resolve()); -}); - -Deno.test("stat", async () => { - const stat1 = await createPath("src").stat(); - assertEquals(stat1?.isDirectory, true); - const stat2 = await createPath("nonExistent").stat(); - assertEquals(stat2, undefined); - - await withTempDir(async () => { - const tempFile = createPath("temp.txt").writeTextSync(""); - const symlinkFile = createPath("other.txt"); - await symlinkFile.symlinkTo(tempFile, { kind: "absolute" }); - const stat3 = await symlinkFile.stat(); - assertEquals(stat3!.isFile, true); - assertEquals(stat3!.isSymlink, false); - }); -}); - -Deno.test("statSync", async () => { - const stat1 = createPath("src").statSync(); - assertEquals(stat1?.isDirectory, true); - const stat2 = createPath("nonExistent").statSync(); - assertEquals(stat2, undefined); - - await withTempDir(() => { - const tempFile = createPath("temp.txt").writeTextSync(""); - const symlinkFile = createPath("other.txt"); - symlinkFile.symlinkToSync(tempFile, { kind: "absolute" }); - const stat3 = symlinkFile.statSync(); - assertEquals(stat3!.isFile, true); - assertEquals(stat3!.isSymlink, false); - }); -}); - -Deno.test("lstat", async () => { - const stat1 = await createPath("src").lstat(); - assertEquals(stat1?.isDirectory, true); - const stat2 = await createPath("nonExistent").lstat(); - assertEquals(stat2, undefined); - - await withTempDir(async () => { - const symlinkFile = createPath("temp.txt"); - const otherFile = createPath("other.txt").writeTextSync(""); - // path ref - await symlinkFile.symlinkTo(otherFile, { kind: "absolute" }); - const stat3 = await symlinkFile.lstat(); - assertEquals(stat3!.isSymlink, true); - }); -}); - -Deno.test("lstatSync", async () => { - const stat1 = createPath("src").lstatSync(); - assertEquals(stat1?.isDirectory, true); - const stat2 = createPath("nonExistent").lstatSync(); - assertEquals(stat2, undefined); - - await withTempDir(() => { - const symlinkFile = createPath("temp.txt"); - const otherFile = createPath("other.txt").writeTextSync(""); - symlinkFile.symlinkToSync(otherFile, { kind: "absolute" }); - assertEquals(symlinkFile.lstatSync()!.isSymlink, true); - }); -}); - -Deno.test("withExtname", () => { - let path = createPath("src").resolve(); - path = path.join("temp", "other"); - assertEquals(path.basename(), "other"); - assertEquals(path.extname(), undefined); - path = path.withExtname("test"); - assertEquals(path.basename(), "other.test"); - path = path.withExtname("test2"); - assertEquals(path.basename(), "other.test2"); - assertEquals(path.extname(), ".test2"); - path = path.withExtname(".txt"); - assertEquals(path.basename(), "other.txt"); - assertEquals(path.extname(), ".txt"); - path = path.withExtname(""); - assertEquals(path.basename(), "other"); - assertEquals(path.extname(), undefined); - path = path.withExtname("txt"); - assertEquals(path.basename(), "other.txt"); - assertEquals(path.extname(), ".txt"); -}); - -Deno.test("withBasename", () => { - let path = createPath("src").resolve(); - path = path.join("temp", "other"); - assertEquals(path.basename(), "other"); - path = path.withBasename("test"); - assertEquals(path.basename(), "test"); - path = path.withBasename("other.asdf"); - assertEquals(path.basename(), "other.asdf"); -}); - -Deno.test("relative", () => { - const path1 = createPath("src"); - const path2 = createPath(".github"); - assertEquals( - path1.relative(path2), - Deno.build.os === "windows" ? "..\\.github" : "../.github", - ); -}); - -Deno.test("exists", async () => { - await withTempDir(async () => { - const file = createPath("file"); - assert(!await file.exists()); - assert(!file.existsSync()); - file.writeTextSync(""); - assert(await file.exists()); - assert(file.existsSync()); - }); -}); - -Deno.test("realpath", async () => { - await withTempDir(async (tempDir) => { - let file = tempDir.join("file").resolve(); - file.writeTextSync(""); - // need to do realPathSync for GH actions CI - file = file.realPathSync(); - // for the comparison, node doesn't canonicalize - // RUNNER~1 to runneradmin for some reason - if (isNode && Deno.build.os === "windows") { - file = createPath(file.toString().replace("\\RUNNER~1\\", "\\runneradmin\\")); - } - const symlink = createPath("other"); - symlink.symlinkToSync(file, { kind: "absolute" }); - assertEquals( - (await symlink.realPath()).toString(), - file.toString(), - ); - assertEquals( - symlink.realPathSync().toString(), - file.toString(), - ); - }); -}); - -Deno.test("mkdir", async () => { - await withTempDir(async () => { - const path = createPath("dir"); - await path.mkdir(); - assert(path.isDirSync()); - path.removeSync(); - assert(!path.isDirSync()); - path.mkdirSync(); - assert(path.isDirSync()); - const nestedDir = path.join("subdir", "subsubdir", "sub"); - await assertRejects(() => nestedDir.mkdir({ recursive: false })); - assertThrows(() => nestedDir.mkdirSync({ recursive: false })); - assert(!nestedDir.parentOrThrow().existsSync()); - await nestedDir.mkdir(); - assert(nestedDir.existsSync()); - await path.remove({ recursive: true }); - assert(!path.existsSync()); - nestedDir.mkdirSync(); - assert(nestedDir.existsSync()); - }); -}); - -Deno.test("symlinkTo", async () => { - await withTempDir(async () => { - const destFile = createPath("temp.txt").writeTextSync(""); - const symlinkFile = destFile.parentOrThrow().join("other.txt"); - await symlinkFile.symlinkTo(destFile, { - kind: "absolute", - }); - const stat = await symlinkFile.stat(); - assertEquals(stat!.isFile, true); - assertEquals(stat!.isSymlink, false); - assert(symlinkFile.isSymlinkSync()); - - // invalid - await assertRejects( - async () => { - // @ts-expect-error should require an options bag - await symlinkFile.symlinkTo(destFile); - }, - Error, - "Please specify if this symlink is absolute or relative. Otherwise provide the target text.", - ); - }); -}); - -Deno.test("symlinkToSync", async () => { - await withTempDir(() => { - const destFile = createPath("temp.txt").writeTextSync(""); - const symlinkFile = destFile.parentOrThrow().join("other.txt"); - - // path ref - symlinkFile.symlinkToSync(destFile, { kind: "absolute" }); - const stat = symlinkFile.statSync(); - assertEquals(stat!.isFile, true); - assertEquals(stat!.isSymlink, false); - assert(symlinkFile.isSymlinkSync()); - - // path ref absolute - symlinkFile.removeSync(); - symlinkFile.symlinkToSync(destFile, { kind: "absolute" }); - assertEquals(symlinkFile.statSync()!.isFile, true); - - // path ref relative - symlinkFile.removeSync(); - symlinkFile.symlinkToSync(destFile, { kind: "relative" }); - assertEquals(symlinkFile.statSync()!.isFile, true); - - // relative text - symlinkFile.removeSync(); - symlinkFile.symlinkToSync("temp.txt"); - assertEquals(symlinkFile.statSync()!.isFile, true); - - // invalid - assertThrows( - () => { - // @ts-expect-error should require an options bag - symlinkFile.symlinkToSync(destFile); - }, - Error, - "Please specify if this symlink is absolute or relative. Otherwise provide the target text.", - ); - }); -}); - -Deno.test("linkTo", async () => { - await withTempDir(async () => { - const destFile = createPath("temp.txt").writeTextSync("data"); - - // async - { - const hardlinkFile = destFile.parentOrThrow().join("other.txt"); - await hardlinkFile.linkTo(destFile); - const stat = hardlinkFile.statSync(); - assertEquals(stat!.isFile, true); - assertEquals(stat!.isSymlink, false); - assert(!hardlinkFile.isSymlinkSync()); - assertEquals(hardlinkFile.readTextSync(), "data"); - } - - // sync - { - const hardlinkFile = destFile.parentOrThrow().join("sync.txt"); - hardlinkFile.linkToSync(destFile); - assertEquals(hardlinkFile.readTextSync(), "data"); - } - }); -}); - -Deno.test("readDir", async () => { - await withTempDir(async () => { - const dir = createPath(".").resolve(); - dir.join("file1").writeTextSync(""); - dir.join("file2").writeTextSync(""); - - const entries1 = []; - for await (const entry of dir.readDir()) { - entries1.push(entry); - } - const entries2 = Array.from(dir.readDirSync()); - entries1.sort((a, b) => a.name.localeCompare(b.name)); - entries2.sort((a, b) => a.name.localeCompare(b.name)); - - for (const entries of [entries1, entries2]) { - assertEquals(entries.length, 2); - assertEquals(entries[0].name, "file1"); - assertEquals(entries[1].name, "file2"); - } - - const filePaths1 = []; - for await (const path of dir.readDirFilePaths()) { - filePaths1.push(path.toString()); - } - const filePaths2 = Array.from(dir.readDirFilePathsSync()).map((p) => p.toString()); - filePaths1.sort(); - filePaths2.sort(); - assertEquals(filePaths1, filePaths2); - assertEquals(filePaths1, entries1.map((entry) => entry.path.toString())); - assertEquals(filePaths1, entries2.map((entry) => entry.path.toString())); - }); -}); - -Deno.test("expandGlob", async () => { - await withTempDir(async () => { - const dir = createPath(".").resolve(); - dir.join("file1").writeTextSync(""); - dir.join("file2").writeTextSync(""); - const subDir = dir.join("dir").join("subDir"); - subDir.join("file.txt").writeTextSync(""); - - const entries1 = []; - for await (const entry of dir.expandGlob("**/*.txt")) { - entries1.push(entry.path); - } - const entries2 = Array.from(dir.expandGlobSync("**/*.txt")).map((e) => e.path); - - assertEquals(entries1, entries2); - - assertEquals(entries1.length, 1); - assertEquals(entries1[0].basename(), "file.txt"); - - const subDir2 = dir.join("dir2"); - subDir2.join("other.txt").writeTextSync(""); - const entries3 = Array.from(subDir2.expandGlobSync("**/*.txt")).map((e) => e.path); - assertEquals(entries3.length, 1); - assertEquals(entries3[0].basename(), "other.txt"); - }); -}); - -Deno.test("walk", async () => { - await withTempDir(async () => { - const dir = createPath("rootDir").mkdirSync(); - dir.join("file1").writeTextSync(""); - dir.join("file2").writeTextSync(""); - const subDir = dir.join("dir").join("subDir"); - subDir.join("file.txt").writeTextSync(""); - - const entries1 = []; - for await (const entry of dir.walk()) { - entries1.push(entry.path); - } - const entries2 = Array.from(dir.walkSync()).map((e) => e.path); - - assertEquals(entries1[0].toString(), dir.resolve().toString()); - assertEquals(entries1, entries2); - assertEquals(entries1.length, 6); - const entryNames = entries1.map((e) => e.basename()); - assertEquals(entryNames.sort(), [ - "dir", - "file.txt", - "file1", - "file2", - "rootDir", - "subDir", - ]); - - const subDir2 = dir.join("dir2"); - subDir2.join("other.txt").writeTextSync(""); - const entries3 = Array.from(subDir2.walkSync()).map((e) => e.path); - assertEquals(entries3[0].toString(), subDir2.resolve().toString()); - assertEquals(entries3.length, 2); - assertEquals(entries3[0].basename(), "dir2"); - assertEquals(entries3[1].basename(), "other.txt"); - }); -}); - -Deno.test("readBytes", async () => { - await withTempDir(async () => { - const file = createPath("file.txt"); - const bytes = new TextEncoder().encode("asdf"); - file.writeSync(bytes); - assertEquals(file.readBytesSync(), bytes); - assertEquals(await file.readBytes(), bytes); - const nonExistent = createPath("not-exists"); - assertThrows(() => nonExistent.readBytesSync()); - await assertRejects(() => nonExistent.readBytes()); - }); -}); - -Deno.test("readMaybeBytes", async () => { - await withTempDir(async () => { - const file = createPath("file.txt"); - const bytes = new TextEncoder().encode("asdf"); - await file.write(bytes); - assertEquals(file.readMaybeBytesSync(), bytes); - assertEquals(await file.readMaybeBytes(), bytes); - const nonExistent = createPath("not-exists"); - assertEquals(await nonExistent.readMaybeText(), undefined); - assertEquals(nonExistent.readMaybeTextSync(), undefined); - }); -}); - -Deno.test("readText", async () => { - await withTempDir(async () => { - const file = createPath("file.txt"); - file.writeTextSync("asdf"); - assertEquals(file.readMaybeTextSync(), "asdf"); - assertEquals(await file.readMaybeText(), "asdf"); - const nonExistent = createPath("not-exists"); - assertThrows(() => nonExistent.readTextSync()); - await assertRejects(() => nonExistent.readText()); - }); -}); - -Deno.test("readMaybeText", async () => { - await withTempDir(async () => { - const file = createPath("file.txt"); - file.writeTextSync("asdf"); - assertEquals(file.readMaybeTextSync(), "asdf"); - assertEquals(await file.readMaybeText(), "asdf"); - const nonExistent = createPath("not-exists"); - assertEquals(await nonExistent.readMaybeText(), undefined); - assertEquals(nonExistent.readMaybeTextSync(), undefined); - }); -}); - -Deno.test("readJson", async () => { - await withTempDir(async () => { - const file = createPath("file.txt"); - file.writeJsonSync({ test: 123 }); - let data = file.readJsonSync(); - assertEquals(data, { test: 123 }); - data = await file.readJson(); - assertEquals(data, { test: 123 }); - }); -}); - -Deno.test("readMaybeJson", async () => { - await withTempDir(async () => { - const file = createPath("file.json"); - file.writeJsonSync({ test: 123 }); - let data = file.readMaybeJsonSync(); - assertEquals(data, { test: 123 }); - data = await file.readMaybeJson(); - assertEquals(data, { test: 123 }); - const nonExistent = createPath("not-exists"); - data = nonExistent.readMaybeJsonSync(); - assertEquals(data, undefined); - data = await nonExistent.readMaybeJson(); - assertEquals(data, undefined); - - file.writeTextSync("1 23 532lkjladf asd"); - assertThrows(() => file.readMaybeJsonSync(), Error, "Failed parsing JSON in 'file.json'."); - await assertRejects(() => file.readMaybeJson(), Error, "Failed parsing JSON in 'file.json'."); - }); -}); - -Deno.test("write", async () => { - await withTempDir(async (dir) => { - // these should all handle creating the directory when it doesn't exist - const file1 = dir.join("subDir1/file.txt"); - const file2 = dir.join("subDir2/file.txt"); - const file3 = dir.join("subDir3/file.txt"); - const file4 = dir.join("subDir4/file.txt"); - - await file1.writeText("test"); - assertEquals(file1.readTextSync(), "test"); - - file2.writeTextSync("test"); - assertEquals(file2.readTextSync(), "test"); - file2.writeTextSync("\ntest", { append: true }); - assertEquals(file2.readTextSync(), "test\ntest"); - - await file3.write(new TextEncoder().encode("test")); - assertEquals(file3.readTextSync(), "test"); - - file4.writeSync(new TextEncoder().encode("test")); - assertEquals(file4.readTextSync(), "test"); - - // writing on top of a file it should return the original not found error - const fileOnFile = file1.join("fileOnFile"); - // windows will throw a NotFound error - const errorClass = Deno.build.os === "windows" ? Deno.errors.NotFound : Error; - await assertRejects(() => fileOnFile.writeText("asdf"), errorClass); - assertThrows(() => fileOnFile.writeTextSync("asdf"), errorClass); - }); -}); - -Deno.test("writeJson", async () => { - await withTempDir(async () => { - const path = createPath("file.json"); - await path.writeJson({ - prop: "test", - }); - assertEquals(path.readTextSync(), `{"prop":"test"}\n`); - await path.writeJson({ - prop: 1, - }); - // should truncate - assertEquals(path.readTextSync(), `{"prop":1}\n`); - - path.writeJsonSync({ - asdf: "test", - }); - assertEquals(path.readTextSync(), `{"asdf":"test"}\n`); - path.writeJsonSync({ - asdf: 1, - }); - // should truncate - assertEquals(path.readTextSync(), `{"asdf":1}\n`); - }); -}); - -Deno.test("writeJsonPretty", async () => { - await withTempDir(async () => { - const path = createPath("file.json"); - await path.writeJsonPretty({ - prop: "test", - }); - assertEquals(path.readTextSync(), `{\n "prop": "test"\n}\n`); - await path.writeJsonPretty({ - prop: 1, - }); - // should truncate - assertEquals(path.readTextSync(), `{\n "prop": 1\n}\n`); - - path.writeJsonPrettySync({ - asdf: "test", - }); - assertEquals(path.readTextSync(), `{\n "asdf": "test"\n}\n`); - path.writeJsonPrettySync({ - asdf: 1, - }); - // should truncate - assertEquals(path.readTextSync(), `{\n "asdf": 1\n}\n`); - }); -}); - -Deno.test("create", async () => { - await withTempDir(async () => { - const path = createPath("file.txt").writeTextSync("text"); - let file = await path.create(); - file.writeTextSync("asdf"); - file.close(); - path.removeSync(); - file = await path.create(); - file.close(); - file = path.createSync(); - file.writeTextSync("asdf"); - file.close(); - path.removeSync(); - file = path.createSync(); - file.close(); - }); -}); - -Deno.test("createNew", async () => { - await withTempDir(async () => { - const path = createPath("file.txt").writeTextSync("text"); - await assertRejects(() => path.createNew()); - path.removeSync(); - let file = await path.createNew(); - file.close(); - assertThrows(() => path.createNewSync()); - path.removeSync(); - file = path.createNewSync(); - file.close(); - }); -}); - -Deno.test("open", async () => { - await withTempDir(async () => { - const path = createPath("file.txt").writeTextSync("text"); - let file = await path.open({ write: true }); - await file.writeText("1"); - file.writeTextSync("2"); - file.close(); - file = path.openSync({ write: true, append: true }); - await file.writeBytes(new TextEncoder().encode("3")); - file.close(); - assertEquals(path.readTextSync(), "12xt3"); - }); -}); - -Deno.test("remove", async () => { - await withTempDir(async () => { - const path = createPath("file.txt").writeTextSync("text"); - assert(path.existsSync()); - assert(!path.removeSync().existsSync()); - path.writeTextSync("asdf"); - assert(path.existsSync()); - assert(!(await path.remove()).existsSync()); - }); -}); - -Deno.test("emptyDir", async () => { - await withTempDir(async (path) => { - const dir = path.join("subDir").mkdirSync(); - const file = dir.join("file.txt").writeTextSync("text"); - const subDir = dir.join("subDir").mkdirSync(); - subDir.join("test").writeTextSync(""); - assert((await dir.emptyDir()).existsSync()); - assert(!file.existsSync()); - assert(!subDir.existsSync()); - assert(dir.existsSync()); - }); -}); - -Deno.test("emptyDirSync", async () => { - await withTempDir((path) => { - const dir = path.join("subDir").mkdirSync(); - const file = dir.join("file.txt").writeTextSync("text"); - const subDir = dir.join("subDir").mkdirSync(); - subDir.join("test").writeTextSync(""); - assert(dir.emptyDirSync().existsSync()); - assert(!file.existsSync()); - assert(!subDir.existsSync()); - assert(dir.existsSync()); - }); -}); - -Deno.test("ensureDir", async () => { - await withTempDir(async (path) => { - const dir = path.join("subDir").mkdirSync(); - const file = dir.join("file.txt").writeTextSync("text"); - const subDir = dir.join("subDir").mkdirSync(); - subDir.join("test").writeTextSync(""); - assert((await dir.ensureDir()).existsSync()); - assert(file.existsSync()); - assert(subDir.existsSync()); - assert((await subDir.join("sub/sub").ensureDir()).existsSync()); - }); -}); - -Deno.test("ensureDirSync", async () => { - await withTempDir((path) => { - const dir = path.join("subDir").mkdirSync(); - const file = dir.join("file.txt").writeTextSync("text"); - const subDir = dir.join("subDir").mkdirSync(); - subDir.join("test").writeTextSync(""); - assert(dir.ensureDirSync().isDirSync()); - assert(file.existsSync()); - assert(subDir.existsSync()); - assert(subDir.join("sub/sub").ensureDirSync().isDirSync()); - }); -}); - -Deno.test("ensureFile", async () => { - await withTempDir(async (path) => { - const dir = path.join("subDir"); - const file = dir.join("file.txt"); - assert((await file.ensureFile()).isFileSync()); - assert(dir.join("sub.txt").ensureFileSync().isFileSync()); - }); -}); - -Deno.test("copy", async () => { - // file - await withTempDir(async () => { - const path = createPath("file.txt").writeTextSync("text"); - const newPath = await path.copy("other.txt"); - assert(path.existsSync()); - assert(newPath.existsSync()); - assertEquals(newPath.readTextSync(), "text"); - const newPath2 = path.copySync("other2.txt"); - assert(newPath2.existsSync()); - assertEquals(newPath2.readTextSync(), "text"); - }); - // directory - await withTempDir(async () => { - const dir = createPath("dir").mkdirSync(); - dir.join("file.txt").writeTextSync("text"); - const dir2 = createPath("dir2"); - await dir.copy(dir2); - assertEquals(dir2.join("file.txt").readTextSync(), "text"); - await assertRejects(() => dir.copy(dir2)); - assertEquals(dir2.join("file.txt").readTextSync(), "text"); - dir.join("file.txt").writeTextSync("text2"); - await dir.copy(dir2, { overwrite: true }); - assertEquals(dir2.join("file.txt").readTextSync(), "text2"); - }); -}); - -Deno.test("copyToDir", async () => { - await withTempDir(async () => { - const path = createPath("file.txt") - .writeTextSync("text"); - const dir = createPath("dir").mkdirSync(); - const newPath = await path.copyToDir(dir); - assert(path.existsSync()); - assert(newPath.existsSync()); - assertEquals(dir.join("file.txt").toString(), newPath.toString()); - assertEquals(newPath.readTextSync(), "text"); - const dir2 = createPath("dir2").mkdirSync(); - const newPath2 = path.copyToDirSync(dir2); - assert(newPath2.existsSync()); - assertEquals(newPath2.readTextSync(), "text"); - assertEquals(newPath2.toString(), dir2.join("file.txt").toString()); - }); -}); - -Deno.test("rename", async () => { - await withTempDir(async () => { - const path = createPath("file.txt").writeTextSync(""); - const newPath = path.renameSync("other.txt"); - assert(!path.existsSync()); - assert(newPath.existsSync()); - path.writeTextSync(""); - const newPath2 = await path.rename("other2.txt"); - assert(!path.existsSync()); - assert(newPath2.existsSync()); - }); -}); - -Deno.test("renameToDir", async () => { - await withTempDir(async () => { - const path = createPath("file.txt") - .writeTextSync("text"); - const dir = createPath("dir").mkdirSync(); - const newPath = await path.renameToDir(dir); - assert(!path.existsSync()); - assert(newPath.existsSync()); - assertEquals(dir.join("file.txt").toString(), newPath.toString()); - assertEquals(newPath.readTextSync(), "text"); - const dir2 = createPath("dir2").mkdirSync(); - const newPath2 = newPath.renameToDirSync(dir2); - assert(!newPath.existsSync()); - assert(newPath2.existsSync()); - assertEquals(newPath2.readTextSync(), "text"); - assertEquals(newPath2.toString(), dir2.join("file.txt").toString()); - - // now try a directory - await dir2.renameToDir(dir); - assert(dir.join("dir2").join("file.txt").existsSync()); - - // try a directory sync - const subDir = dir.join("subdir"); - subDir.mkdirSync(); - dir.join("dir2").renameToDirSync(subDir); - assert(subDir.join("dir2").join("file.txt").existsSync()); - }); -}); - -Deno.test("pipeTo", async () => { - await withTempDir(async () => { - const largeText = "asdf".repeat(100_000); - const textFile = createPath("file.txt").writeTextSync(largeText); - const otherFilePath = textFile.parentOrThrow().join("other.txt"); - const otherFile = otherFilePath.openSync({ write: true, create: true }); - await textFile.pipeTo(otherFile.writable); - assertEquals(otherFilePath.readTextSync(), largeText); - }); -}); - -Deno.test("instanceof check", () => { - class OtherPath { - // should match because of this - private static instanceofSymbol = Symbol.for("dax.Path"); - - static [Symbol.hasInstance](instance: any) { - return instance?.constructor?.instanceofSymbol === OtherPath.instanceofSymbol; - } - } - - assert(createPath("test") instanceof Path); - assert(!(new URL("https://example.com") instanceof Path)); - assert(new OtherPath() instanceof Path); - assert(createPath("test") instanceof OtherPath); -}); - -Deno.test("toFileUrl", () => { - const path = createPath(import.meta.url); - assertEquals(path.toString(), stdPath.fromFileUrl(import.meta.url)); - assertEquals(path.toFileUrl(), new URL(import.meta.url)); -}); - -Deno.test("append", async () => { - await withTempDir(async (path) => { - const file = path.join("file.txt"); - await file.append(new TextEncoder().encode("1\n")); - file.appendSync(new TextEncoder().encode("2\n")); - await file.appendText("3\n"); - file.appendTextSync("4\n"); - assertEquals(file.readTextSync(), "1\n2\n3\n4\n"); - }); -}); diff --git a/src/path.ts b/src/path.ts index d9e4d2b..e69de29 100644 --- a/src/path.ts +++ b/src/path.ts @@ -1,1267 +0,0 @@ -import { writeAll, writeAllSync } from "@std/io/write-all"; -import * as stdPath from "@std/path"; - -import { copy, copySync } from "@std/fs/copy"; -import { emptyDir, emptyDirSync } from "@std/fs/empty-dir"; -import { ensureDir, ensureDirSync } from "@std/fs/ensure-dir"; -import { ensureFile, ensureFileSync } from "@std/fs/ensure-file"; -import { expandGlob, expandGlobSync } from "@std/fs/expand-glob"; -import { walk, walkSync } from "@std/fs/walk"; -import { symbols } from "./common.ts"; - -/** - * `ExpandGlobOptions` from https://deno.land/std/fs/expand_glob.ts - * @internal - */ -type DenoStdExpandGlobOptions = import("@std/fs/expand-glob").ExpandGlobOptions; -export type ExpandGlobOptions = DenoStdExpandGlobOptions; -/** - * `WalkOptions` from https://deno.land/std/fs/walk.ts - * @internal - */ -type DenoStdWalkOptions = import("@std/fs/walk").WalkOptions; -export type WalkOptions = DenoStdWalkOptions; - -const PERIOD_CHAR_CODE = ".".charCodeAt(0); - -/** @internal */ -export function createPath(path: string | URL | ImportMeta | Path): Path { - if (path instanceof Path) { - return path; - } else { - return new Path(path); - } -} - -export interface WalkEntry extends Deno.DirEntry { - path: Path; -} - -export interface PathSymlinkOptions { - /** Creates the symlink as absolute or relative. */ - kind: "absolute" | "relative"; -} - -export interface SymlinkOptions extends Partial, Partial { -} - -/** - * Holds a reference to a path providing helper methods. - * - * Create one via `$`: `const srcDir = $.path("src");` - */ -export class Path { - readonly #path: string; - #knownResolved = false; - - /** This is a special symbol that allows different versions of - * Dax's `Path` API to match on `instanceof` checks. Ideally - * people shouldn't be mixing versions, but if it happens then - * this will maybe reduce some bugs (or cause some... tbd). - * @internal - */ - private static instanceofSymbol = Symbol.for("dax.Path"); - - constructor(path: string | URL | ImportMeta | Path) { - if (path instanceof URL) { - this.#path = stdPath.fromFileUrl(path); - } else if (path instanceof Path) { - this.#path = path.toString(); - } else if (typeof path === "string") { - if (path.startsWith("file://")) { - this.#path = stdPath.fromFileUrl(path); - } else { - this.#path = path; - } - } else { - this.#path = stdPath.fromFileUrl(path.url); - } - } - - /** @internal */ - static [Symbol.hasInstance](instance: any): boolean { - // this should never change because it should work accross versions - return instance?.constructor?.instanceofSymbol === Path.instanceofSymbol; - } - - /** @internal */ - [Symbol.for("Deno.customInspect")](): string { - return `Path("${this.#path}")`; - } - - /** @internal */ - [Symbol.for("nodejs.util.inspect.custom")](): string { - return `Path("${this.#path}")`; - } - - /** Gets the string representation of this path. */ - toString(): string { - return this.#path; - } - - /** Resolves the path and gets the file URL. */ - toFileUrl(): URL { - const resolvedPath = this.resolve(); - return stdPath.toFileUrl(resolvedPath.toString()); - } - - /** If this path reference is the same as another one. */ - equals(otherPath: Path): boolean { - return this.resolve().toString() === otherPath.resolve().toString(); - } - - /** Joins the provided path segments onto this path. */ - join(...pathSegments: string[]): Path { - return new Path(stdPath.join(this.#path, ...pathSegments)); - } - - /** Resolves this path to an absolute path along with the provided path segments. */ - resolve(...pathSegments: string[]): Path { - if (this.#knownResolved && pathSegments.length === 0) { - return this; - } - - const resolvedPath = stdPath.resolve(this.#path, ...pathSegments); - if (pathSegments.length === 0 && resolvedPath === this.#path) { - this.#knownResolved = true; - return this; - } else { - const pathRef = new Path(resolvedPath); - pathRef.#knownResolved = true; - return pathRef; - } - } - - /** - * Normalizes the `path`, resolving `'..'` and `'.'` segments. - * Note that resolving these segments does not necessarily mean that all will be eliminated. - * A `'..'` at the top-level will be preserved, and an empty path is canonically `'.'`. - */ - normalize(): Path { - return new Path(stdPath.normalize(this.#path)); - } - - /** Follows symlinks and gets if this path is a directory. */ - isDirSync(): boolean { - return this.statSync()?.isDirectory ?? false; - } - - /** Follows symlinks and gets if this path is a file. */ - isFileSync(): boolean { - return this.statSync()?.isFile ?? false; - } - - /** Gets if this path is a symlink. */ - isSymlinkSync(): boolean { - return this.lstatSync()?.isSymlink ?? false; - } - - /** Gets if this path is an absolute path. */ - isAbsolute(): boolean { - return stdPath.isAbsolute(this.#path); - } - - /** Gets if this path is relative. */ - isRelative(): boolean { - return !this.isAbsolute(); - } - - /** Resolves the `Deno.FileInfo` of this path following symlinks. */ - async stat(): Promise { - try { - return await Deno.stat(this.#path); - } catch (err) { - if (err instanceof Deno.errors.NotFound) { - return undefined; - } else { - throw err; - } - } - } - - /** Synchronously resolves the `Deno.FileInfo` of this - * path following symlinks. */ - statSync(): Deno.FileInfo | undefined { - try { - return Deno.statSync(this.#path); - } catch (err) { - if (err instanceof Deno.errors.NotFound) { - return undefined; - } else { - throw err; - } - } - } - - /** Resolves the `Deno.FileInfo` of this path without - * following symlinks. */ - async lstat(): Promise { - try { - return await Deno.lstat(this.#path); - } catch (err) { - if (err instanceof Deno.errors.NotFound) { - return undefined; - } else { - throw err; - } - } - } - - /** Synchronously resolves the `Deno.FileInfo` of this path - * without following symlinks. */ - lstatSync(): Deno.FileInfo | undefined { - try { - return Deno.lstatSync(this.#path); - } catch (err) { - if (err instanceof Deno.errors.NotFound) { - return undefined; - } else { - throw err; - } - } - } - - /** - * Gets the directory path. In most cases, it is recommended - * to use `.parent()` instead since it will give you a `Path`. - */ - dirname(): string { - return stdPath.dirname(this.#path); - } - - /** Gets the file or directory name of the path. */ - basename(): string { - return stdPath.basename(this.#path); - } - - /** Resolves the path getting all its ancestor directories in order. */ - *ancestors(): Generator { - let ancestor = this.parent(); - while (ancestor != null) { - yield ancestor; - ancestor = ancestor.parent(); - } - } - - *components(): Generator { - const path = this.normalize(); - let last_index = 0; - - // yield the prefix - if (path.#path.startsWith("\\\\?\\")) { - last_index = nextSlash(path.#path, 4); - if (last_index === -1) { - yield path.#path; - return; - } else { - yield path.#path.substring(0, last_index); - last_index += 1; // move past next slash - } - } else if (path.#path.startsWith("/")) { - // move past the initial slash - last_index += 1; - } - - while (true) { - const index = nextSlash(path.#path, last_index); - if (index < 0) { - const part = path.#path.substring(last_index); - if (part.length > 0) { - yield part; - } - return; - } - yield path.#path.substring(last_index, index); - last_index = index + 1; - } - - function nextSlash(path: string, start: number) { - for (let i = start; i < path.length; i++) { - const c = path.charCodeAt(i); - if (c === 47 || c === 92) { - return i; - } - } - return -1; - } - } - - // This is private because this doesn't handle stuff like `\\?\` at the start - // so it's only used internally with #endsWith for perf. API consumers should - // use .components() - *#rcomponents(): Generator { - const path = this.normalize(); - let last_index = undefined; - while (last_index == null || last_index > 0) { - const index = nextSlash(path.#path, last_index == null ? undefined : last_index - 1); - if (index < 0) { - const part = path.#path.substring(0, last_index); - if (part.length > 0) { - yield part; - } - return; - } - const part = path.#path.substring(index + 1, last_index); - if (last_index != null || part.length > 0) { - yield part; - } - last_index = index; - } - - function nextSlash(path: string, start: number | undefined) { - for (let i = start ?? path.length - 1; i >= 0; i--) { - const c = path.charCodeAt(i); - if (c === 47 || c === 92) { - return i; - } - } - return -1; - } - } - - startsWith(path: Path | URL | string): boolean { - const startsWithComponents = ensurePath(path).components(); - for (const component of this.components()) { - const next = startsWithComponents.next(); - if (next.done) { - return true; - } - if (next.value !== component) { - return false; - } - } - return startsWithComponents.next().done ?? true; - } - - endsWith(path: Path | URL | string): boolean { - const endsWithComponents = ensurePath(path).#rcomponents(); - for (const component of this.#rcomponents()) { - const next = endsWithComponents.next(); - if (next.done) { - return true; - } - if (next.value !== component) { - return false; - } - } - return endsWithComponents.next().done ?? true; - } - - /** Gets the parent directory or returns undefined if the parent is the root directory. */ - parent(): Path | undefined { - const resolvedPath = this.resolve(); - const dirname = resolvedPath.dirname(); - if (dirname === resolvedPath.#path) { - return undefined; - } else { - return new Path(dirname); - } - } - - /** Gets the parent or throws if the current directory was the root. */ - parentOrThrow(): Path { - const parent = this.parent(); - if (parent == null) { - throw new Error(`Cannot get the parent directory of '${this.#path}'.`); - } - return parent; - } - - /** - * Returns the extension of the path with leading period or undefined - * if there is no extension. - */ - extname(): string | undefined { - const extName = stdPath.extname(this.#path); - return extName.length === 0 ? undefined : extName; - } - - /** Gets a new path reference with the provided extension. */ - withExtname(ext: string): Path { - const currentExt = this.extname(); - const hasLeadingPeriod = ext.charCodeAt(0) === PERIOD_CHAR_CODE; - if (!hasLeadingPeriod && ext.length !== 0) { - ext = "." + ext; - } - return new Path(this.#path.substring(0, this.#path.length - (currentExt?.length ?? 0)) + ext); - } - - /** Gets a new path reference with the provided file or directory name. */ - withBasename(basename: string): Path { - const currentBaseName = this.basename(); - return new Path(this.#path.substring(0, this.#path.length - currentBaseName.length) + basename); - } - - /** Gets the relative path from this path to the specified path. */ - relative(to: string | URL | Path): string { - const toPath = ensurePath(to); - return stdPath.relative(this.resolve().#path, toPath.resolve().#path); - } - - /** Gets if the path exists. Beware of TOCTOU issues. */ - exists(): Promise { - return this.lstat().then((info) => info != null); - } - - /** Synchronously gets if the path exists. Beware of TOCTOU issues. */ - existsSync(): boolean { - return this.lstatSync() != null; - } - - /** Resolves to the absolute normalized path, with symbolic links resolved. */ - realPath(): Promise { - return Deno.realPath(this.#path).then((path) => new Path(path)); - } - - /** Synchronously resolves to the absolute normalized path, with symbolic links resolved. */ - realPathSync(): Path { - return new Path(Deno.realPathSync(this.#path)); - } - - /** Expands the glob using the current path as the root. */ - async *expandGlob( - glob: string | URL, - options?: Omit, - ): AsyncGenerator { - const entries = expandGlob(glob, { - root: this.resolve().toString(), - ...options, - }); - for await (const entry of entries) { - yield this.#stdWalkEntryToDax(entry); - } - } - - /** Synchronously expands the glob using the current path as the root. */ - *expandGlobSync( - glob: string | URL, - options?: Omit, - ): Generator { - const entries = expandGlobSync(glob, { - root: this.resolve().toString(), - ...options, - }); - for (const entry of entries) { - yield this.#stdWalkEntryToDax(entry); - } - } - - /** Walks the file tree rooted at the current path, yielding each file or - * directory in the tree filtered according to the given options. */ - async *walk(options?: WalkOptions): AsyncIterableIterator { - // Resolve the path before walking so that these paths always point to - // absolute paths in the case that someone changes the cwd after walking. - for await (const entry of walk(this.resolve().toString(), options)) { - yield this.#stdWalkEntryToDax(entry); - } - } - - /** Synchronously walks the file tree rooted at the current path, yielding each - * file or directory in the tree filtered according to the given options. */ - *walkSync(options?: WalkOptions): Iterable { - for (const entry of walkSync(this.resolve().toString(), options)) { - yield this.#stdWalkEntryToDax(entry); - } - } - - #stdWalkEntryToDax(entry: import("@std/fs/walk").WalkEntry): WalkEntry { - return { - ...entry, - path: new Path(entry.path), - }; - } - - /** Creates a directory at this path. - * @remarks By default, this is recursive. - */ - async mkdir(options?: Deno.MkdirOptions): Promise { - await Deno.mkdir(this.#path, { - recursive: true, - ...options, - }); - return this; - } - - /** Synchronously creates a directory at this path. - * @remarks By default, this is recursive. - */ - mkdirSync(options?: Deno.MkdirOptions): this { - Deno.mkdirSync(this.#path, { - recursive: true, - ...options, - }); - return this; - } - - /** - * Creates a symlink to the provided target path. - */ - async symlinkTo( - targetPath: URL | Path, - opts: Partial & PathSymlinkOptions, - ): Promise; - /** - * Creates a symlink at the provided path with the provided target text. - */ - async symlinkTo( - target: string, - opts?: SymlinkOptions, - ): Promise; - async symlinkTo( - target: string | URL | Path, - opts?: SymlinkOptions, - ): Promise { - await createSymlink(this.#resolveCreateSymlinkOpts(target, opts)); - } - - /** - * Synchronously creates a symlink to the provided target path. - */ - symlinkToSync( - targetPath: URL | Path, - opts: Partial & PathSymlinkOptions, - ): void; - /** - * Synchronously creates a symlink at the provided path with the provided target text. - */ - symlinkToSync( - target: string, - opts?: SymlinkOptions, - ): void; - symlinkToSync(target: string | URL | Path, opts?: SymlinkOptions): void { - createSymlinkSync(this.#resolveCreateSymlinkOpts(target, opts)); - } - - #resolveCreateSymlinkOpts(target: string | URL | Path, opts: SymlinkOptions | undefined): CreateSymlinkOpts { - if (opts?.kind == null) { - if (typeof target === "string") { - return { - fromPath: this.resolve(), - targetPath: ensurePath(target), - text: target, - type: opts?.type, - }; - } else { - throw new Error("Please specify if this symlink is absolute or relative. Otherwise provide the target text."); - } - } - const targetPath = ensurePath(target).resolve(); - if (opts?.kind === "relative") { - const fromPath = this.resolve(); - let relativePath: string; - if (fromPath.dirname() === targetPath.dirname()) { - // we don't want it to do `../basename` - relativePath = targetPath.basename(); - } else { - relativePath = fromPath.relative(targetPath); - } - return { - fromPath, - targetPath, - text: relativePath, - type: opts?.type, - }; - } else { - return { - fromPath: this.resolve(), - targetPath, - text: targetPath.#path, - type: opts?.type, - }; - } - } - - /** - * Creates a hardlink to the provided target path. - */ - async linkTo( - targetPath: string | URL | Path, - ): Promise { - const targetPathRef = ensurePath(targetPath).resolve(); - await Deno.link(targetPathRef.toString(), this.resolve().toString()); - } - - /** - * Synchronously creates a hardlink to the provided target path. - */ - linkToSync( - targetPath: string | URL | Path, - ): void { - const targetPathRef = ensurePath(targetPath).resolve(); - Deno.linkSync(targetPathRef.toString(), this.resolve().toString()); - } - - /** Reads the entries in the directory. */ - async *readDir(): AsyncIterable { - const dir = this.resolve(); - for await (const entry of Deno.readDir(dir.#path)) { - yield { - ...entry, - path: dir.join(entry.name), - }; - } - } - - /** Synchronously reads the entries in the directory. */ - *readDirSync(): Iterable { - const dir = this.resolve(); - for (const entry of Deno.readDirSync(dir.#path)) { - yield { - ...entry, - path: dir.join(entry.name), - }; - } - } - - /** Reads only the directory file paths, not including symlinks. */ - async *readDirFilePaths(): AsyncIterable { - const dir = this.resolve(); - for await (const entry of Deno.readDir(dir.#path)) { - if (entry.isFile) { - yield dir.join(entry.name); - } - } - } - - /** Synchronously reads only the directory file paths, not including symlinks. */ - *readDirFilePathsSync(): Iterable { - const dir = this.resolve(); - for (const entry of Deno.readDirSync(dir.#path)) { - if (entry.isFile) { - yield dir.join(entry.name); - } - } - } - - /** Reads the bytes from the file. */ - readBytes(options?: Deno.ReadFileOptions): Promise { - return Deno.readFile(this.#path, options); - } - - /** Synchronously reads the bytes from the file. */ - readBytesSync(): Uint8Array { - return Deno.readFileSync(this.#path); - } - - /** Calls `.readBytes()`, but returns undefined if the path doesn't exist. */ - readMaybeBytes(options?: Deno.ReadFileOptions): Promise { - return notFoundToUndefined(() => this.readBytes(options)); - } - - /** Calls `.readBytesSync()`, but returns undefined if the path doesn't exist. */ - readMaybeBytesSync(): Uint8Array | undefined { - return notFoundToUndefinedSync(() => this.readBytesSync()); - } - - /** Reads the text from the file. */ - readText(options?: Deno.ReadFileOptions): Promise { - return Deno.readTextFile(this.#path, options); - } - - /** Synchronously reads the text from the file. */ - readTextSync(): string { - return Deno.readTextFileSync(this.#path); - } - - /** Calls `.readText()`, but returns undefined when the path doesn't exist. - * @remarks This still errors for other kinds of errors reading a file. - */ - readMaybeText(options?: Deno.ReadFileOptions): Promise { - return notFoundToUndefined(() => this.readText(options)); - } - - /** Calls `.readTextSync()`, but returns undefined when the path doesn't exist. - * @remarks This still errors for other kinds of errors reading a file. - */ - readMaybeTextSync(): string | undefined { - return notFoundToUndefinedSync(() => this.readTextSync()); - } - - /** Reads and parses the file as JSON, throwing if it doesn't exist or is not valid JSON. */ - async readJson(options?: Deno.ReadFileOptions): Promise { - return this.#parseJson(await this.readText(options)); - } - - /** Synchronously reads and parses the file as JSON, throwing if it doesn't - * exist or is not valid JSON. */ - readJsonSync(): T { - return this.#parseJson(this.readTextSync()); - } - - #parseJson(text: string) { - try { - return JSON.parse(text) as T; - } catch (err) { - throw new Error(`Failed parsing JSON in '${this.toString()}'.`, { - cause: err, - }); - } - } - - /** - * Calls `.readJson()`, but returns undefined if the file doesn't exist. - * @remarks This method will still throw if the file cannot be parsed as JSON. - */ - readMaybeJson(options?: Deno.ReadFileOptions): Promise { - return notFoundToUndefined(() => this.readJson(options)); - } - - /** - * Calls `.readJsonSync()`, but returns undefined if the file doesn't exist. - * @remarks This method will still throw if the file cannot be parsed as JSON. - */ - readMaybeJsonSync(): T | undefined { - return notFoundToUndefinedSync(() => this.readJsonSync()); - } - - /** Writes out the provided bytes to the file. */ - async write(data: Uint8Array, options?: Deno.WriteFileOptions): Promise { - await this.#withFileForWriting(options, (file) => file.write(data)); - return this; - } - - /** Synchronously writes out the provided bytes to the file. */ - writeSync(data: Uint8Array, options?: Deno.WriteFileOptions): this { - this.#withFileForWritingSync(options, (file) => { - file.writeSync(data); - }); - return this; - } - - /** Writes out the provided text to the file. */ - async writeText(text: string, options?: Deno.WriteFileOptions): Promise { - await this.#withFileForWriting(options, (file) => file.writeText(text)); - return this; - } - - /** Synchronously writes out the provided text to the file. */ - writeTextSync(text: string, options?: Deno.WriteFileOptions): this { - this.#withFileForWritingSync(options, (file) => { - file.writeTextSync(text); - }); - return this; - } - - /** Writes out the provided object as compact JSON. */ - async writeJson(obj: unknown, options?: Deno.WriteFileOptions): Promise { - const text = JSON.stringify(obj); - await this.#writeTextWithEndNewLine(text, options); - return this; - } - - /** Synchronously writes out the provided object as compact JSON. */ - writeJsonSync(obj: unknown, options?: Deno.WriteFileOptions): this { - const text = JSON.stringify(obj); - this.#writeTextWithEndNewLineSync(text, options); - return this; - } - - /** Writes out the provided object as formatted JSON. */ - async writeJsonPretty(obj: unknown, options?: Deno.WriteFileOptions): Promise { - const text = JSON.stringify(obj, undefined, 2); - await this.#writeTextWithEndNewLine(text, options); - return this; - } - - /** Synchronously writes out the provided object as formatted JSON. */ - writeJsonPrettySync(obj: unknown, options?: Deno.WriteFileOptions): this { - const text = JSON.stringify(obj, undefined, 2); - this.#writeTextWithEndNewLineSync(text, options); - return this; - } - - #writeTextWithEndNewLine(text: string, options: Deno.WriteFileOptions | undefined) { - return this.#withFileForWriting(options, async (file) => { - await file.writeText(text); - await file.writeText("\n"); - }); - } - - /** Appends the provided bytes to the file. */ - async append(data: Uint8Array, options?: Omit): Promise { - await this.#withFileForAppending(options, (file) => file.write(data)); - return this; - } - - /** Synchronously appends the provided bytes to the file. */ - appendSync(data: Uint8Array, options?: Omit): this { - this.#withFileForAppendingSync(options, (file) => { - file.writeSync(data); - }); - return this; - } - - /** Appends the provided text to the file. */ - async appendText(text: string, options?: Omit): Promise { - await this.#withFileForAppending(options, (file) => file.writeText(text)); - return this; - } - - /** Synchronously appends the provided text to the file. */ - appendTextSync(text: string, options?: Omit): this { - this.#withFileForAppendingSync(options, (file) => { - file.writeTextSync(text); - }); - return this; - } - - #withFileForAppending( - options: Omit | undefined, - action: (file: FsFileWrapper) => Promise, - ) { - return this.#withFileForWriting({ - append: true, - ...options, - }, action); - } - - async #withFileForWriting( - options: Deno.WriteFileOptions | undefined, - action: (file: FsFileWrapper) => Promise, - ) { - const file = await this.#openFileMaybeCreatingDirectory({ - write: true, - create: true, - truncate: options?.append !== true, - ...options, - }); - try { - return await action(file); - } finally { - try { - file.close(); - } catch { - // ignore - } - } - } - - /** Opens a file, but handles if the directory does not exist. */ - async #openFileMaybeCreatingDirectory(options: Deno.OpenOptions) { - const resolvedPath = this.resolve(); // pre-resolve before going async in case the cwd changes - try { - return await resolvedPath.open(options); - } catch (err) { - if (err instanceof Deno.errors.NotFound) { - // attempt to create the parent directory when it doesn't exist - const parent = resolvedPath.parent(); - if (parent != null) { - try { - await parent.mkdir(); - } catch { - throw err; // throw the original error - } - } - return await resolvedPath.open(options); - } else { - throw err; - } - } - } - - #writeTextWithEndNewLineSync(text: string, options: Deno.WriteFileOptions | undefined) { - this.#withFileForWritingSync(options, (file) => { - file.writeTextSync(text); - file.writeTextSync("\n"); - }); - } - - #withFileForAppendingSync( - options: Omit | undefined, - action: (file: FsFileWrapper) => T, - ) { - return this.#withFileForWritingSync({ - append: true, - ...options, - }, action); - } - - #withFileForWritingSync(options: Deno.WriteFileOptions | undefined, action: (file: FsFileWrapper) => T) { - const file = this.#openFileForWritingSync(options); - try { - return action(file); - } finally { - try { - file.close(); - } catch { - // ignore - } - } - } - - /** Opens a file for writing, but handles if the directory does not exist. */ - #openFileForWritingSync(options: Deno.WriteFileOptions | undefined) { - return this.#openFileMaybeCreatingDirectorySync({ - write: true, - create: true, - truncate: options?.append !== true, - ...options, - }); - } - - /** Opens a file for writing, but handles if the directory does not exist. */ - #openFileMaybeCreatingDirectorySync(options: Deno.OpenOptions) { - try { - return this.openSync(options); - } catch (err) { - if (err instanceof Deno.errors.NotFound) { - // attempt to create the parent directory when it doesn't exist - const parent = this.resolve().parent(); - if (parent != null) { - try { - parent.mkdirSync(); - } catch { - throw err; // throw the original error - } - } - return this.openSync(options); - } else { - throw err; - } - } - } - - /** Changes the permissions of the file or directory. */ - async chmod(mode: number): Promise { - await Deno.chmod(this.#path, mode); - return this; - } - - /** Synchronously changes the permissions of the file or directory. */ - chmodSync(mode: number): this { - Deno.chmodSync(this.#path, mode); - return this; - } - - /** Changes the ownership permissions of the file. */ - async chown(uid: number | null, gid: number | null): Promise { - await Deno.chown(this.#path, uid, gid); - return this; - } - - /** Synchronously changes the ownership permissions of the file. */ - chownSync(uid: number | null, gid: number | null): this { - Deno.chownSync(this.#path, uid, gid); - return this; - } - - /** Creates a new file or opens the existing one. */ - create(): Promise { - return Deno.create(this.#path) - .then((file) => createFsFileWrapper(file)); - } - - /** Synchronously creates a new file or opens the existing one. */ - createSync(): FsFileWrapper { - return createFsFileWrapper(Deno.createSync(this.#path)); - } - - /** Creates a file throwing if a file previously existed. */ - createNew(): Promise { - return this.open({ - createNew: true, - read: true, - write: true, - }); - } - - /** Synchronously creates a file throwing if a file previously existed. */ - createNewSync(): FsFileWrapper { - return this.openSync({ - createNew: true, - read: true, - write: true, - }); - } - - /** Opens a file. */ - open(options?: Deno.OpenOptions): Promise { - return Deno.open(this.#path, options) - .then((file) => createFsFileWrapper(file)); - } - - /** Opens a file synchronously. */ - openSync(options?: Deno.OpenOptions): FsFileWrapper { - return createFsFileWrapper(Deno.openSync(this.#path, options)); - } - - /** Removes the file or directory from the file system. */ - async remove(options?: Deno.RemoveOptions): Promise { - await Deno.remove(this.#path, options); - return this; - } - - /** Removes the file or directory from the file system synchronously. */ - removeSync(options?: Deno.RemoveOptions): this { - Deno.removeSync(this.#path, options); - return this; - } - - /** - * Ensures that a directory is empty. - * Deletes directory contents if the directory is not empty. - * If the directory does not exist, it is created. - * The directory itself is not deleted. - */ - async emptyDir(): Promise { - await emptyDir(this.toString()); - return this; - } - - /** Synchronous version of `emptyDir()` */ - emptyDirSync(): this { - emptyDirSync(this.toString()); - return this; - } - - /** Ensures that the directory exists. - * If the directory structure does not exist, it is created. Like mkdir -p. - */ - async ensureDir(): Promise { - await ensureDir(this.toString()); - return this; - } - - /** Synchronously ensures that the directory exists. - * If the directory structure does not exist, it is created. Like mkdir -p. - */ - ensureDirSync(): this { - ensureDirSync(this.toString()); - return this; - } - - /** - * Ensures that the file exists. - * If the file that is requested to be created is in directories that do - * not exist these directories are created. If the file already exists, - * it is NOTMODIFIED. - */ - async ensureFile(): Promise { - await ensureFile(this.toString()); - return this; - } - - /** - * Synchronously ensures that the file exists. - * If the file that is requested to be created is in directories that do - * not exist these directories are created. If the file already exists, - * it is NOTMODIFIED. - */ - ensureFileSync(): this { - ensureFileSync(this.toString()); - return this; - } - - /** Copies a file or directory to the provided destination. - * @returns The destination file path. - */ - async copy(destinationPath: string | URL | Path, options?: { overwrite?: boolean }): Promise { - const pathRef = ensurePath(destinationPath); - await copy(this.#path, pathRef.#path, options); - return pathRef; - } - - /** Copies a file or directory to the provided destination synchronously. - * @returns The destination file path. - */ - copySync(destinationPath: string | URL | Path, options?: { overwrite?: boolean }): Path { - const pathRef = ensurePath(destinationPath); - copySync(this.#path, pathRef.#path, options); - return pathRef; - } - - /** - * Copies the file or directory to the specified directory. - * @returns The destination file path. - */ - copyToDir(destinationDirPath: string | URL | Path, options?: { overwrite?: boolean }): Promise { - const destinationPath = ensurePath(destinationDirPath) - .join(this.basename()); - return this.copy(destinationPath, options); - } - - /** - * Copies the file or directory to the specified directory synchronously. - * @returns The destination file path. - */ - copyToDirSync(destinationDirPath: string | URL | Path, options?: { overwrite?: boolean }): Path { - const destinationPath = ensurePath(destinationDirPath) - .join(this.basename()); - return this.copySync(destinationPath, options); - } - - /** - * Moves the file or directory returning a promise that resolves to - * the renamed path. - */ - rename(newPath: string | URL | Path): Promise { - const pathRef = ensurePath(newPath); - return Deno.rename(this.#path, pathRef.#path).then(() => pathRef); - } - - /** - * Moves the file or directory returning the renamed path synchronously. - */ - renameSync(newPath: string | URL | Path): Path { - const pathRef = ensurePath(newPath); - Deno.renameSync(this.#path, pathRef.#path); - return pathRef; - } - - /** - * Moves the file or directory to the specified directory. - * @returns The destination file path. - */ - renameToDir(destinationDirPath: string | URL | Path): Promise { - const destinationPath = ensurePath(destinationDirPath) - .join(this.basename()); - return this.rename(destinationPath); - } - - /** - * Moves the file or directory to the specified directory synchronously. - * @returns The destination file path. - */ - renameToDirSync(destinationDirPath: string | URL | Path): Path { - const destinationPath = ensurePath(destinationDirPath) - .join(this.basename()); - return this.renameSync(destinationPath); - } - - /** Opens the file and pipes it to the writable stream. */ - async pipeTo(dest: WritableStream, options?: PipeOptions): Promise { - const file = await Deno.open(this.#path, { read: true }); - try { - await file.readable.pipeTo(dest, options); - } finally { - try { - file.close(); - } catch { - // ignore - } - } - return this; - } -} - -function ensurePath(path: string | URL | Path) { - return path instanceof Path ? path : new Path(path); -} - -async function createSymlink(opts: CreateSymlinkOpts) { - let kind = opts.type; - if (kind == null && Deno.build.os === "windows") { - const info = await opts.targetPath.lstat(); - if (info?.isDirectory) { - kind = "dir"; - } else if (info?.isFile) { - kind = "file"; - } else { - throw new Deno.errors.NotFound( - `The target path '${opts.targetPath}' did not exist or path kind could not be determined. ` + - `When the path doesn't exist, you need to specify a symlink type on Windows.`, - ); - } - } - - await Deno.symlink( - opts.text, - opts.fromPath.toString(), - kind == null ? undefined : { - type: kind, - }, - ); -} - -interface CreateSymlinkOpts { - fromPath: Path; - targetPath: Path; - text: string; - type: "file" | "dir" | undefined; -} - -function createSymlinkSync(opts: CreateSymlinkOpts) { - let kind = opts.type; - if (kind == null && Deno.build.os === "windows") { - const info = opts.targetPath.lstatSync(); - if (info?.isDirectory) { - kind = "dir"; - } else if (info?.isFile) { - kind = "file"; - } else { - throw new Deno.errors.NotFound( - `The target path '${opts.targetPath}' did not exist or path kind could not be determined. ` + - `When the path doesn't exist, you need to specify a symlink type on Windows.`, - ); - } - } - - Deno.symlinkSync( - opts.text, - opts.fromPath.toString(), - kind == null ? undefined : { - type: kind, - }, - ); -} - -function createFsFileWrapper(file: Deno.FsFile): FsFileWrapper { - Object.setPrototypeOf(file, FsFileWrapper.prototype); - return file as FsFileWrapper; -} - -export class FsFileWrapper extends Deno.FsFile { - [symbols.readable](): ReadableStream { - return this.readable; - } - - [symbols.writable](): WritableStream { - return this.writable; - } - - /** Writes the provided text to this file. */ - writeText(text: string): Promise { - return this.writeBytes(new TextEncoder().encode(text)); - } - - /** Synchronously writes the provided text to this file. */ - writeTextSync(text: string): this { - return this.writeBytesSync(new TextEncoder().encode(text)); - } - - /** Writes the provided bytes to the file. */ - async writeBytes(bytes: Uint8Array): Promise { - await writeAll(this, bytes); - return this; - } - - /** Synchronously writes the provided bytes to the file. */ - writeBytesSync(bytes: Uint8Array): this { - writeAllSync(this, bytes); - return this; - } -} - -async function notFoundToUndefined(action: () => Promise) { - try { - return await action(); - } catch (err) { - if (err instanceof Deno.errors.NotFound) { - return undefined; - } else { - throw err; - } - } -} - -function notFoundToUndefinedSync(action: () => T) { - try { - return action(); - } catch (err) { - if (err instanceof Deno.errors.NotFound) { - return undefined; - } else { - throw err; - } - } -} diff --git a/src/pipes.ts b/src/pipes.ts index 14f5b1e..b729707 100644 --- a/src/pipes.ts +++ b/src/pipes.ts @@ -1,9 +1,9 @@ +import type { FsFileWrapper, Path } from "@david/path"; import { Buffer } from "@std/io/buffer"; import { writeAll, writeAllSync } from "@std/io/write-all"; import type { CommandBuilder, KillSignal } from "./command.ts"; import { abortSignalToPromise } from "./common.ts"; import { logger } from "./console/logger.ts"; -import type { FsFileWrapper, Path } from "./path.ts"; import type { RequestBuilder } from "./request.ts"; const encoder = new TextEncoder(); diff --git a/src/request.test.ts b/src/request.test.ts index 74b3bc9..03f908f 100644 --- a/src/request.test.ts +++ b/src/request.test.ts @@ -148,8 +148,16 @@ Deno.test("$.request", (t) => { .showProgress() .pipeThrough(new TextDecoderStream()); const reader = readable.getReader(); - const result = await reader.read(); - assertEquals(result.value, "text".repeat(1000)); + let result = ""; + let done = false; + while (!done) { + const chunkResult = await reader.read(); + done = chunkResult.done; + if (chunkResult.value) { + result += chunkResult.value; + } + } + assertEquals(result, "text".repeat(1000)); }); step("pipeToPath", async () => { diff --git a/src/request.ts b/src/request.ts index f7316bc..b8a7f86 100644 --- a/src/request.ts +++ b/src/request.ts @@ -1,8 +1,8 @@ +import { Path } from "@david/path"; import { formatMillis, symbols } from "./common.ts"; import { TimeoutError } from "./common.ts"; import { type Delay, delayToMs, filterEmptyRecordValues, getFileNameFromUrl } from "./common.ts"; import type { ProgressBar } from "./console/mod.ts"; -import { Path } from "./path.ts"; interface RequestBuilderState { noThrow: boolean | number[]; diff --git a/src/with_temp_dir.ts b/src/with_temp_dir.ts index a90922a..bcae32e 100644 --- a/src/with_temp_dir.ts +++ b/src/with_temp_dir.ts @@ -1,4 +1,4 @@ -import { createPath, type Path } from "./path.ts"; +import { Path } from "@david/path"; /** * Creates a temporary directory, changes the cwd to this directory, @@ -6,14 +6,14 @@ import { createPath, type Path } from "./path.ts"; */ export async function withTempDir(action: (path: Path) => Promise | void) { await using dirPath = usingTempDir(); - await action(createPath(dirPath).resolve()); + await action(new Path(dirPath).resolve()); } export function usingTempDir(): Path & AsyncDisposable { const originalDirPath = Deno.cwd(); const dirPath = Deno.makeTempDirSync(); Deno.chdir(dirPath); - const pathRef = createPath(dirPath).resolve(); + const pathRef = new Path(dirPath).resolve(); (pathRef as any)[Symbol.asyncDispose] = async () => { try { await Deno.remove(dirPath, { recursive: true }); From 667a6f1e36e529a87c3ec5e99b34a1f75a5038c9 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Sat, 7 Sep 2024 10:09:48 +0200 Subject: [PATCH 2/9] canary --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01079af..86a6d01 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,6 +26,7 @@ jobs: - name: Install deno uses: denoland/setup-deno@v1 + version: canary - uses: dprint/check@v2.1 if: runner.os == 'Linux' From 14eeed081828ded188684064fefa54e3c5774449 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Sat, 7 Sep 2024 10:17:17 +0200 Subject: [PATCH 3/9] fix --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 86a6d01..b5a1633 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,8 @@ jobs: - name: Install deno uses: denoland/setup-deno@v1 - version: canary + with: + deno-version: canary - uses: dprint/check@v2.1 if: runner.os == 'Linux' From 604d5c78fc487f457e9afc5ca9c9559f247d8637 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Sat, 7 Sep 2024 10:23:46 +0200 Subject: [PATCH 4/9] update --- src/command.ts | 10 +++++----- src/request.ts | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/command.ts b/src/command.ts index e043f0a..fda851b 100644 --- a/src/command.ts +++ b/src/command.ts @@ -57,7 +57,7 @@ class Deferred { interface ShellPipeWriterKindWithOptions { kind: ShellPipeWriterKind; - options?: PipeOptions; + options?: StreamPipeOptions; } interface CommandBuilderStateCommand { @@ -348,8 +348,8 @@ export class CommandBuilder implements PromiseLike { /** Set the stdout kind. */ stdout(kind: ShellPipeWriterKind): CommandBuilder; - stdout(kind: WritableStream, options?: PipeOptions): CommandBuilder; - stdout(kind: ShellPipeWriterKind, options?: PipeOptions): CommandBuilder { + stdout(kind: WritableStream, options?: StreamPipeOptions): CommandBuilder; + stdout(kind: ShellPipeWriterKind, options?: StreamPipeOptions): CommandBuilder { return this.#newWithState((state) => { if (state.combinedStdoutStderr && kind !== "piped" && kind !== "inheritPiped") { throw new Error( @@ -369,8 +369,8 @@ export class CommandBuilder implements PromiseLike { /** Set the stderr kind. */ stderr(kind: ShellPipeWriterKind): CommandBuilder; - stderr(kind: WritableStream, options?: PipeOptions): CommandBuilder; - stderr(kind: ShellPipeWriterKind, options?: PipeOptions): CommandBuilder { + stderr(kind: WritableStream, options?: StreamPipeOptions): CommandBuilder; + stderr(kind: ShellPipeWriterKind, options?: StreamPipeOptions): CommandBuilder { return this.#newWithState((state) => { if (state.combinedStdoutStderr && kind !== "piped" && kind !== "inheritPiped") { throw new Error( diff --git a/src/request.ts b/src/request.ts index b8a7f86..9431b9c 100644 --- a/src/request.ts +++ b/src/request.ts @@ -329,7 +329,7 @@ export class RequestBuilder implements PromiseLike { } /** Pipes the response body to the provided writable stream. */ - async pipeTo(dest: WritableStream, options?: PipeOptions): Promise { + async pipeTo(dest: WritableStream, options?: StreamPipeOptions): Promise { const response = await this.fetch(); return await response.pipeTo(dest, options); } @@ -582,7 +582,7 @@ export class RequestResponse { } /** Pipes the response body to the provided writable stream. */ - pipeTo(dest: WritableStream, options?: PipeOptions): Promise { + pipeTo(dest: WritableStream, options?: StreamPipeOptions): Promise { return this.#withReturnHandling(() => this.readable.pipeTo(dest, options)); } From fe639639f763a5cc262c529ef24206aba4fe270c Mon Sep 17 00:00:00 2001 From: David Sherret Date: Sat, 7 Sep 2024 10:25:52 +0200 Subject: [PATCH 5/9] update --- deno.json | 2 +- src/console/utils.ts | 6 ++---- src/request.test.ts | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/deno.json b/deno.json index 0134e1d..0052e6d 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "name": "@david/dax", - "nodeModulesDir": false, + "nodeModulesDir": "none", "tasks": { "test": "deno test -A", "wasmbuild": "deno run -A https://deno.land/x/wasmbuild@0.15.6/main.ts --sync --out ./src/lib" diff --git a/src/console/utils.ts b/src/console/utils.ts index a4700b3..8a7b105 100644 --- a/src/console/utils.ts +++ b/src/console/utils.ts @@ -79,11 +79,9 @@ function isTerminal(pipe: { isTerminal?(): boolean; rid?: number }) { return pipe.isTerminal(); } else if ( pipe.rid != null && - // deno-lint-ignore no-deprecated-deno-api - typeof Deno.isatty === "function" + typeof (Deno as any).isatty === "function" ) { - // deno-lint-ignore no-deprecated-deno-api - return Deno.isatty(pipe.rid); + return (Deno as any).isatty(pipe.rid); } else { throw new Error("Unsupported pipe."); } diff --git a/src/request.test.ts b/src/request.test.ts index 03f908f..3480f78 100644 --- a/src/request.test.ts +++ b/src/request.test.ts @@ -298,7 +298,7 @@ Deno.test("$.request", (t) => { let caughtErr: TimeoutError | undefined; try { await response.text(); - } catch (err) { + } catch (err: any) { caughtErr = err; } if (isNode) { From 4c699aca14ad51fafce812d0b7e1719f6fef510a Mon Sep 17 00:00:00 2001 From: David Sherret Date: Mon, 9 Sep 2024 18:28:43 -0400 Subject: [PATCH 6/9] update --- .github/workflows/ci.yml | 21 +++++++++++++++------ deno.json | 1 + 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01079af..89f5dd4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,9 +12,6 @@ jobs: if: | github.event_name == 'push' || !startsWith(github.event.pull_request.head.label, 'dsherret:') - permissions: - contents: read - id-token: write runs-on: ${{ matrix.os }} timeout-minutes: 30 strategy: @@ -26,6 +23,7 @@ jobs: - name: Install deno uses: denoland/setup-deno@v1 + version: canary - uses: dprint/check@v2.1 if: runner.os == 'Linux' @@ -48,11 +46,22 @@ jobs: - name: npm build run: deno run -A ./scripts/build_npm.ts ${{steps.get_tag_version.outputs.TAG_VERSION}} - - name: Publish to JSR on tag - if: runner.os == 'Linux' - run: deno run -A jsr:@david/publish-on-tag@0.1.3 - name: npm publish if: startsWith(github.ref, 'refs/tags/') && runner.os == 'Linux' env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: cd npm && npm publish + +jobs: + jsr: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v4 + - name: Install deno + uses: denoland/setup-deno@v1 + version: canary + - name: Publish to JSR on tag + run: deno run -A jsr:@david/publish-on-tag@0.1.3 diff --git a/deno.json b/deno.json index 0134e1d..216cb05 100644 --- a/deno.json +++ b/deno.json @@ -1,5 +1,6 @@ { "name": "@david/dax", + "version": "0.0.0", "nodeModulesDir": false, "tasks": { "test": "deno test -A", From e5be86c85d247fb50f1d299a27d144867e9ac944 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Mon, 9 Sep 2024 18:31:11 -0400 Subject: [PATCH 7/9] maybe fix --- .github/workflows/ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01c8c88..f33bc91 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,7 +62,8 @@ jobs: steps: - uses: actions/checkout@v4 - name: Install deno - uses: denoland/setup-deno@v1 - version: canary + uses: denoland/setup-deno@v1 + with: + deno-version: canary - name: Publish to JSR on tag run: deno run -A jsr:@david/publish-on-tag@0.1.3 From 54951fd8b2420e8b81e624fc4298bc270e7990ac Mon Sep 17 00:00:00 2001 From: David Sherret Date: Mon, 9 Sep 2024 18:31:36 -0400 Subject: [PATCH 8/9] fix --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f33bc91..7a52135 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,7 +53,6 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} run: cd npm && npm publish -jobs: jsr: runs-on: ubuntu-latest permissions: From 73b1c01dcb814e0c81c0b2c1578fb2043daa4c4d Mon Sep 17 00:00:00 2001 From: David Sherret Date: Mon, 9 Sep 2024 18:36:23 -0400 Subject: [PATCH 9/9] update --- deno.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deno.json b/deno.json index 216cb05..88c6cd3 100644 --- a/deno.json +++ b/deno.json @@ -1,7 +1,7 @@ { "name": "@david/dax", "version": "0.0.0", - "nodeModulesDir": false, + "nodeModulesDir": "none", "tasks": { "test": "deno test -A", "wasmbuild": "deno run -A https://deno.land/x/wasmbuild@0.15.6/main.ts --sync --out ./src/lib"