diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..e4c7a0e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -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 diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..a3a6e1b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -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. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..7a90316 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -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 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..018260c --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,8 @@ + + +## 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` diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..a914ca2 --- /dev/null +++ b/.github/workflows/release.yml @@ -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: | + 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 }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..a68bcb4 --- /dev/null +++ b/CONTRIBUTING.md @@ -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/.ts (flags, confirmation, rendering) + └── client/api/.ts (typed calls, envelope unwrap) + └── client/http.ts (auth header, retry/backoff, YuqueError) +``` + +- One domain = one `src/commands/.ts` exporting a single + `registerCommands` function, plus one thin `src/client/api/.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). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..51e5445 --- /dev/null +++ b/SECURITY.md @@ -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. +- 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. diff --git a/src/cli.ts b/src/cli.ts index ee3d185..8eb3104 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -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 ', 'Yuque API token (overrides YUQUE_TOKEN / YUQUE_PERSONAL_TOKEN)') .option('--host ', 'Yuque host, e.g. https://your-space.yuque.com (overrides YUQUE_HOST)')