Skip to content

feat!: 基于 OpenAPI 规格重写为 TypeScript CLI(覆盖全部 38 个官方操作)#2

Closed
chen201724 wants to merge 14 commits into
mainfrom
feat/openapi-cli
Closed

feat!: 基于 OpenAPI 规格重写为 TypeScript CLI(覆盖全部 38 个官方操作)#2
chen201724 wants to merge 14 commits into
mainfrom
feat/openapi-cli

Conversation

@chen201724

Copy link
Copy Markdown
Member

概要

将本仓库整体替换为一套规格驱动的语雀命令行工具(与 yuque-mcp-server 同一套工程约定),完全覆盖原有的交互式 JS 实现。命令面以 vendored 的官方 OpenAPI 3.1 规格(spec/yuque-openapi.yaml,38 个 operation)为唯一事实源,由契约测试逐行钉死。

内容

  • 26 条 noun-verb 命令覆盖全部 38 个官方 operation:auth/ping、user、search、repo、doc(含版本历史)、toc、group 成员管理、团队统计;知识库参数同时接受数字 id 与 owner/slug(id/namespace 两组路径变体折叠为同一命令)
  • 鉴权YUQUE_TOKEN / --token(flag 优先,兼容 YUQUE_PERSONAL_TOKEN);YUQUE_HOST 支持空间与私有化部署
  • 脚本化契约:稳定退出码(0/1/2/3/4/5)、--json 全量输出、--all 自动翻页、429 自动退避;破坏性命令需交互确认或 --yes
  • method-aware 重试:非幂等写操作在超时/5xx 下绝不静默重试,错误信息会提示"请求可能已生效"
  • 契约锁tests/spec-coverage.test.ts 钉死 operationId→method+path→命令 映射;双语 README 由 tests/docs/command-surface-docs.test.ts 锁定(数量标题 + 边界匹配 + 反向锁)
  • 质量:163 个测试;实现后经多智能体对抗式评审确认并修复 23 个问题;npm run check(lint+format+typecheck+test+build+dist smoke)为合并门槛,CI 已配置(Node 20/22)

兼容性说明

  • Breaking:原交互式 REPL 实现整体移除;本 PR 后仓库即为新 CLI
  • package.json 暂定名 @yuque/cli、版本 0.1.0。npm 上 yuque-cli(0.1.1,bin yuque)即本仓库旧实现发布的包——若沿用该包名发布可改回 yuque-cli 并升 major,建议合并后另行决策

🤖 Generated with Claude Code

chen201724 and others added 5 commits July 22, 2026 17:41
…leton, spec contract

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Domains: auth/ping, user, search, repo, doc (incl. versions), toc, group members,
team statistics. Bilingual READMEs, docs lock test, exit-code contract fix
(exitOverride before subcommand registration).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…fy help style

- drop repo create --type (not in spec requestBody); add --enhanced-privacy
- add repo list --filter-by-ability; cap --limit/--page/--role per spec bounds
- capitalize all command descriptions consistently

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- http: method-aware retry — never silently replay non-idempotent writes on
  timeout/5xx; flag ambiguity in the error message; pin backoff schedule in tests
- docs lock: boundary-aware command matching (no prefix false-positives) plus a
  reverse lock against phantom README rows; spec table now pins method+path per op
- doc/user: validate --offset/--limit regardless of --all; document precedence
- output: EastAsianWidth-based display width (Hangul/emoji/CJK ext); EPIPE exits 0
- stats: --json always prints the bare rows array in both paging modes
- help/READMEs: mark required flags, fix toc verbs, rekey troubleshooting on real
  error strings, document YUQUE_PERSONAL_TOKEN fallback

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…e OpenAPI

Replaces the previous interactive JS CLI entirely: 26 noun-verb commands locked
1:1 against the vendored OpenAPI spec (38 operations), stable exit codes,
--json/--all scripting support, method-aware retry, and bilingual READMEs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e9dee74af4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/commands/stats.ts Outdated
)
: await fetchPage(ctx.http, { ...filters, page: opts.page, limit: opts.limit });
if (ctx.json) {
printJson(rows);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve stats pagination metadata in JSON

When a stats list command runs with --json but without --all, the API client's page object includes total, yet the callbacks extract only .members, .books, or .docs and this line prints that reduced array. Scripts therefore cannot retrieve the result count despite --json promising the full payload; preserve the page object for non---all JSON output.

AGENTS.md reference: AGENTS.md:L34-L34

Useful? React with 👍 / 👎.

Comment thread src/commands/repo.ts Outdated
.requiredOption('--slug <slug>', 'repo path (slug) (required)')
.option('--group', 'create under a group instead of a user')
.option('--description <description>', 'repo description')
.option('--public <n>', 'visibility: 0 private, 1 public, 2 org-only', parseNonNegativeInt)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject unsupported repo visibility values

For repo create and repo update, --public is documented and specified as the enum 0|1|2, but parseNonNegativeInt also accepts values such as 3. Those invalid invocations reach the API and become API failures with exit code 1 instead of being rejected locally as usage errors with exit code 2; use an enum-aware parser for these options.

AGENTS.md reference: AGENTS.md:L26-L27

Useful? React with 👍 / 👎.

Comment thread src/commands/doc.ts Outdated
printRecord(detail, DOC_META_FIELDS);
return;
}
writeBody(detail.body);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Render spreadsheet document bodies

When doc get receives a Sheet document, the OpenAPI response carries its content in body_sheet and may omit body; reading only detail.body therefore prints nothing and exits successfully for that supported document type. Select the appropriate content field based on the response type, or report that the format cannot be rendered.

Useful? React with 👍 / 👎.

Comment thread src/client/http.ts
this.sleep = options.sleep ?? defaultSleep;
this.axios = axios.create({
baseURL: `${options.host}/api/v2`,
timeout: options.timeoutMs ?? 30000,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

30s 超时够么

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

和yuque-mcp-server 客户端保持一致正常都够用;加了一个配置项也可以调大。

Comment thread .gitignore
coverage/
*.tgz

# Env files

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里为啥要删除了

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里之前改多了,补回来了

Comment thread src/commands/toc.ts
action: opts.action,
...(opts.actionMode !== undefined && { action_mode: opts.actionMode }),
...(opts.targetUuid !== undefined && { target_uuid: opts.targetUuid }),
...(opts.nodeUuid !== undefined && { node_uuid: opts.nodeUuid }),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nodeUuid 和 targetUuid 都是非必填的么?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

target_uuid 不填默认为根节点,node_uuid 是移动/更新/删除场景才需要

Comment thread src/commands/toc.ts
const opts = update.opts<TocUpdateOptions>();
const body: TocUpdateBody = {
action: opts.action,
...(opts.actionMode !== undefined && { action_mode: opts.actionMode }),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个也是可以直接留空么

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

是的,创建/移动时需要

chen201724 and others added 9 commits July 23, 2026 17:26
- stats --json keeps the page object ({rows-key, total}) in both paging modes
- repo --public validates the 0|1|2 enum locally (exit 2, no wire call)
- doc get renders sheet/table docs via body_sheet/body_table fallback, warns
  on stderr when nothing is renderable
- toc update enforces spec cross-field rules: edit/remove need --node-uuid,
  creating needs --type plus per-type fields; --target-uuid stays optional
  (defaults to root)
- request timeout configurable via --timeout / YUQUE_TIMEOUT_MS (default 30s,
  same as yuque-mcp-server)
- restore log/env/editor entries dropped from .gitignore in the rewrite

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ated real-API suite

- tests/e2e mock suite: FixtureServer + async runner spawn the real dist/bin.js
  and assert wire traffic, rendering, exit codes, retry semantics (31 cases)
- keep the env-gated real-API suite (cli.e2e.test.ts) and document both layers
  in tests/e2e/README.md
- npm run check now ends with test:e2e; CI gate unchanged (single check)
- .github/workflows/real-api-e2e.yml: weekly + manual read-only run, enabled
  only when the YUQUE_E2E_TOKEN secret exists

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
npm run check's e2e step now enables the gated real-API read paths whenever the
YUQUE_E2E_TOKEN secret is configured (Node 22 leg only, weekly schedule added
for drift detection); the separate real-api workflow is removed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The account's first repo can be a Design board, whose /docs and /toc endpoints
404; filter with repo list --type Book (and point at YUQUE_E2E_REPO in the
failure hint).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
/users/{login}/repos 404s for some accounts (private profiles) while the
numeric id works; probe both and reuse the working ref for user-groups and
repo-list assertions. YUQUE_E2E_LOGIN still pins it explicitly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… empty

CI's YUQUE_E2E_TOKEN belongs to a dedicated empty test account; first run
creates cli-e2e-sandbox with one fixture doc via the CLI itself, later runs
discover it through the Book listing. No manual setup or repo variables needed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Reuses the org-owned npm name (superseding the 0.1.x interactive CLI) with a
major bump for the breaking rewrite; bin stays `yuque`.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ional deps

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…bindings breaks npm ci

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chen201724

Copy link
Copy Markdown
Member Author

改为独立仓库开发,本 PR 不再需要:新实现已迁移至 https://github.com/yuque/cli(npm 包名 @yuque/cli)。原仓库保持不变。

@chen201724 chen201724 closed this Jul 23, 2026
@chen201724
chen201724 deleted the feat/openapi-cli branch July 23, 2026 10:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants