Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Bug report
description: Something in the CLI behaves incorrectly
labels: [bug]
body:
- type: input
id: version
attributes:
label: CLI version
description: Output of `yuque --version`
placeholder: 1.1.0
validations:
required: true
- type: input
id: environment
attributes:
label: Environment
description: OS and Node.js version (`node --version`)
placeholder: macOS 15 / Node 22.11.0
validations:
required: true
- type: textarea
id: command
attributes:
label: Command and behavior
description: >-
The exact command you ran (redact tokens and private doc slugs), what
happened, and what you expected. Include the exit code (`echo $?`)
and output β€” `--json` output is especially helpful.
placeholder: |
$ yuque doc get team/handbook onboarding
exit code: 4
...
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional context
description: >-
Anything else relevant β€” custom `--host` / space host, whether a team
token or personal token is used, proxy setup, etc.
validations:
required: false
8 changes: 8 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
blank_issues_enabled: true
contact_links:
- name: Security vulnerability
url: https://github.com/yuque/yuque-open-cli/security/advisories/new
about: Please report security issues privately via a security advisory, not a public issue.
- name: Yuque OpenAPI documentation
url: https://www.yuque.com/yuque/developer/api
about: Questions about the Yuque API itself (rather than this CLI) are answered here.
29 changes: 29 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Feature request
description: Propose a new command, flag, or behavior
labels: [enhancement]
body:
- type: textarea
id: problem
attributes:
label: Problem
description: What are you trying to do that the CLI currently makes hard?
validations:
required: true
- type: textarea
id: proposal
attributes:
label: Proposed command surface
description: >-
Sketch the command(s) as you would type them. If this maps to a Yuque
OpenAPI endpoint, link it β€” the CLI surface is spec-driven.
placeholder: |
yuque note list --all --json
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives considered
description: Workarounds you use today (scripts, other tools, raw curl).
validations:
required: false
8 changes: 8 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!-- Describe what changes and why. Link the issue if there is one. -->

## Checklist

- [ ] `npm run check` passes locally (the exact gate CI runs)
- [ ] User-visible changes are reflected in **both** `README.md` and `README.zh-CN.md`, and in `CHANGELOG.md`
- [ ] API-surface changes follow the spec-driven flow: `spec/yuque-openapi.yaml` edited, `npm run gen:types` run, command surface extended (see [CONTRIBUTING.md](https://github.com/yuque/yuque-open-cli/blob/main/CONTRIBUTING.md))
- [ ] Structural changes are reflected in `AGENTS.md`
70 changes: 70 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Release

# Tag-driven release: push a `vX.Y.Z` tag and this workflow runs the full
# check gate, publishes to npm with provenance, and creates the GitHub
# Release from the matching CHANGELOG.md section.
#
# One-time setup: add an npm automation token as the NPM_TOKEN repo secret
# (Settings β†’ Secrets and variables β†’ Actions). Publishing fails fast with a
# clear error if the secret is missing.
#
# Release steps (also documented in CONTRIBUTING.md):
# 1. Bump `version` in package.json and add the `## X.Y.Z` CHANGELOG entry.
# 2. Land that on main, then: git tag vX.Y.Z && git push origin vX.Y.Z

on:
push:
tags: ['v*.*.*']

permissions:
contents: write # create the GitHub Release
id-token: write # npm provenance attestation

jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
registry-url: https://registry.npmjs.org
# npm install, not npm ci β€” see ci.yml for the lockfile rationale.
- run: npm install --no-audit --no-fund
- name: Verify tag matches package.json version
run: |
pkg_version="$(node -p "require('./package.json').version")"
tag_version="${GITHUB_REF_NAME#v}"
if [ "$pkg_version" != "$tag_version" ]; then
echo "Tag $GITHUB_REF_NAME does not match package.json version $pkg_version" >&2
exit 1
fi
- name: Full check gate
run: npm run check
- name: Publish to npm (with provenance)
run: |
if [ -z "$NODE_AUTH_TOKEN" ]; then
echo "NPM_TOKEN secret is not configured (Settings β†’ Secrets and variables β†’ Actions)" >&2
exit 1
fi
npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Create GitHub Release from CHANGELOG
run: |
Comment on lines +54 to +55

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Move CHANGELOG validation before publishing

When a release tag has no matching nonempty ## X.Y.Z sectionβ€”for example, because the version was bumped but the changelog was missedβ€”this validation runs only after npm publish has permanently published that name/version. The job then exits without creating the GitHub Release, and rerunning stops at the already-published version; extract and validate the notes before publishing, and ideally make already-published reruns idempotent.

AGENTS.md reference: AGENTS.md:L89-L89

Useful? React with πŸ‘Β / πŸ‘Ž.

version="${GITHUB_REF_NAME#v}"
awk -v ver="$version" '
$0 ~ "^## "ver"$" { found=1; next }
found && /^## / { exit }
found { print }
' CHANGELOG.md > /tmp/release-notes.md
if ! [ -s /tmp/release-notes.md ]; then
echo "No CHANGELOG.md section found for $version" >&2
exit 1
fi
gh release create "$GITHUB_REF_NAME" \
--title "$GITHUB_REF_NAME" \
--notes-file /tmp/release-notes.md
env:
GH_TOKEN: ${{ github.token }}
112 changes: 112 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Contributing to yuque-open-cli

Thanks for your interest in improving the Yuque CLI! This document covers the
development workflow, the conventions the codebase enforces, and how releases
are cut.

> Working with an AI agent (or as one)? [AGENTS.md](./AGENTS.md) is the
> authoritative architecture map and convention guide β€” it is kept in lockstep
> with the code by tests.

## Development setup

Requirements: Node.js β‰₯ 20.

```bash
git clone https://github.com/yuque/yuque-open-cli.git
cd yuque-open-cli
npm install
npm run dev -- --help # run the CLI from source (tsx)
```

To exercise commands against the real API you need a token from
[Yuque Developer Settings](https://www.yuque.com/settings/tokens):

```bash
YUQUE_TOKEN=... npm run dev -- auth status
```

## The check gate

Every change must pass the single unified gate β€” the same command CI runs:

```bash
npm run check
```

That is: ESLint, Prettier check, generated-types drift check, `tsc`, unit
tests with coverage, build, packaged-CLI smoke test, and the mock-server e2e
suite. If `npm run check` is green locally, CI will be green.

Useful narrower loops while iterating:

```bash
npm test # unit tests once
npm run test:watch # unit tests in watch mode
npm run test:e2e # build + e2e against the bundled mock server
```

## Spec-driven workflow

The OpenAPI spec is the source of truth for the API surface:

1. Edit `spec/yuque-openapi.yaml` β€” never edit `src/client/types.gen.ts` by
hand.
2. Run `npm run gen:types` to regenerate the types.
3. Adapt the thin compatibility layer in `src/client/types.ts` if public type
names changed.

`npm run gen:types:check` (part of the check gate) fails if the generated
file drifts from the spec. `tests/spec-coverage.test.ts` fails if a spec
operation has no corresponding CLI command, so extending the spec means
extending the command surface in the same change.

## Code layout and conventions

```
bin.ts β†’ cli.ts (commander program, error β†’ exit code)
└── commands/<domain>.ts (flags, confirmation, rendering)
└── client/api/<domain>.ts (typed calls, envelope unwrap)
└── client/http.ts (auth header, retry/backoff, YuqueError)
```

- One domain = one `src/commands/<domain>.ts` exporting a single
`register<Domain>Commands` function, plus one thin `src/client/api/<domain>.ts`.
- `src/client/http.ts` is the only HTTP exit; `src/errors.ts` is the only
place exit codes are defined.
- Destructive commands must go through `confirmDestructive` (`--yes` to skip).
- Every command supports `--json`; human-readable output goes through the
helpers in `src/output.ts`.
- The full `--help` surface is pinned by a golden file β€” when you add or
change flags, regenerate it as instructed by the failing test and review
the diff.

## Pull requests

- Branch from `main`; keep PRs focused on one concern.
- Update docs in the same PR: both `README.md` and `README.zh-CN.md` for any
user-visible change, `AGENTS.md` for structural changes, and `CHANGELOG.md`
under the upcoming version heading.
- `npm run check` must pass.

## Releasing (maintainers)

Releases are tag-driven via `.github/workflows/release.yml`:

1. Bump `version` in `package.json` and add the matching `## X.Y.Z` section
at the top of `CHANGELOG.md`; land that on `main`.
2. Tag and push:

```bash
git tag vX.Y.Z && git push origin vX.Y.Z
```

The workflow re-runs the full check gate, publishes to npm with provenance,
and creates the GitHub Release from the CHANGELOG section. It requires the
`NPM_TOKEN` repository secret (an npm automation token with publish rights on
`yuque-open-cli`).

## Reporting security issues

Please do not open public issues for vulnerabilities β€” see
[SECURITY.md](./SECURITY.md).
27 changes: 27 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Security Policy

## Supported versions

Only the latest release published on npm receives security fixes.

## Reporting a vulnerability

Please report vulnerabilities privately via
[GitHub Security Advisories](https://github.com/yuque/yuque-open-cli/security/advisories/new)
β€” do not open a public issue.

Include what you can: affected version, reproduction steps, and impact. You
can expect an acknowledgement within a few business days.

## Scope notes for this CLI

- The CLI authenticates with a Yuque API token supplied via `--token`,
`YUQUE_TOKEN`, or `YUQUE_PERSONAL_TOKEN`. Tokens are only ever sent to the
configured Yuque host (`https://www.yuque.com` by default, or the host you
set via `--host` / `YUQUE_HOST`) as the `X-Auth-Token` header.
Comment on lines +19 to +21

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 Avoid promising host-bound tokens while redirects remain enabled

When the configured API endpoint returns a cross-origin 3xx response, Axios follows it by default, while src/client/http.ts:38-46 sets X-Auth-Token globally without disabling redirects or stripping that custom header before redirecting. Consequently, the token can be forwarded beyond the configured host, contradicting this absolute security guarantee; either enforce same-origin redirects in the HTTP client or qualify the documentation.

Useful? React with πŸ‘Β / πŸ‘Ž.

- The CLI never writes your token to disk. Prefer the environment variable
over `--token` in shared environments β€” command-line flags can be visible
to other processes and shell history.
- Anything that would trick the CLI into sending the token to a non-Yuque
host, leaking it into output/logs, or executing content returned by the
API is in scope and we want to hear about it.
4 changes: 3 additions & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ const { version: VERSION } = require('../package.json') as { version: string };
export function buildProgram(): Command {
const program = new Command('yuque');
program
.description('Scriptable toolkit for the Yuque (语雀) Open API β€” search, read, write, and manage docs')
.description(
'Scriptable toolkit for the Yuque (语雀) Open API β€” search, read, write, and manage docs'
)
.version(VERSION, '-v, --version', 'print the CLI version')
.option('--token <token>', 'Yuque API token (overrides YUQUE_TOKEN / YUQUE_PERSONAL_TOKEN)')
.option('--host <host>', 'Yuque host, e.g. https://your-space.yuque.com (overrides YUQUE_HOST)')
Expand Down
Loading