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

@shixinde/apifox-swagger

v4.3.1

Published

从 Apifox 导出 Swagger/OpenAPI 文档并生成 TypeScript 类型定义的工具

Readme

@shixinde/apifox-swagger

一个用于从 Apifox 导出 Swagger/OpenAPI 文档并生成 TypeScript 类型定义的工具。

功能特性

  • 🚀 支持从 Apifox 云端 API 导出文档
  • 🏠 支持从本地 Apifox 客户端导出文档
  • 📝 自动生成 TypeScript 类型定义
  • 🔧 支持命令行工具和编程接口
  • ⚙️ 支持配置文件
  • 🛠️ 自动处理 OpenAPI 格式兼容性

安装

npm install @shixinde/apifox-swagger
# 或
yarn add @shixinde/apifox-swagger
# 或
pnpm add @shixinde/apifox-swagger

使用方法

1. 命令行工具

安装后可以直接使用 apifox-swagger 命令:

云端模式(推荐)

# 基本用法 - 导出整个项目
apifox-swagger apifox-swagger --projectId 266643 --outdir ./output

# 导出指定文件夹
apifox-swagger apifox-swagger --projectId 266643 --folderId 123456 --folderName "用户模块" --outdir ./output

# 使用 token 参数
apifox-swagger apifox-swagger --projectId 266643 --outdir ./output --token your-access-token

# 生成类型安全的 API 工具
apifox-swagger apifox-swagger --projectId 266643 --outdir ./output --types

# 或使用环境变量
APIOFX_ACCESS_TOKEN=your-token apifox-swagger apifox-swagger --projectId 266643 --outdir ./output --types

命令行选项

  • --projectId <projectId>: Apifox 项目 ID(云端导出时必需)
  • --outdir <outdir>: 输出目录(默认:./apifox
  • --token <token>: 访问令牌(可替代环境变量)
  • --local: 从本地 Apifox 客户端导出
  • --folderId <folderId>: 指定文件夹 ID
  • --folderName <folderName>: 指定文件夹名称

2. 编程接口

基本用法

import { exportSwagger } from '@shixinde/apifox-swagger';

// 从云端导出
const result = await exportSwagger({
  projectId: '266643',
  token: 'your-access-token',
  outdir: './output',
  useLocal: false
});

// 从云端导出并生成类型文件
const result = await exportSwagger({
  projectId: '266643',
  token: 'your-access-token',
  outdir: './output',
  useLocal: false,
  // generateTypes: true
});

// 从本地客户端导出
const result = await exportSwagger({
  outdir: './output',
  useLocal: true
});

使用配置文件

创建 apifox.config.js

export default {
  projectId: '266643',
  outdir: './src/types',
  useLocal: false,
  // token: 'your-token', // 可选,也可通过环境变量设置
};

创建导出脚本 scripts/export-api.js

import { exportSwagger } from '@shixinde/apifox-swagger';
import config from '../apifox.config.js';

async function runExport() {
  try {
    const result = await exportSwagger({
      ...config,
      token: config.token || process.env.APIFOX_ACCESS_TOKEN
    });
    console.log('导出成功:', result);
  } catch (error) {
    console.error('导出失败:', error.message);
  }
}

runExport();

package.json 中添加脚本:

{
  "scripts": {
    "export-api": "node scripts/export-api.js"
  }
}

3. 环境变量

可以通过环境变量设置访问令牌:

export APIFOX_ACCESS_TOKEN=your-access-token

或在 .env 文件中:

APIFOX_ACCESS_TOKEN=your-access-token
APIFOX_PROJECT_ID=266643

输出文件

工具会在指定的输出目录下创建 swagger 文件夹,包含:

  • all.json: OpenAPI/Swagger JSON 文档
  • all.ts: TypeScript 类型定义文件
  • index.ts: 类型安全的 API 工具,包含 makeURL 函数和相关类型定义

如果指定了文件夹名称,文件名会使用文件夹名称替代 all

API 参考

exportSwagger(options)

导出 Swagger 文档的主要函数。

参数

  • options.projectId (string): Apifox 项目 ID(云端导出时必需)
  • options.token (string): 访问令牌(可选,优先级高于环境变量)
  • options.outdir (string): 输出目录
  • options.useLocal (boolean): 是否使用本地客户端(默认:false)
  • options.folderId (string): 文件夹 ID(可选)
  • options.folderName (string): 文件夹名称(可选)

返回值

返回一个 Promise,解析为包含导出信息的对象。

获取 Apifox 访问令牌

  1. 登录 Apifox 网页版
  2. 进入个人设置 → API 访问令牌
  3. 创建新的访问令牌
  4. 复制令牌并妥善保存

获取项目 ID

  1. 在 Apifox 中打开你的项目
  2. 查看浏览器地址栏,URL 中包含项目 ID
  3. 例如:https://www.apifox.cn/web/project/266643 中的 266643 就是项目 ID

故障排除

云端模式问题

  1. 检查访问令牌

    • 确保 APIFOX_ACCESS_TOKEN 环境变量已设置
    • 或使用 --token 参数传递令牌
  2. 检查项目信息

    • 验证项目 ID 是否正确
    • 确保有项目访问权限
  3. 网络连接

    • 验证网络连接是否正常
    • 检查是否有代理或防火墙限制
    • 确保有项目的访问权限

TypeScript 类型生成失败

  1. 检查 OpenAPI 文档格式是否正确
  2. 确保安装了所有必需的依赖

基本用法

# 生成类型文件
apifox-swagger apifox-swagger --projectId 266643 --outdir ./output --types

单个类型文件使用

// 1. 导入生成的 paths 类型
import type { paths } from '../swagger/all'

// 2. 导入类型工具
import { 
  makeURL, 
  type MakeURL, 
  type MakeRequest, 
  type MakeResponse,
  type InferMethodFromPaths 
} from '@shixinde/apifox-swagger/types'

// 3. 创建类型安全的 URL(传入 paths 类型)
const orderUrl = makeURL<paths, '/api/omo/course/order/new', 'post'>('/api/omo/course/order/new', 'post')

// 4. 提取类型信息
type OrderAPI = MakeURL<paths, '/api/omo/course/order/new', 'post'>
type RequestData = OrderAPI['jsonData']     // 请求体类型
type ResponseData = OrderAPI['responseData'] // 响应体类型
type QueryParams = OrderAPI['query']        // 查询参数类型
type PathParams = OrderAPI['pathParams']    // 路径参数类型

多个类型文件使用

如果你有多个 API 模块,可以配置多个类型文件:

// apifox.config.js
export default {
  types: {
    outdir: './src/swagger',
    // 多个类型文件配置
    files: [
      { filename: 'user', projectId: '266643' },
      { filename: 'order', projectId: '2364644' },
      { filename: 'product', projectId: '2364645' }
    ]
  }
};
方式一:统一类型导入(推荐)

为了简化使用,建议创建一个统一的类型导入文件:

// src/types/api.ts - 创建统一的类型导入文件
import type { paths as UserPaths } from '../swagger/user'
import type { paths as OrderPaths } from '../swagger/order'
import type { paths as ProductPaths } from '../swagger/product'

// 合并所有 API 路径类型
export type AllPaths = UserPaths & OrderPaths & ProductPaths

// 重新导出工具类型
export { 
  makeURL, 
  type MakeURL, 
  type MakeRequest, 
  type MakeResponse,
  type InferMethodFromPaths 
} from '@shixinde/apifox-swagger/types'

然后在业务代码中直接使用:

// 在业务代码中使用
import { makeURL, type MakeURL, type AllPaths } from '../types/api'

// 现在可以直接使用所有 API 路径,无需指定具体的 paths 类型
const userLogin = makeURL<AllPaths, '/api/user/login', 'post'>('/api/user/login', 'post')
const orderCreate = makeURL<AllPaths, '/api/order/create', 'post'>('/api/order/create', 'post')
const productList = makeURL<AllPaths, '/api/product/list', 'get'>('/api/product/list', 'get')

// 类型推断也会正常工作
type UserLoginAPI = MakeURL<AllPaths, '/api/user/login', 'post'>
type OrderCreateAPI = MakeURL<AllPaths, '/api/order/create', 'post'>
type ProductListAPI = MakeURL<AllPaths, '/api/product/list', 'get'>
方式二:分别导入

如果需要明确区分不同模块的类型,也可以分别导入:

// 分别导入不同模块的类型
import type { paths as UserPaths } from '../swagger/user'
import type { paths as OrderPaths } from '../swagger/order'
import type { paths as ProductPaths } from '../swagger/product'

import { 
  makeURL, 
  type MakeURL, 
  type MakeRequest, 
  type MakeResponse 
} from '@shixinde/apifox-swagger/types'

// 用户相关 API
type UserLoginAPI = MakeURL<UserPaths, '/api/user/login', 'post'>
const userLogin = makeURL<UserPaths, '/api/user/login', 'post'>('/api/user/login', 'post')

// 订单相关 API
type OrderCreateAPI = MakeURL<OrderPaths, '/api/order/create', 'post'>
const orderCreate = makeURL<OrderPaths, '/api/order/create', 'post'>('/api/order/create', 'post')

// 产品相关 API
type ProductListAPI = MakeURL<ProductPaths, '/api/product/list', 'get'>
const productList = makeURL<ProductPaths, '/api/product/list', 'get'>('/api/product/list', 'get')

高级用法

1. 创建类型安全的 API 客户端

// 使用统一的类型导入
import { 
  makeURL, 
  type MakeRequest, 
  type MakeResponse, 
  type InferMethodFromPaths,
  type AllPaths 
} from '../types/api'

// 通用 API 调用函数
function apiCall<
  U extends keyof AllPaths,
  M extends InferMethodFromPaths<AllPaths, U>
>(
  url: U,
  method: M,
  data?: MakeRequest<AllPaths, U, M>
): Promise<MakeResponse<AllPaths, U, M>> {
  // 实现 API 调用逻辑
  return fetch(url, {
    method: method as string,
    headers: {
      'Content-Type': 'application/json',
    },
    body: data?.jsonData ? JSON.stringify(data.jsonData) : undefined,
  }).then(res => res.json())
}

// 使用示例
async function createOrder() {
  const result = await apiCall('/api/omo/course/order/new', 'post', {
    jsonData: {
      // 这里会有完整的类型提示
      courseId: '123',
      quantity: 1
    }
  })
  
  // result 也有正确的类型
  console.log(result.orderId)
}

2. React Hook 集成

import { useState, useEffect } from 'react'
import { 
  makeURL, 
  type MakeRequest, 
  type MakeResponse, 
  type InferMethodFromPaths,
  type AllPaths 
} from '../types/api'

function useAPI<U extends keyof AllPaths, M extends InferMethodFromPaths<AllPaths, U>>(
  url: U,
  method: M,
  data?: MakeRequest<AllPaths, U, M>
) {
  const [loading, setLoading] = useState(false)
  const [result, setResult] = useState<MakeResponse<AllPaths, U, M> | null>(null)
  const [error, setError] = useState<Error | null>(null)

  useEffect(() => {
    setLoading(true)
    apiCall(url, method, data)
      .then(setResult)
      .catch(setError)
      .finally(() => setLoading(false))
  }, [url, method, data])

  return { loading, result, error }
}

// 在组件中使用
function OrderComponent() {
  const { loading, result, error } = useAPI('/api/omo/course/order/list', 'get', {
    query: { page: 1, size: 10 }
  })

  if (loading) return <div>Loading...</div>
  if (error) return <div>Error: {error.message}</div>
  
  return (
    <div>
      {result?.orders.map(order => (
        <div key={order.id}>{order.title}</div>
      ))}
    </div>
  )
}

类型说明

  • makeURL<TPaths, U, M>(url, method): 创建类型安全的 URL 和方法组合,返回只读元组 [url, method]
  • MakeURL<TPaths, U, M>: 包含完整 API 信息的类型,包括请求参数和响应数据
  • MakeRequest<TPaths, U, M>: 请求参数类型,包括 query、pathParams、formData、jsonData
  • MakeResponse<TPaths, U, M>: 响应数据类型
  • InferMethodFromPaths<TPaths, U>: 从路径推断可用的 HTTP 方法
  • paths: 从 Swagger 文档生成的所有 API 路径的类型定义

重要提示

  • makeURL 函数主要用于运行时 URL 构建,返回元组类型
  • 进行类型推断时,应直接使用 MakeURL<TPaths, U, M> 类型,而不是从 makeURL 函数返回值推断
  • 错误用法:type API = MakeURL<UserPaths, (typeof makeURL(...))[0], (typeof makeURL(...))[1]>
  • 正确用法:type API = MakeURL<UserPaths, '/api/path', 'get'>

其中:

  • TPaths: 从 Swagger 文档导入的 paths 类型
  • U: API 路径,如 /api/user/login
  • M: HTTP 方法,如 getpost

最佳实践

  1. 统一类型导入:创建 src/types/api.ts 文件,统一导入和导出所有 API 类型,避免在每个业务文件中重复导入多个 paths 类型
  2. 统一的 API 客户端:创建一个统一的 API 调用函数,所有接口调用都通过它进行
  3. 类型复用:将常用的类型定义导出,在多个文件中复用
  4. 错误处理:结合 TypeScript 的联合类型,优雅地处理 API 错误
  5. 自动更新:将类型生成集成到构建流程中,确保类型定义始终是最新的
  6. 模块化管理:对于大型项目,可以按业务模块分别生成类型文件,然后通过统一的类型文件合并导出

许可证

MIT

完整使用示例

以下是一个完整的使用示例,展示如何在实际项目中使用类型安全的 API 工具:

// src/api/types.ts - 导入生成的类型
// 第一步:导入生成的 paths 类型
import type { paths as UserPaths } from '../../output/swagger/all'

// 第二步:导入类型工具
import { 
  makeURL, 
  type MakeURL, 
  type MakeRequest, 
  type MakeResponse,
  type InferMethodFromPaths 
} from '@shixinde/apifox-swagger/types'

// 第三步:定义具体的 API 类型
type ClassListAPI = MakeURL<UserPaths, '/api/org/class/list', 'get'>
type ClassCreateAPI = MakeURL<UserPaths, '/api/org/class/create', 'post'>
type ClassUpdateAPI = MakeURL<UserPaths, '/api/org/class/update', 'put'>

// 注意:如果出现 "类型不能赋给类型never的参数" 错误,请检查:
// 1. 是否正确导入了 paths 类型:import type { paths as UserPaths } from '../../output/swagger/all'
// 2. 路径是否存在于 Swagger 文档中
// 3. HTTP 方法是否正确
// 4. 是否已经运行了代码生成命令:npx apifox-swagger

// 创建类型安全的 URL
const classListUrl = makeURL<UserPaths, '/api/org/class/list', 'get'>('/api/org/class/list', 'get')
const classCreateUrl = makeURL<UserPaths, '/api/org/class/create', 'post'>('/api/org/class/create', 'post')
const classUpdateUrl = makeURL<UserPaths, '/api/org/class/update', 'put'>('/api/org/class/update', 'put')

// 更简洁的类型推断方式
// 注意:makeURL 函数返回的是元组,不能直接用于类型推断
// 正确的方式是直接使用 MakeURL 类型
type ClassListURL = MakeURL<UserPaths, '/api/org/class/list', 'get'>;

// 如果需要运行时的 URL 构建,可以这样使用:
const url = makeURL<UserPaths, '/api/org/class/list', 'get'>('/api/org/class/list', 'get');

// 提取各种类型
type Response = ClassListURL['responseData'];        // 响应数据类型
type Query = ClassListURL['query'];                  // 查询参数类型
type PathParams = ClassListURL['pathParams'];        // 路径参数类型
type FormData = ClassListURL['formData'];            // 表单数据类型
type JsonData = ClassListURL['jsonData'];            // JSON 数据类型

// 导出具体的业务类型
export type Classes = Response['data'];     // 班级列表数据类型
export type ClassItem = Classes[0];        // 单个班级数据类型

// 示例:不同 API 的类型提取
// 正确的方式:直接使用 MakeURL 类型
type CreateURL = MakeURL<UserPaths, '/api/org/class/create', 'post'>;
type CreateRequest = CreateURL['jsonData']; // 创建班级的请求数据类型
type CreateResponse = CreateURL['responseData']; // 创建班级的响应数据类型

type UpdateURL = MakeURL<UserPaths, '/api/org/class/update', 'put'>;
type UpdateRequest = UpdateURL['jsonData']; // 更新班级的请求数据类型
type UpdatePathParams = UpdateURL['pathParams']; // 更新班级的路径参数类型(如 id)

// 带查询参数的 API 示例
type SearchURL = MakeURL<UserPaths, '/api/org/class/search', 'get'>;
type SearchQuery = SearchURL['query'];      // 搜索查询参数类型(如 keyword, page, size)
type SearchResponse = SearchURL['responseData']; // 搜索响应数据类型

// 文件上传 API 示例
type UploadURL = MakeURL<UserPaths, '/api/org/class/upload', 'post'>;
type UploadFormData = UploadURL['formData']; // 文件上传表单数据类型
type UploadResponse = UploadURL['responseData']; // 文件上传响应数据类型

// 如果需要运行时 URL 构建,可以这样使用:
const createUrl = makeURL<UserPaths, '/api/org/class/create', 'post'>('/api/org/class/create', 'post');
const updateUrl = makeURL<UserPaths, '/api/org/class/update', 'put'>('/api/org/class/update', 'put');

// 通用 API 调用函数
function apiCall<
  U extends keyof UserPaths,
  M extends InferMethodFromPaths<UserPaths, U>
>(
  url: U,
  method: M,
  data?: MakeRequest<UserPaths, U, M>
): Promise<MakeResponse<UserPaths, U, M>> {
  const baseURL = 'https://api.example.com'
  
  return fetch(`${baseURL}${url}`, {
    method: method as string,
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${getToken()}`
    },
    body: data?.jsonData ? JSON.stringify(data.jsonData) : undefined,
  }).then(res => {
    if (!res.ok) {
      throw new Error(`HTTP ${res.status}: ${res.statusText}`)
    }
    return res.json()
  })
}

// 实际使用示例
async function examples() {
  // 1. 获取班级列表(GET 请求,带查询参数)
  const classList = await apiCall('/api/org/class/list', 'get', {
    query: {
      page: 1,
      size: 10,
      keyword: '高一'
    }
  });
  // classList 的类型会被自动推断为对应的响应类型
  console.log(classList.data); // 类型安全的访问

  // 2. 创建班级(POST 请求,带 JSON 数据)
  const newClass = await apiCall('/api/org/class/create', 'post', {
    jsonData: {
      name: '高一(1)班',
      teacherId: 123,
      capacity: 50
    }
  });
  console.log(newClass.data.id); // 新创建班级的 ID

  // 3. 更新班级(PUT 请求,带路径参数和 JSON 数据)
  const updatedClass = await apiCall('/api/org/class/update', 'put', {
    pathParams: {
      id: 456
    },
    jsonData: {
      name: '高一(2)班',
      capacity: 45
    }
  });
  console.log(updatedClass.message); // 更新结果消息

  // 4. 文件上传(POST 请求,带表单数据)
  const uploadResult = await apiCall('/api/org/class/upload', 'post', {
    formData: {
      file: new File([''], 'students.xlsx'),
      classId: 789
    }
  });
  console.log(uploadResult.data.uploadedCount); // 上传的学生数量
}

// 类型提取的高级用法
type ApiEndpoints = {
  // 班级相关 API
  classList: MakeURL<UserPaths, '/api/org/class/list', 'get'>;
  classCreate: MakeURL<UserPaths, '/api/org/class/create', 'post'>;
  classUpdate: MakeURL<UserPaths, '/api/org/class/update', 'put'>;
  classUpload: MakeURL<UserPaths, '/api/org/class/upload', 'post'>;
};

// 提取所有响应类型
type ApiResponses = {
  [K in keyof ApiEndpoints]: ApiEndpoints[K]['responseData'];
};

// 提取所有请求类型
type ApiRequests = {
  [K in keyof ApiEndpoints]: {
    query: ApiEndpoints[K]['query'];
    pathParams: ApiEndpoints[K]['pathParams'];
    jsonData: ApiEndpoints[K]['jsonData'];
    formData: ApiEndpoints[K]['formData'];
  };
};

// 业务层类型定义
export type ClassListResponse = ApiResponses['classList'];
export type ClassCreateRequest = ApiRequests['classCreate']['jsonData'];
export type ClassUpdateRequest = ApiRequests['classUpdate']['jsonData'];
export type ClassUploadFormData = ApiRequests['classUpload']['formData'];

// 具体的 API 调用函数
export const classAPI = {
  // 获取班级列表
  async getList(params?: MakeRequest<UserPaths, '/api/org/class/list', 'get'>['query']) {
    return apiCall('/api/org/class/list', 'get', { query: params })
  },
  
  // 创建班级
  async create(data: MakeRequest<UserPaths, '/api/org/class/create', 'post'>['jsonData']) {
    return apiCall('/api/org/class/create', 'post', { jsonData: data })
  },
  
  // 更新班级
  async update(
    id: string, 
    data: MakeRequest<UserPaths, '/api/org/class/update', 'put'>['jsonData']
  ) {
    return apiCall('/api/org/class/update', 'put', { 
      pathParams: { id },
      jsonData: data 
    })
  }
}

// React Hook 示例
import { useState, useEffect } from 'react'

export function useClassList(params?: MakeRequest<UserPaths, '/api/org/class/list', 'get'>['query']) {
  const [loading, setLoading] = useState(false)
  const [data, setData] = useState<MakeResponse<UserPaths, '/api/org/class/list', 'get'> | null>(null)
  const [error, setError] = useState<Error | null>(null)

  useEffect(() => {
    setLoading(true)
    classAPI.getList(params)
      .then(setData)
      .catch(setError)
      .finally(() => setLoading(false))
  }, [params])

  return { loading, data, error, refetch: () => classAPI.getList(params) }
}

// 在组件中使用
function ClassListComponent() {
  const { loading, data, error } = useClassList({ page: 1, size: 10 })
  
  const handleCreate = async (formData: any) => {
    try {
      await classAPI.create({
        name: formData.name,
        description: formData.description
        // TypeScript 会提供完整的类型提示
      })
      // 重新获取列表
    } catch (err) {
      console.error('创建失败:', err)
    }
  }

  if (loading) return <div>Loading...</div>
  if (error) return <div>Error: {error.message}</div>
  
  return (
    <div>
      {data?.list?.map(item => (
        <div key={item.id}>
          <h3>{item.name}</h3>
          <p>{item.description}</p>
        </div>
      ))}
    </div>
  )
}

// 工具函数
function getToken(): string {
  return localStorage.getItem('token') || ''
}

贡献

欢迎提交 Issue 和 Pull Request!