npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@devmc12/dir-tree

v1.0.1

Published

Headless directory tree reader, parser, renderer, and annotation toolkit.

Downloads

247

Readme

Dir Tree

面向浏览器与 Node.js 应用的 Headless 目录树工具集

npm version npm downloads License Node.js TypeScript

English | 简体中文

dir-tree 能把本地目录、ZIP 包、GitHub/GitLab 仓库、粘贴的树文本或内存数据,转换为稳定的 FileNode 树,并支持将其渲染为 ASCII、编辑、注释和导出。核心是纯函数且与运行时无关,同一套逻辑在 Node.js 和浏览器中都能运行,且不带任何 UI 依赖。

本包是 dir-tree.com 的 headless 版本,可在该站点体验完整应用。

Dir Tree Overview

特性

  • 多种数据源 —— 通过统一的适配器接口读取本地目录、ZIP 包、GitHub/GitLab 仓库、导入文本或内存树
  • 可配置的 ASCII 渲染 —— 连接符风格、缩进、行号、完整路径,以及文件大小 / 修改时间元数据
  • 纯函数式树编辑 —— 创建、重命名、移动、删除、聚焦、可见性与展开等操作返回新树,不修改你的状态
  • 导入与导出 —— 解析 JSON、XML、HTML、Markdown 或 ASCII 树文本,并导出回 JSON 或 ASCII
  • 注释 —— 与具体 provider 无关的请求 / diff / patch 辅助函数,支持逐节点注释;AI 或手动流程自行接入
  • 同构且模块化 —— 支持 tree-shaking 的子路径导出,提供 ESM、CommonJS 与类型声明,可在 Node.js 与浏览器中运行

安装

npm install @devmc12/dir-tree

环境要求:

  • Node.js >=18.18
  • TypeScript 用户可直接从包中导入类型
  • 核心与大部分适配器是同构的。Node.js 文件系统读取从 @devmc12/dir-tree/node 导出,浏览器专用 API 从 @devmc12/dir-tree/browser 导出

快速开始

import { FileSystemReader, InMemoryFileTreeAdapter } from '@devmc12/dir-tree';
import { renderAsciiTree } from '@devmc12/dir-tree/ascii';

const reader = new FileSystemReader(
  new InMemoryFileTreeAdapter({
    name: 'project',
    path: 'project',
    kind: 'directory',
    children: [
      {
        name: 'src',
        path: 'project/src',
        kind: 'directory',
        children: [
          {
            name: 'index.ts',
            path: 'project/src/index.ts',
            kind: 'file',
          },
        ],
      },
    ],
  })
);

const tree = await reader.read({
  depth: 3,
  exclude: ['node_modules', 'dist'],
  sort: { sortBy: 'name', order: 'asc', foldersFirst: true },
});

console.log(renderAsciiTree(tree));

数据源读取

内存树

当应用已经持有树形数据,或需要确定性的测试时,使用 InMemoryFileTreeAdapter

import { FileSystemReader, InMemoryFileTreeAdapter } from '@devmc12/dir-tree';

const tree = await new FileSystemReader(
  new InMemoryFileTreeAdapter({
    name: 'docs',
    path: 'docs',
    kind: 'directory',
    children: [{ name: 'api.md', path: 'docs/api.md', kind: 'file' }],
  })
).read();

Node.js 目录路径

使用 @devmc12/dir-tree/nodeNodeFileSystemAdapter 可以在 Node.js 中读取磁盘上的目录路径。它遵循同样的 depthexcludeuseGitignorereadFileMetasort 选项,并与浏览器入口隔离,打包器不会把 node:fs 带入客户端构建。

import { FileSystemReader } from '@devmc12/dir-tree';
import { NodeFileSystemAdapter } from '@devmc12/dir-tree/node';
import { renderAsciiTree } from '@devmc12/dir-tree/ascii';

const tree = await new FileSystemReader(
  new NodeFileSystemAdapter('./my-project', {
    exclude: ['node_modules', 'dist'],
    useGitignore: true,
  })
).read({ readFileMeta: true });

console.log(renderAsciiTree(tree));

浏览器目录选择器

先用浏览器辅助函数检测目录选择器支持情况,再用 LocalFileSystemAdapter 读取。该能力仅限浏览器环境。

import { FileSystemReader, LocalFileSystemAdapter } from '@devmc12/dir-tree';
import { isNativeDirectoryPickerSupported } from '@devmc12/dir-tree/browser';

if (!isNativeDirectoryPickerSupported()) {
  throw new Error('当前环境不支持目录选择');
}

const handle = await window.showDirectoryPicker({ mode: 'read' });
const tree = await new FileSystemReader(
  new LocalFileSystemAdapter({}, handle)
).read({ readFileMeta: true, useGitignore: true });

对于不支持原生目录选择器、但支持传统 webkitdirectory 选择器的浏览器,可使用 @devmc12/dir-tree/browser 中的 openLegacyDirectoryPickercreateLegacyDirectorySkipMatcher,将排除规则下沉到目录遍历中。

import {
  FileSystemReader,
  LegacyDirectoryFilesAdapter,
} from '@devmc12/dir-tree';
import {
  createLegacyDirectorySkipMatcher,
  openLegacyDirectoryPicker,
} from '@devmc12/dir-tree/browser';

const skipDirectory = createLegacyDirectorySkipMatcher(
  ['node_modules', 'dist'],
  false
);
const files = await openLegacyDirectoryPicker({
  recursive: true,
  skipDirectory,
});
const tree = await new FileSystemReader(
  new LegacyDirectoryFilesAdapter(files, {})
).read();

ZIP 文件

ZipFileSystemAdapter 支持 BlobArrayBufferUint8Array 输入。

import { FileSystemReader, ZipFileSystemAdapter } from '@devmc12/dir-tree';

const response = await fetch('/fixtures/project.zip');
const tree = await new FileSystemReader(
  new ZipFileSystemAdapter(await response.arrayBuffer(), {}, 'project')
).read({ showHidden: false });

远程仓库

RemoteRepositoryFileSystemAdapter 用于读取 GitHub 或 GitLab 仓库树。当平台需要鉴权时传入 token,或注入自定义 API client 以便测试和自托管集成。

import {
  FileSystemReader,
  RemoteRepositoryFileSystemAdapter,
} from '@devmc12/dir-tree';

const tree = await new FileSystemReader(
  new RemoteRepositoryFileSystemAdapter({
    repositoryUrl: 'https://github.com/example/project/tree/main/src',
    token: 'github-token',
  })
).read({ depth: 4 });

远程仓库辅助函数从 @devmc12/dir-tree/adapters 导出,包括 URL 解析、ref/路径解析、分支解析、平台条目映射,以及 fetch client 创建。需要先加载默认分支与分支列表(例如填充分支下拉框)时,使用 resolveRemoteRepositoryBranches

import { resolveRemoteRepositoryBranches } from '@devmc12/dir-tree/adapters';

const { branches, defaultBranch, ref, path } =
  await resolveRemoteRepositoryBranches({
    input: 'https://github.com/example/project/tree/main/src',
    token: 'github-token',
  });

解析与渲染

把导入的 JSON、XML、HTML、Markdown 列表、Markdown 文档或 ASCII 树文本解析为 FileNode 树,再渲染回 ASCII。

import { parseImportedTreeText } from '@devmc12/dir-tree/parser';
import {
  createAsciiTreeOptionsFromConfig,
  renderAsciiTree,
} from '@devmc12/dir-tree/ascii';

const parsed = parseImportedTreeText(
  `project
  ├── src
  └── README.md`,
  'project'
);

const ascii = renderAsciiTree(
  parsed.tree,
  createAsciiTreeOptionsFromConfig({
    connectorStyle: 'unicode',
    showLineNumbers: true,
  })
);

编辑树

树操作都是纯函数。它们返回克隆后的树或结构化的编辑结果,不会修改宿主 UI 状态。

import { createFileTreeNode, renameFileTreeNode } from '@devmc12/dir-tree/tree';

const created = createFileTreeNode(tree, 'project/src', {
  kind: 'file',
  name: 'new-file.ts',
});

const renamed = created
  ? renameFileTreeNode(created.tree, created.path, 'main.ts')
  : null;

导出与导入 JSON

当需要一个可序列化、包含可选注释与可见性状态的树文件时,使用 transfer 辅助函数。

import {
  createExportedFileTreeJson,
  parseImportedFileTreeJson,
} from '@devmc12/dir-tree/transfer';

const json = createExportedFileTreeJson(tree, annotations, { visibility });
const restored = parseImportedFileTreeJson(json);

注释 Provider 边界

本包不调用任何 AI 服务。它只定义 provider 请求载荷、provider 结果、补丁归一化与 diff 工具。模型调用、token、配额、存储、分析和通知都由你的应用负责。

import {
  applyTreeAnnotationPatches,
  createAnnotationDiffResult,
  createAnnotationProviderRequest,
  createTreeAnnotationPatchesFromProviderResult,
  type AnnotationProvider,
  type TreeAnnotationMap,
} from '@devmc12/dir-tree/annotations';

const provider: AnnotationProvider = {
  async annotate(payload) {
    return {
      annotations: payload.nodes.map(node => ({
        path: node.path,
        comment: `描述 ${node.kind}`,
      })),
    };
  },
};

const annotations: TreeAnnotationMap = {};
const request = createAnnotationProviderRequest({ tree, annotations });
const result = await provider.annotate(request.payload);
const patches = createTreeAnnotationPatchesFromProviderResult(
  result,
  request.sourcePaths
);
const diff = createAnnotationDiffResult(
  annotations,
  patches,
  request.allowedPaths
);
const nextAnnotations = applyTreeAnnotationPatches(
  annotations,
  diff.applyPatches
);

当数据源被再次读取时,可只保留仍匹配新树路径的注释,或将其整体重置。

import { resolveTreeAnnotationsAfterRead } from '@devmc12/dir-tree/annotations';

const retainedAnnotations = resolveTreeAnnotationsAfterRead(
  nextTree,
  annotations,
  'matching-paths'
);

项目结构

dir-tree/src
├── adapters                  # 文件数据源适配器:内存、Node.js、浏览器、ZIP 与远程仓库
│   └── remoteRepository      # GitHub/GitLab 仓库树的抓取与映射
├── annotations               # 注释 provider、补丁、diff、选项与带注释 ASCII
├── ascii                     # ASCII 树渲染、选项与等宽工具
├── browser                   # 可选的浏览器专用选择器与拖拽数据源辅助函数
├── node                      # Node.js 专用入口,暴露文件系统适配器
├── parser                    # 导入树文本解析器(JSON、XML、HTML、Markdown、ASCII)
├── reader                    # FileSystemReader、读取选项、元数据与 reader 工具
├── selection                 # 纯函数式的级联树选择模型
├── transfer                  # JSON 树导入/导出辅助函数
└── tree                      # 纯函数式的树编辑、可见性、展开、路径与统计工具

每个顶层目录都对应 package.json#exports 中的一个包子路径导出。

Playground

GitHub 仓库包含 playground/,是一个简单的 Vite React 示例,通过本地 Vite 与 TypeScript 别名演示公开的 dir-tree 导入。它只是一个最小参考实现,并不完整。完整的应用请见 dir-tree.com

npm run dev:playground
npm run build:playground

Node.js 示例(无浏览器)见 playground-node/,演示 NodeFileSystemAdapter、解析、注释、树编辑与 JSON 传输:

npm run start:playground-node

文档

校验

npm run lint
npm run typecheck
npm run test
npm run build
npm run smoke:exports
npm run smoke:install
npm run pack:verify
npm --prefix playground run typecheck
npm run build:playground

npm run smoke:install 会打包本地包,将 tarball 安装到一个临时的消费者项目中,并验证 ESM、CommonJS 与 TypeScript 消费者导入。