@williamthorsen/toolbelt.filesystem 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## 0.8.1 — 2026-08-28
|
|
6
|
+
|
|
7
|
+
### Refactoring
|
|
8
|
+
|
|
9
|
+
- Upgrade eslint-config-typescript to v12.0.1 and satisfy its new rules (#236)
|
|
10
|
+
|
|
11
|
+
Upgrades `@williamthorsen/eslint-config-typescript` to v12 and fixes violations surfaced by the new rules banning unpublished barrels and floating disposables.
|
|
12
|
+
|
|
13
|
+
## 0.8.0 — 2026-08-24
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
- Add a recursive listFiles to createTempTree's entry API (#212)
|
|
18
|
+
|
|
19
|
+
Adds `listFiles` to the handle `createTempTree` returns in `@williamthorsen/toolbelt.filesystem/candidate`. It reports every file below a tree-relative directory, at any depth, as sorted `/`-separated paths relative to that directory, whereas `list` reaches one level and reports names alone. A symlink below that directory is neither named nor descended, so every path in the result names a file held inside the tree.
|
|
20
|
+
|
|
5
21
|
## 0.7.0 — 2026-08-21
|
|
6
22
|
|
|
7
23
|
### Features
|
package/README.md
CHANGED
|
@@ -2,21 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Filesystem utilities for TypeScript and JavaScript.
|
|
4
4
|
|
|
5
|
-
<!-- section:release-notes -->
|
|
6
|
-
## Release notes — v0.7.0 (2026-08-21)
|
|
7
|
-
|
|
8
|
-
### Features
|
|
9
|
-
|
|
10
|
-
- 🚨 **Breaking:** Fix createTempTree's symlink guard and disposal, and complete its entry API (#207)
|
|
11
|
-
|
|
12
|
-
Fixes an issue where `TempTree.symlink` in `@williamthorsen/toolbelt.filesystem/candidate` refused a target outside the tree and rewrote a relative target to an absolute path inside it. The containment check now applies to the link path alone and the target is stored as given, so a link reads back as the string that was passed.
|
|
13
|
-
|
|
14
|
-
Separately, fixes an issue where disposing a `TempTree` left the tree on disk when one of its directories had been made read-only.
|
|
15
|
-
|
|
16
|
-
Adds six methods to `TempTree`. `writeAll` applies the constructor's map of entries to a tree already built; `exists`, `list`, `read`, `readJson`, and `rm` read the tree back and remove from it, so a suite scaffolding through the handle reads its own fixture through it rather than reaching for `node:fs`.
|
|
17
|
-
|
|
18
|
-
Migration: A caller relying on the previous absolute storage passes `tree.resolve(target)`, which is written unchanged.
|
|
19
|
-
<!-- /section:release-notes -->
|
|
5
|
+
<!-- section:release-notes --><!-- /section:release-notes -->
|
|
20
6
|
|
|
21
7
|
## Installation
|
|
22
8
|
|
|
@@ -300,6 +286,7 @@ interface TempTree extends Disposable {
|
|
|
300
286
|
readonly dir: string;
|
|
301
287
|
exists(entryPath: string): boolean;
|
|
302
288
|
list(entryPath?: string): string[];
|
|
289
|
+
listFiles(entryPath?: string): string[];
|
|
303
290
|
mkdir(entryPath: string): string;
|
|
304
291
|
read(entryPath: string): string;
|
|
305
292
|
readJson(entryPath: string): unknown;
|
|
@@ -349,19 +336,22 @@ tree.symlink('node_modules/.bin', tree.resolve('store/kit/bin')); // reads back
|
|
|
349
336
|
|
|
350
337
|
The link type is chosen from the target, which is where the one portability difference lives. An absolute directory target is linked as a junction, which Windows creates without the elevation a directory symlink needs; a relative directory target is linked as a directory, which needs that elevation, because Node normalizes a junction's target to an absolute path and would discard the relative string. Every other target, one that does not exist included, is linked as a file, matching what Node falls back to when no type is given.
|
|
351
338
|
|
|
352
|
-
`exists`, `list`, `read`, `readJson`, and `rm` read the tree back and remove from it, each through the same containment check:
|
|
339
|
+
`exists`, `list`, `listFiles`, `read`, `readJson`, and `rm` read the tree back and remove from it, each through the same containment check:
|
|
353
340
|
|
|
354
341
|
```ts
|
|
355
342
|
using tree = createTempTree({ 'packages/app/package.json': '{ "name": "app" }', 'packages/app/src/main.ts': 'export {};\n' });
|
|
356
343
|
|
|
357
344
|
tree.list(); // ['packages'], defaulting to the tree root
|
|
358
345
|
tree.list('packages/app'); // ['package.json', 'src'], sorted
|
|
346
|
+
tree.listFiles('packages'); // ['app/package.json', 'app/src/main.ts'], at any depth
|
|
359
347
|
tree.read('packages/app/src/main.ts'); // 'export {};\n'
|
|
360
348
|
tree.readJson('packages/app/package.json'); // unknown, for the caller to narrow
|
|
361
349
|
tree.exists('packages/app/tsconfig.json'); // false
|
|
362
350
|
tree.rm('packages/app');
|
|
363
351
|
```
|
|
364
352
|
|
|
353
|
+
`listFiles` reaches every depth and reports paths relative to the directory it was given, sorted, with `/` as the separator on every platform: a path a test asserts on is a value rather than a location, so `'app/src/main.ts'` should not vary by platform. It parts from `list` twice. A directory that is not there answers `[]` where `list` raises `ENOENT`, which is what lets a suite assert that a build emitted nothing without guarding the call; a path that exists as a file still raises `ENOTDIR`, as `list` does. And a symlink below the directory it was given is neither named nor descended, so every path in the result names a file held inside the tree, where `list` reports a link by name at its own level. The directory given as the argument is the exception, followed as `list`, `read`, and `exists` follow theirs: one naming a link out of the tree lists the target's files.
|
|
354
|
+
|
|
365
355
|
`read` returns UTF-8 text, and a missing entry raises `ENOENT` rather than answering emptily -- `exists` is the check. `readJson` returns `unknown`, so a caller narrows it rather than trusting an asserted type; contents that do not parse raise an error naming the entry, which the parse error alone does not. `exists` follows a symlink, so a dangling one answers `false`. `rm` is recursive and silent on an entry that is not there.
|
|
366
356
|
|
|
367
357
|
`writeJson` writes two-space-indented JSON ending in a newline, so a tree outliving a crashed run reads as a real config file would. A fixture needing exact bytes goes through `write` instead. A value `JSON.stringify` cannot represent -- `undefined`, a function, a symbol -- is refused rather than written, so an optional binding that arrived empty fails at the call that passed it instead of surfacing later as a parse error.
|
|
@@ -6,6 +6,7 @@ export interface TempTree extends Disposable {
|
|
|
6
6
|
readonly dir: string;
|
|
7
7
|
exists(entryPath: string): boolean;
|
|
8
8
|
list(entryPath?: string): string[];
|
|
9
|
+
listFiles(entryPath?: string): string[];
|
|
9
10
|
mkdir(entryPath: string): string;
|
|
10
11
|
read(entryPath: string): string;
|
|
11
12
|
readJson(entryPath: string): unknown;
|
|
@@ -18,6 +18,12 @@ export function createTempTree(entries, options = {}) {
|
|
|
18
18
|
function list(entryPath = '') {
|
|
19
19
|
return fs.readdirSync(resolveWithinTree(dir, [entryPath])).toSorted();
|
|
20
20
|
}
|
|
21
|
+
function listFiles(entryPath = '') {
|
|
22
|
+
const rootPath = resolveWithinTree(dir, [entryPath]);
|
|
23
|
+
if (!fs.existsSync(rootPath))
|
|
24
|
+
return [];
|
|
25
|
+
return listFilesBelow(rootPath, '').toSorted();
|
|
26
|
+
}
|
|
21
27
|
function mkdir(entryPath) {
|
|
22
28
|
const absolutePath = resolveWithinTree(dir, [entryPath]);
|
|
23
29
|
fs.mkdirSync(absolutePath, { recursive: true });
|
|
@@ -74,6 +80,7 @@ export function createTempTree(entries, options = {}) {
|
|
|
74
80
|
dir,
|
|
75
81
|
exists,
|
|
76
82
|
list,
|
|
83
|
+
listFiles,
|
|
77
84
|
mkdir,
|
|
78
85
|
read,
|
|
79
86
|
readJson,
|
|
@@ -109,6 +116,20 @@ function chooseLinkType(absoluteLinkPath, targetPath) {
|
|
|
109
116
|
}
|
|
110
117
|
return path.isAbsolute(targetPath) ? 'junction' : 'dir';
|
|
111
118
|
}
|
|
119
|
+
function listFilesBelow(dir, prefix) {
|
|
120
|
+
const paths = [];
|
|
121
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
122
|
+
for (const entry of entries) {
|
|
123
|
+
const relativePath = `${prefix}${entry.name}`;
|
|
124
|
+
if (entry.isFile()) {
|
|
125
|
+
paths.push(relativePath);
|
|
126
|
+
}
|
|
127
|
+
else if (entry.isDirectory()) {
|
|
128
|
+
paths.push(...listFilesBelow(path.join(dir, entry.name), `${relativePath}/`));
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return paths;
|
|
132
|
+
}
|
|
112
133
|
function restoreDirectoryPermissions(dir) {
|
|
113
134
|
fs.chmodSync(dir, 0o700);
|
|
114
135
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@williamthorsen/toolbelt.filesystem",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Filesystem utilities",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"config-cascade",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"CHANGELOG.md"
|
|
45
45
|
],
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@williamthorsen/toolbelt.errors": "0.6.
|
|
47
|
+
"@williamthorsen/toolbelt.errors": "0.6.2"
|
|
48
48
|
},
|
|
49
49
|
"engines": {
|
|
50
50
|
"node": ">=24.0.0"
|