Skip to content

Commit 084dd0c

Browse files
yangshunclaude
andcommitted
Add treeJson() API and improve public interface
- Split tree() (text) and treeJson() (structured JSON) into separate exports, removing the json option from Options - Change tree from default export to named export - Rename JsonNode to TreeJsonEntry with discriminated union type (directories have required contents, files do not) - Update README with new API docs and type definition - CLI wraps treeJson() result in array for Linux tree -J compatibility Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c434416 commit 084dd0c

5 files changed

Lines changed: 131 additions & 134 deletions

File tree

‎README.md‎

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -91,20 +91,55 @@ Options:
9191

9292
## Node.js API
9393

94+
### `tree(path, options?)`
95+
96+
Returns the tree as a formatted string.
97+
9498
```js
95-
import tree from 'tree-node-cli';
99+
import { tree } from 'tree-node-cli';
100+
101+
const string = tree('path/to/dir', {
102+
allFiles: true,
103+
exclude: [/node_modules/, /lcov/],
104+
maxDepth: 4,
105+
});
96106

97-
const string = tree('path/to/dir', options);
107+
console.log(string);
98108
```
99109

110+
### `treeJson(path, options?)`
111+
112+
Returns the tree as a structured `TreeJsonEntry` object (or `null` if the path is excluded).
113+
114+
```ts
115+
// Discriminated union — `contents` only exists on directories
116+
type TreeJsonEntry =
117+
| { type: 'directory'; name: string; contents: TreeJsonEntry[];
118+
size?: number; mode?: string; time?: string }
119+
| { type: 'file'; name: string;
120+
size?: number; mode?: string; time?: string }
121+
// size: present when `sizes: true`
122+
// mode: present when `permissions: true`
123+
// time: present when `date: true`
124+
```
125+
126+
```js
127+
import { treeJson } from 'tree-node-cli';
128+
129+
const result = treeJson('path/to/dir', { sizes: true });
130+
console.log(result);
131+
// { type: 'directory', name: 'dir', size: 4096, contents: [...] }
132+
```
133+
134+
### Options
135+
100136
`options` is a configuration object with the following fields:
101137

102138
| Field | Default | Type | Description |
103139
| --------------- | -------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------- |
104140
| `allFiles` | `false` | `boolean` | All files are printed. By default, tree does not print hidden files (those beginning with a dot). |
105141
| `date` | `false` | `boolean` | Show last modification time for each entry. |
106142
| `dirsFirst` | `false` | `boolean` | List directories before files. |
107-
| `json` | `false` | `boolean` | Output the tree as a JSON structure. |
108143
| `fullPath` | `false` | `boolean` | Print the full path prefix for each file. |
109144
| `noIndent` | `false` | `boolean` | Print entries without tree indentation lines. |
110145
| `permissions` | `false` | `boolean` | Show file type and permissions (e.g. `[drwxr-xr-x]`). |
@@ -121,18 +156,6 @@ const string = tree('path/to/dir', options);
121156
| `sortBy` | `'name'` | `string` | Sort the output. Options: `'name'`, `'version'`, `'mtime'`, `'ctime'`, `'size'`. |
122157
| `unsorted` | `false` | `boolean` | Do not sort. List files in directory order. |
123158

124-
```js
125-
import tree from 'tree-node-cli';
126-
127-
const string = tree('path/to/dir', {
128-
allFiles: true,
129-
exclude: [/node_modules/, /lcov/],
130-
maxDepth: 4,
131-
});
132-
133-
console.log(string);
134-
```
135-
136159
**Note:** To exclude the contents of a directory while retaining the directory itself, use a trailing slash with the `-I` option (e.g., `-I "node_modules/"`). For the Node.js API, provide a regex matching the directory contents (e.g., `/node_modules\//`). See [#33](https://github.com/yangshun/tree-node-cli/issues/33) for more details.
137160

138161
## License

‎src/cli.ts‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import { createRequire } from 'module';
44
import cac from 'cac';
5-
import tree, { type Options } from './index';
5+
import { type Options, tree, treeJson } from './index';
66

77
const require = createRequire(import.meta.url);
88
const { version } = require('../package.json');
@@ -83,7 +83,6 @@ const options: Options = {
8383
...((process.argv.includes('-i') || process.argv.includes('--no-indent')) && {
8484
noIndent: true,
8585
}),
86-
...(opts.json != null && { json: opts.json as boolean }),
8786
...(opts.permissions != null && { permissions: opts.permissions as boolean }),
8887
...(opts.prune != null && { prune: opts.prune as boolean }),
8988
...(opts.quote != null && { quote: opts.quote as boolean }),
@@ -103,5 +102,9 @@ const options: Options = {
103102
...(opts.unsorted != null && { unsorted: opts.unsorted as boolean }),
104103
};
105104

106-
const output = tree(path, options);
107-
console.log(output);
105+
if (opts.json) {
106+
const jsonOutput = treeJson(path, options);
107+
console.log(JSON.stringify([jsonOutput], null, 2));
108+
} else {
109+
console.log(tree(path, options));
110+
}

‎src/formats/json.ts‎

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,51 @@
11
import type { RequiredOptions, TreeNode } from '../index';
22
import { formatPermissions } from '../utils/format-permissions';
33

4-
export interface JsonNode {
5-
type: 'directory' | 'file';
4+
interface TreeJsonEntryBase {
65
name: string;
76
size?: number;
87
mode?: string;
98
time?: string;
10-
contents?: JsonNode[];
119
}
1210

11+
interface TreeJsonDirectoryEntry extends TreeJsonEntryBase {
12+
type: 'directory';
13+
contents: TreeJsonEntry[];
14+
}
15+
16+
interface TreeJsonFileEntry extends TreeJsonEntryBase {
17+
type: 'file';
18+
}
19+
20+
export type TreeJsonEntry = TreeJsonDirectoryEntry | TreeJsonFileEntry;
21+
1322
export function formatAsJson(
1423
node: TreeNode,
1524
options: RequiredOptions,
16-
): JsonNode {
17-
const jsonNode: JsonNode = {
18-
type: node.isDirectory ? 'directory' : 'file',
25+
): TreeJsonEntry {
26+
const base: TreeJsonEntryBase = {
1927
name: node.name,
2028
};
2129

2230
if (options.sizes) {
23-
jsonNode.size = node.size;
31+
base.size = node.size;
2432
}
2533

2634
if (options.permissions) {
27-
jsonNode.mode = formatPermissions(node.mode, node.isDirectory);
35+
base.mode = formatPermissions(node.mode, node.isDirectory);
2836
}
2937

3038
if (options.date) {
31-
jsonNode.time = `${node.mtime.toLocaleDateString('en-CA')} ${node.mtime.toLocaleTimeString('en-GB')}`;
39+
base.time = `${node.mtime.toLocaleDateString('en-CA')} ${node.mtime.toLocaleTimeString('en-GB')}`;
3240
}
3341

3442
if (node.isDirectory) {
35-
jsonNode.contents = node.children.map((child) =>
36-
formatAsJson(child, options),
37-
);
43+
return {
44+
...base,
45+
type: 'directory',
46+
contents: node.children.map((child) => formatAsJson(child, options)),
47+
};
3848
}
3949

40-
return jsonNode;
50+
return { ...base, type: 'file' };
4151
}

0 commit comments

Comments
 (0)