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

@cmtx/asset

v0.2.0-alpha.3

Published

Markdown asset management for cmtx - 支持本地图片上传和远程图片转移

Readme

@cmtx/asset

npm version License

Markdown 资产管理管道 —— 上传本地图片到云存储、在存储服务间转移、下载远程图片、带引用检查的删除。协调文件 I/O 和跨包业务逻辑编排。

完整 API 文档:运行 pnpm run docs 生成(位于 docs/api/ 目录)

子模块

@cmtx/asset/file - 文件操作服务

提供文件系统级别的图片处理功能(从 @cmtx/core 迁移):

  • 图片筛选(从文件/目录)
  • 图片替换(文件/目录批量替换)
  • 图片删除(安全删除本地图片)
  • 通用文件操作

@cmtx/asset/upload

本地图片上传到对象存储,支持智能去重和模板命名。

核心 API:

  • batchUploadImages(sources, config) — 批量上传,按文件路径/MD5 自动去重
  • renderReplacementText(match, cloudResult, replaceOptions?, imageFormat?) — 渲染替换文本(支持模板变量)
  • matchesToSources(matches, baseDir) — ImageMatch[] → UploadSource[] 转换
  • applyReplacementOps(content, ops) — 按偏移量应用替换操作
  • uploadAndReplaceFile(filePath, matches, baseDir, config, accessor?) — 单文件上传+替换(@deprecated 推荐使用 @cmtx/rule-engine/nodepublishAndReplaceFile

注意: 上传管道只负责上传和文本替换。文件读写由调用者通过 FileAccessor 控制。

@cmtx/asset/transfer

远程图片在存储间转移,支持并发控制和进度跟踪。

@cmtx/asset/download

远程图片下载到本地,支持命名模板和并发控制。

@cmtx/asset/delete

图片删除服务,提供引用检查和多种删除策略(trash/move/hard-delete)。

@cmtx/asset/config - 配置管理

提供 YAML 配置文件加载、验证和环境变量替换功能。

Service 层 - 统一服务接口

提供 Service 层 API,作为 Rule 引擎的统一服务接口(方案 D):

  • Service<TConfig> 接口:所有服务的基础契约
  • AssetService:封装 upload/download/delete pipeline 的 Facade
  • CoreService:封装 filterImages/replaceImages 的核心处理
import { AssetService, CoreService, createAssetService, createCoreService } from "@cmtx/asset";

// 创建 AssetService
const assetService = createAssetService({
    adapter: storageAdapter,
    prefix: "images/",
});

// 上传文档中的图片
const result = await assetService.uploadImagesInDocument(document, baseDirectory);

// 创建 CoreService
const coreService = createCoreService();

// 筛选文档中的图片
const images = coreService.filterImages(document, { mode: "sourceType", value: "local" });

核心理念

  • 文件系统操作中心:负责所有文件级操作(筛选、替换、删除、上传、下载)
  • 业务逻辑编排:协调各组件完成复杂任务,调用 @cmtx/template 生成文本
  • 纯文本处理委托:调用 @cmtx/core 执行纯文本处理(解析、替换、格式化)
  • 高度模块化:通过配置构建器模式实现灵活组合
  • 统一适配器接口:通过 @cmtx/storage 支持多种云存储服务

特性

文件操作服务(FileService)

通过 FileService 提供文件系统级别的图片处理(从 @cmtx/core 迁移):

import { createFileService, FileService } from "@cmtx/asset/file";

// 创建服务实例
const fileService = createFileService();

// 从文件筛选图片
const images = await fileService.filterImagesFromFile("/path/to/file.md");

// 替换文件中的图片
const result = await fileService.replaceImagesInFile("/path/to/file.md", [
    { field: "src", pattern: "./old.png", newSrc: "./new.png" },
]);

// 删除本地图片(含引用检查)
const deleteResult = await fileService.deleteLocalImage("/path/to/image.png", {
    strategy: "trash",
});

上传功能

  • 智能上传 - executeUploadPipeline() 扫描并上传 Markdown 文件中的本地图片
  • 模板系统 - 集成 @cmtx/template 的 Builder 模式,支持灵活的命名规则
  • 多字段替换 - 同时替换 src、alt、title 字段,支持模板变量渲染
  • 职责分离 - pipeline 只负责上传和替换,写回和删除由调用者负责

转移功能

  • 远程转移 - transferRemoteImages() 将远程图片从源存储转移到目标存储
  • URL 识别 - 自动识别属于源存储的 URL
  • 并发控制 - 支持并发传输和进度跟踪
  • 自动替换 - 传输完成后自动替换 Markdown 中的 URL

下载功能

  • 远程下载 - createDownloadService() 将 Markdown 中的远程图片下载到本地
  • 命名模板 - 支持 {date}, {name}, {ext}, {sequence} 等变量
  • 域名过滤 - 可选只下载特定域名的图片
  • 并发控制 - 可配置并发下载数量

DeleteService

专门的图片删除服务,提供引用检查和安全的删除策略:

import { DeleteService } from "@cmtx/asset/delete";

const service = new DeleteService({
    workspaceRoot: "/path/to/workspace",
    options: {
        strategy: "trash",
        removeFromMarkdown: true,
    },
});

// 扫描引用
const target = await service.scanReferences("./images/test.png");

// 执行删除
const result = await service.delete(target);

配置管理(Config)

提供 YAML 配置文件加载和验证:

import { ConfigLoader, ConfigValidator } from "@cmtx/asset/config";

// 加载配置
const config = await ConfigLoader.loadFromFile("./cmtx.config.yaml");

// 验证配置
const errors = ConfigValidator.validate(config);

云存储 URL 检测

import { detectStorageUrl, isSignedUrl, isStorageUrl,
         isAliyunOssUrl, isAwsS3Url, isTencentCosUrl } from "@cmtx/asset";

detectStorageUrl("https://mybucket.oss-cn-hangzhou.aliyuncs.com/image.png");
// => { provider: "aliyun", bucket: "mybucket", region: "cn-hangzhou" }

isStorageUrl("https://mybucket.oss-cn-hangzhou.aliyuncs.com/image.png"); // true
isAliyunOssUrl("https://mybucket.oss-cn-hangzhou.aliyuncs.com/image.png"); // true

URL 存在性检测

通过 HTTP HEAD 请求检测 URL 是否可访问,支持批量检测和并发控制:

import { checkUrlExists, checkUrlExistsBatch } from "@cmtx/asset";

// 检测单个 URL
const result = await checkUrlExists("https://example.com/image.png");
if (result.exists) {
    console.log(`URL exists (HTTP ${result.statusCode})`);
} else {
    console.log(`URL not found: ${result.error}`);
}

// 批量检测(带并发控制)
const batch = await checkUrlExistsBatch([
    "https://example.com/image1.png",
    "https://example.com/image2.png",
    "https://example.com/image3.png",
], { concurrency: 3 });

console.log(`${batch.existsCount}/${batch.total} URLs exist`);

从文本提取 URL 并检测

从 Markdown/HTML/纯文本中提取 URL 并批量检测可达性:

import { extractUrlsFromText, checkUrlsInText } from "@cmtx/asset";

// 仅提取 URL(纯同步,无网络请求)
const urls = extractUrlsFromText(`
    ![logo](https://example.com/logo.png)
    [docs](https://example.com/docs)
    Visit https://example.com/path
`);
// ['https://example.com/logo.png', 'https://example.com/docs', 'https://example.com/path']

// 提取 + 检测一步完成
const result = await checkUrlsInText(`
    ![valid](https://example.com/valid.png)
    ![missing](https://example.com/missing.png)
`, { concurrency: 3 });

console.log(`Extracted ${result.extractedUrls.length} URLs`);
console.log(`${result.existsCount}/${result.total} URLs exist`);

架构优势

  • 文件系统收束 - 所有文件操作统一在 asset 包,core 只做纯文本处理
  • 分层设计 - 与 @cmtx/template 和 @cmtx/core 协作
  • 模块化设计 - 配置构建器模式,链式调用 API
  • 适配器模式 - 通过 @cmtx/storage 支持多种云存储服务
  • 事件驱动 - 完整的进度跟踪和回调机制
  • 智能去重 - 单文件和跨文件两级去重,优化上传效率

安装

pnpm add @cmtx/asset

# 如果使用阿里云 OSS
pnpm add ali-oss

# 如果使用腾讯云 COS
pnpm add cos-nodejs-sdk-v5

注意: @cmtx/core 不再需要单独安装,它作为 @cmtx/asset 的间接依赖。

快速开始

文件操作(FileService)

FileService 是文件系统操作的统一入口,封装了从 @cmtx/core 迁移的图片处理功能:

import { createFileService } from "@cmtx/asset/file";

const fs = createFileService();

// 1. 从文件筛选图片
const images = await fs.filterImagesFromFile("/path/to/file.md", {
    mode: "sourceType",
    value: "local", // 只筛选本地图片
});

// 2. 从目录批量筛选
const allImages = await fs.filterImagesFromDirectory("./docs", {
    mode: "regex",
    value: /\.png$/i,
});

// 3. 替换文件中的图片
const replaceResult = await fs.replaceImagesInFile("/path/to/file.md", [
    {
        field: "src",
        pattern: "./old.png",
        newSrc: "https://cdn.example.com/new.png",
        newAlt: "New description",
    },
]);

// 4. 批量替换目录中的图片
const dirResult = await fs.replaceImagesInDirectory("./docs", [
    { field: "src", pattern: "./old/", newSrc: "./new/" },
]);

// 5. 安全删除本地图片(含引用检查)
const deleteResult = await fs.deleteLocalImage("/path/to/image.png", {
    strategy: "trash",
    maxRetries: 3,
});

上传本地图片

import { uploadLocalImageInMarkdown, ConfigBuilder } from "@cmtx/asset/upload";
import { AliOSSAdapter } from "@cmtx/storage/adapters/ali-oss";
import OSS from "ali-oss";

// 1. 配置 OSS 客户端
const ossClient = new OSS({
    region: "oss-cn-hangzhou",
    accessKeyId: "your-access-key-id",
    accessKeySecret: "your-access-key-secret",
    bucket: "your-bucket-name",
});

// 2. 创建适配器
const adapter = new AliOSSAdapter(ossClient);

// 3. 构建配置(链式调用)
const config = new ConfigBuilder()
    .storages({
        default: { adapter, namingTemplate: "{date}_{hash}{ext}" },
    })
    .useStorage("default")
    .prefix("blog/images/")
    .replace({
        fields: {
            src: "{cloudSrc}?x-oss-process=image/resize,w_640",
            alt: "{originalAlt} - 来自我的博客",
        },
    })
    .build();

// 4. 执行上传
const result = await uploadLocalImageInMarkdown("/path/to/article.md", config);

console.log(`成功上传 ${result.uploaded} 个图片`);
console.log(`成功替换 ${result.replaced} 个引用`);

// 5. 获取处理后的内容
console.log(result.content); // 处理后的 Markdown 内容

API 说明

uploadLocalImageInMarkdown 参数:

interface UploadOptions {
  writeFile?: boolean;  // 是否写入文件,默认 false
}

await uploadLocalImageInMarkdown(
  filePath: string,
  config: UploadConfig,
  options?: UploadOptions
);

返回值:

interface UploadResult {
    success: boolean; // 是否全部成功
    content: string; // 处理后的 Markdown 内容
    uploaded: number; // 上传成功的图片数
    replaced: number; // 替换成功的引用数
    deleted: number; // 删除成功的本地图片数
    failed?: FailedItem[]; // 失败项
    deduplicationInfo?: DeduplicationInfo; // 去重信息
}

使用示例:

// 默认不写入文件,返回内容(适合集成到其他工具)
const result = await uploadLocalImageInMarkdown(filePath, config);
console.log(result.content);

// 写入文件(CLI 行为)
await uploadLocalImageInMarkdown(filePath, config, { writeFile: true });

统一流水线(高级用法)

当你需要把上传能力集成到编辑器内存文本或自定义文档系统时,可以使用统一流水线 API:

import {
    executeUploadPipeline,
    type DocumentAccessor,
    type UploadStrategy,
} from "@cmtx/asset/upload";

const result = await executeUploadPipeline({
    documentAccessor,
    uploadStrategy,
    config,
    baseDirectory: process.cwd(),
});

该模式把“文档读写”和“上传动作”抽象为策略接口,CLI(文件写回)和 VS Code(内存编辑)可共用同一上传编排逻辑。

转移远程图片

import { transferRemoteImages, TransferConfigBuilder } from "@cmtx/asset/transfer";

// 构建传输配置
const transferConfig = new TransferConfigBuilder()
    .source({
        customDomain: "https://private.example.com",
        credentials: {
            provider: "aliyun-oss",
            accessKeyId: process.env.SOURCE_KEY_ID,
            accessKeySecret: process.env.SOURCE_KEY_SECRET,
            region: "oss-cn-hangzhou",
            bucket: "private-bucket",
        },
    })
    .target({
        customDomain: "https://cdn.example.com",
        credentials: {
            provider: "aliyun-oss",
            accessKeyId: process.env.TARGET_KEY_ID,
            accessKeySecret: process.env.TARGET_KEY_SECRET,
            region: "oss-cn-beijing",
            bucket: "public-bucket",
        },
        prefix: "images/",
    })
    .options({
        concurrency: 5,
        onProgress: (progress) => {
            console.log(`${progress.current}/${progress.total}: ${progress.fileName}`);
        },
    })
    .build();

// 执行传输
const result = await transferRemoteImages("/path/to/article.md", transferConfig);

console.log(`成功传输 ${result.success} 个图片`);
console.log(`失败: ${result.failed}`);
console.log(`跳过: ${result.skipped}`);

架构关系

用户/CLI/VSCode → @cmtx/asset (文件操作 + 业务编排)
                     ↓              ↓               ↓
                 FileService    @cmtx/template    @cmtx/core
                 (文件读写)     (模板渲染)        (纯文本处理)
                     ↓              ↓               ↓
                 筛选/替换      生成替换文本      解析/格式化
                 删除/上传
                     ↓
              @cmtx/storage (云存储适配器)

@cmtx/asset 的职责:

  1. 文件系统操作(FileService):读取文件、调用 core 处理文本、写回文件
  2. 业务逻辑编排:协调上传、转移、下载、删除等复杂任务
  3. 调用 @cmtx/template:生成具体的替换文本和命名规则
  4. 调用 @cmtx/core:执行纯文本处理(解析、替换、格式化)
  5. 调用 @cmtx/storage:与云存储服务交互

智能去重

@cmtx/asset/upload 提供两层去重机制,显著提升上传效率:

单文件去重

在单个 Markdown 文件内,相同的本地图片只上传一次:

# 原始文档

![Logo](./images/logo.png)
![Logo Copy](./images/logo.png)
![Logo Again](./images/logo.png)

# 处理后

![Logo](https://cdn.example.com/logo.png)
![Logo Copy](https://cdn.example.com/logo.png)
![Logo Again](https://cdn.example.com/logo.png)

# 结果:3 个引用,1 次上传

跨文件全局去重

处理多个 Markdown 文件时,相同图片在不同文件间只上传一次:

// 跨文件全局去重通过多次调用 uploadLocalImageInMarkdown 实现,
// 内部缓存机制会自动检测重复文件
const result = await uploadLocalImageInMarkdown("./docs/article1.md", config);

// 假设有 3 个文件都引用 logo.png // 只会上传 1 次,其他 2 个文件会使用缓存的 URL console.log(result.deduplicationInfo); // { // uniqueFiles: 1, // 实际唯一的图片文件数 // totalReferences: 3, // 总引用数 // duplicateCount: 2, // 单文件内的重复数 // globalDedupSaved: 2 // 跨文件节省的引用数 // }


## 模板变量

### 命名模板变量

用于生成远程文件名:

- `{name}` - 文件名(不含扩展名)
- `{ext}` - 文件扩展名(如 `.png`)
- `{fileName}` - 完整文件名
- `{date}` - 当前日期(YYYY-MM-DD)
- `{year}/{month}/{day}` - 年月日
- `{md5}` - 完整 MD5 哈希
- `{md5_8}` - MD5 前 8 位
- `{md5_16}` - MD5 前 16 位

### 字段模板变量

用于替换 Markdown 字段值:

- `{cloudSrc}` - 云端图片 URL
- `{originalSrc}` - 原始 src 值
- `{originalAlt}` - 原始 alt 值
- `{originalTitle}` - 原始 title 值
- `{date}` - 当前日期
- `{timestamp}` - 时间戳

## 示例

查看 [examples](./examples/) 目录了解更多使用示例:

### 上传功能

- [01-basic-upload.ts](./examples/01-basic-upload.ts) - 基础上传功能演示
- [02-field-templates.ts](./examples/02-field-templates.ts) - 高级字段模板配置
- [03-aliyun-oss.ts](./examples/03-aliyun-oss.ts) - 阿里云 OSS 集成完整示例

### 转移功能

- [transfer-basic.ts](./examples/transfer-basic.ts) - 基础转移功能演示
- [transfer-cross-account.ts](./examples/transfer-cross-account.ts) - 跨账号传输示例
- [transfer-batch.ts](./examples/transfer-batch.ts) - 批量传输示例
- [transfer-config-file.ts](./examples/transfer-config-file.ts) - 配置文件使用示例

## 配置文件

支持使用 YAML 配置文件进行传输:

```yaml
# transfer-config.yaml
source:
    customDomain: https://private.example.com
    config:
        bucket: source-bucket
        region: oss-cn-hangzhou

target:
    customDomain: https://cdn.example.com
    prefix: images/
    overwrite: false
    config:
        bucket: target-bucket
        region: oss-cn-beijing

options:
    concurrency: 5
    tempDir: /tmp/cmtx-transfer
    filter:
        extensions:
            - .jpg
            - .png
            - .gif
        maxSize: 10485760 # 10MB

使用环境变量注入敏感信息(完整环境变量名见 CFG-001 配置参考):

source:
    config:
        accessKeyId: ${CMTX_ALIYUN_ACCESS_KEY_ID}
        accessKeySecret: ${CMTX_ALIYUN_ACCESS_KEY_SECRET}

许可证

Apache-2.0

相关包