From 3cc7bb5021541e5658af38090ef4d4a7e4ef9019 Mon Sep 17 00:00:00 2001 From: oratis Date: Sat, 8 Aug 2026 22:10:38 +0800 Subject: [PATCH] feat!: publish as @deepcode/cli and release on partial credentials MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BREAKING CHANGE: the CLI publishes as @deepcode/cli. Install with `npm i -g @deepcode/cli`. The binary is still `deepcode`. The unscoped `deepcode-cli` on npm belongs to an unrelated project (guocong-bincai/deepcode-cli, currently 1.3.2), so publishing under that name was never possible — `pnpm publish` would have 403'd. A scope we control also removes the squatting risk permanently. Historical snapshots (MORNING_REPORT, DEVELOPMENT_PLAN, HANDOFF, BEHAVIOR_PARITY, release-artifacts) keep the old name; they record what was true when written. Separately, the release graph no longer requires every credential to exist. validate detects which secrets are configured and skips the legs that cannot run: no Apple secrets skips build-mac, no NPM_TOKEN skips publish-cli. Skipped rather than failed — a red release for a missing credential teaches people to ignore red releases. A job that actually fails still blocks the release, and the anti-partial-release rule stands: npm publication still waits on both installable artifacts. github-release now runs on what was actually built, and states in the release body which artifacts are absent and why. A release page missing a DMG otherwise reads as "there is no Mac build" rather than "it was not produced this time". Caught by typecheck: `@deepcode/cli` inside a regex literal terminates it early. That assertion is now a toContain. Co-Authored-By: Claude Opus 5 --- .github/workflows/release.yml | 46 ++++++++++++++++++- CHANGELOG.md | 4 ++ README.md | 4 +- apps/cli/README.md | 4 +- apps/cli/package.json | 2 +- apps/cli/src/cli.ts | 2 +- apps/cli/src/commands.ts | 2 +- apps/cli/src/parity-commands.test.ts | 2 +- docs/MIGRATION_FROM_CLAUDE_CODE.md | 2 +- docs/RELEASING.md | 23 ++++++++-- docs/cli-flags.md | 2 +- docs/quickstart.md | 2 +- .../core/src/config/claude-compat.test.ts | 2 +- 13 files changed, 81 insertions(+), 16 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5b4c3a1..65e83f7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,6 +21,8 @@ jobs: version: ${{ steps.version.outputs.version }} channel: ${{ steps.version.outputs.channel }} is_mandatory: ${{ steps.version.outputs.is_mandatory }} + has_apple: ${{ steps.credentials.outputs.has_apple }} + has_npm: ${{ steps.credentials.outputs.has_npm }} steps: - uses: actions/checkout@v7 - uses: pnpm/action-setup@v6 @@ -56,6 +58,25 @@ jobs: - run: pnpm docs:check - run: pnpm release:check + # Which publication legs can even run. A job that cannot possibly succeed + # should be skipped, not failed — a red release for a missing credential + # teaches people to ignore red releases. + - name: Detect available publishing credentials + id: credentials + run: | + if [ -n "${{ secrets.APPLE_ID }}" ] && [ -n "${{ secrets.CSC_LINK }}" ]; then + echo "has_apple=true" >> "$GITHUB_OUTPUT" + else + echo "has_apple=false" >> "$GITHUB_OUTPUT" + echo "::warning::Apple signing secrets absent — skipping the Mac client build. See docs/RELEASING.md." + fi + if [ -n "${{ secrets.NPM_TOKEN }}" ]; then + echo "has_npm=true" >> "$GITHUB_OUTPUT" + else + echo "has_npm=false" >> "$GITHUB_OUTPUT" + echo "::warning::NPM_TOKEN absent — skipping the npm publish. See docs/RELEASING.md." + fi + - name: Install Chromium run: pnpm --filter @deepcode/desktop exec playwright install --with-deps chromium @@ -93,10 +114,13 @@ jobs: # Publish CLI to npm # ---------------------------------------------------------------------- publish-cli: - name: Publish deepcode-cli to npm + name: Publish @deepcode/cli to npm # Avoid a partial release: do not publish npm until both installable # desktop/editor artifacts have built successfully. needs: [validate, build-vscode, build-mac] + # The anti-partial-release rule stands: npm still waits on both installable + # artifacts. It additionally needs a token to exist at all. + if: needs.validate.outputs.has_npm == 'true' runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -209,6 +233,7 @@ jobs: build-mac: name: Build + sign + notarize Mac client (Tauri) needs: validate + if: needs.validate.outputs.has_apple == 'true' runs-on: macos-14 timeout-minutes: 30 steps: @@ -333,6 +358,16 @@ jobs: github-release: name: Publish GitHub Release needs: [validate, publish-cli, build-vscode, build-mac] + # Ship what was actually built. `always()` is required because a skipped + # dependency would otherwise skip this job too — but the guard still refuses + # to publish when something tried and *failed*, so a broken build never + # becomes a release. + if: >- + always() + && needs.validate.result == 'success' + && needs.build-vscode.result == 'success' + && needs.build-mac.result != 'failure' + && needs.publish-cli.result != 'failure' runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -346,6 +381,7 @@ jobs: - run: pnpm install --frozen-lockfile - name: Download Mac DMG + if: needs.build-mac.result == 'success' uses: actions/download-artifact@v8 with: name: mac-release @@ -367,6 +403,14 @@ jobs: FROM=$(git rev-list --max-parents=0 HEAD) fi npx tsx scripts/gen-release-notes.ts "$FROM" HEAD > release-notes.md + # A release page missing an artifact reads as "there is no Mac build" + # rather than "it was not produced this time". Say which. + if [ "${{ needs.build-mac.result }}" != "success" ]; then + printf '\n---\n\n> **No macOS build in this release.** Apple signing credentials were not configured when it was cut; see `docs/RELEASING.md`.\n' >> release-notes.md + fi + if [ "${{ needs.publish-cli.result }}" != "success" ]; then + printf '\n> **Not published to npm.** `NPM_TOKEN` was not configured when this release was cut; install from source or the VSIX.\n' >> release-notes.md + fi cat release-notes.md - name: Create GitHub Release diff --git a/CHANGELOG.md b/CHANGELOG.md index 253a8b9..9899a44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ adopted, what was rejected, and where the implementation diverged from the plan. ### ⚠️ Breaking +- **The CLI is published as `@deepcode/cli`, not `deepcode-cli`.** Install with + `npm i -g @deepcode/cli`. The unscoped name on npm belongs to an unrelated + project, so it was never ours to publish to. The binary is still `deepcode` + and nothing about the tool's behaviour changes. (#249) - **Unattended runs no longer inherit a permissive permission mode.** A `permissions.defaultMode` of `bypassPermissions` or `acceptEdits` — chosen for interactive convenience — is clamped to `default` for scheduled jobs, which diff --git a/README.md b/README.md index 02b663b..6fb53c2 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ DeepCode 让 DeepSeek 可以在本地代码库中执行读取、编辑、命令 ```bash # 1. 装 CLI -npm i -g deepcode-cli +npm i -g @deepcode/cli # 2. 设 DeepSeek key(首次启动会引导) deepcode @@ -82,7 +82,7 @@ packages/ core/ # @deepcode/core — agent loop, providers, tools, MCP, sandbox, hooks (UI-agnostic) shared-ui/ # @deepcode/shared-ui — types shared between CLI + Mac client + VS Code apps/ - cli/ # deepcode-cli — Node.js CLI (npm publishable) + cli/ # @deepcode/cli — Node.js CLI (npm publishable) desktop/ # @deepcode/desktop — Tauri 2 + React Mac client vscode/ # deepcode — VS Code extension (app-server protocol client) lsp/ # @deepcode/lsp — LSP bridge for Neovim/Emacs/Sublime (v1.1) diff --git a/apps/cli/README.md b/apps/cli/README.md index cc3a1b4..9138435 100644 --- a/apps/cli/README.md +++ b/apps/cli/README.md @@ -1,11 +1,11 @@ -# deepcode-cli +# @deepcode/cli DeepCode CLI — Node.js 一键安装的命令行 AI 编程代理。 ## Install (post-v1) ```bash -npm i -g deepcode-cli +npm i -g @deepcode/cli deepcode --help ``` diff --git a/apps/cli/package.json b/apps/cli/package.json index 52e3eb6..7e1c9f2 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,5 +1,5 @@ { - "name": "deepcode-cli", + "name": "@deepcode/cli", "version": "0.3.0", "description": "DeepCode CLI — DeepSeek-powered AI coding agent for real codebases", "license": "MIT", diff --git a/apps/cli/src/cli.ts b/apps/cli/src/cli.ts index 955d119..2ea2a0b 100644 --- a/apps/cli/src/cli.ts +++ b/apps/cli/src/cli.ts @@ -78,7 +78,7 @@ async function main(): Promise { return doctor(); } if (args.upgrade) { - process.stdout.write(`Run: npm i -g deepcode-cli@latest\n`); + process.stdout.write(`Run: npm i -g @deepcode/cli@latest\n`); process.stdout.write(`(The Mac client updates itself; only the CLI needs this.)\n`); return 0; } diff --git a/apps/cli/src/commands.ts b/apps/cli/src/commands.ts index cd5b8b8..41bd3c8 100644 --- a/apps/cli/src/commands.ts +++ b/apps/cli/src/commands.ts @@ -1151,7 +1151,7 @@ export const UpgradeCommand: SlashCommand = { run() { return [ `DeepCode CLI v${VERSION}`, - 'Update the CLI: npm i -g deepcode-cli@latest', + 'Update the CLI: npm i -g @deepcode/cli@latest', 'The macOS desktop app auto-updates via GitHub Releases.', ]; }, diff --git a/apps/cli/src/parity-commands.test.ts b/apps/cli/src/parity-commands.test.ts index be25438..f92ccc0 100644 --- a/apps/cli/src/parity-commands.test.ts +++ b/apps/cli/src/parity-commands.test.ts @@ -139,7 +139,7 @@ describe('/upgrade + /privacy-settings', () => { it('/upgrade shows the version + update instructions', async () => { const out = (await reg.match('/upgrade')!.cmd.run([], ctx())).join('\n'); expect(out).toMatch(/DeepCode CLI v\d/); - expect(out).toMatch(/npm i -g deepcode-cli@latest/); + expect(out).toContain('npm i -g @deepcode/cli@latest'); }); it('/privacy-settings shows data locations + the DeepSeek endpoint', async () => { diff --git a/docs/MIGRATION_FROM_CLAUDE_CODE.md b/docs/MIGRATION_FROM_CLAUDE_CODE.md index 4b6ecf7..670a5d3 100644 --- a/docs/MIGRATION_FROM_CLAUDE_CODE.md +++ b/docs/MIGRATION_FROM_CLAUDE_CODE.md @@ -25,7 +25,7 @@ The copy below is only needed if you want DeepCode to own its own copies. ```bash # 1. Install DeepCode CLI -npm install -g deepcode-cli +npm install -g @deepcode/cli # 2. Set your DeepSeek key mkdir -p ~/.deepcode diff --git a/docs/RELEASING.md b/docs/RELEASING.md index f8232b2..281a6fc 100644 --- a/docs/RELEASING.md +++ b/docs/RELEASING.md @@ -10,7 +10,19 @@ build failure cannot create an avoidable partial release. ### 1. GitHub Actions secrets Set these in repo settings → Secrets and variables → Actions → New -repository secret. All six are required for the complete release graph. +repository secret. All six are required for the _complete_ release graph. + +**A release without them still works, partially.** `validate` detects which +credential sets are present and skips the legs it cannot run: + +| Missing | Effect | +| ------------- | ---------------------------------------------------------------- | +| Apple secrets | `build-mac` is **skipped**; no DMG, and the release notes say so | +| `NPM_TOKEN` | `publish-cli` is **skipped**; nothing is published to npm | +| Neither | GitHub Release still ships with the VSIX and source | + +Skipped, not failed — a red release for a missing credential teaches people to +ignore red releases. A job that actually _fails_ still blocks the release. | Secret | Purpose | | ----------------------------- | ----------------------------------------------------------------- | @@ -19,7 +31,7 @@ repository secret. All six are required for the complete release graph. | `APPLE_TEAM_ID` | 10-character team ID (from developer.apple.com → membership) | | `CSC_LINK` | Base64-encoded `.p12` of the Developer ID Application cert | | `CSC_KEY_PASSWORD` | Password used when exporting the `.p12` | -| `NPM_TOKEN` | npm access token with `publish` scope | +| `NPM_TOKEN` | npm access token with `publish` scope for the `@deepcode` scope | ### 2. Export the Developer ID certificate @@ -48,6 +60,11 @@ App-specific passwords → Generate. Save the 16-char password as [npmjs.com](https://www.npmjs.com) → account → Access Tokens → Generate new token → **Automation** (CI-friendly) → save as `NPM_TOKEN`. +The CLI publishes as **`@deepcode/cli`**. The unscoped `deepcode-cli` on npm +belongs to an unrelated project, so the `@deepcode` org must exist and the token +must be able to publish into it. The workflow already passes `--access public`, +which scoped packages need in order not to default to private. + ## Releasing ```bash @@ -151,7 +168,7 @@ download manually; the "Relaunch to update" flow lights up once the feed exists. ## After a release -- Verify: `npm view deepcode-cli@` shows the new version +- Verify: `npm view @deepcode/cli@` shows the new version - Verify: `https://github.com/oratis/deepcode/releases/tag/v` has the DMG and version-matched VSIX attached - Optional: announce in the README / homepage diff --git a/docs/cli-flags.md b/docs/cli-flags.md index 2424c61..d8e80c1 100644 --- a/docs/cli-flags.md +++ b/docs/cli-flags.md @@ -20,7 +20,7 @@ deepcode upgrade # CLI self-update | `-h`, `--help` | Print usage | M2 ✅ | | `-v`, `--version` | Print version | M2 ✅ | | `doctor` | Health check (runtime, credentials, config provenance/trust/issues) | M2 ✅ | -| `upgrade` | Print `npm i -g deepcode-cli@latest` hint | M2 ✅ | +| `upgrade` | Print `npm i -g @deepcode/cli@latest` hint | M2 ✅ | | `-p`, `--print ` | Headless one-shot | M8 | ## Session shaping diff --git a/docs/quickstart.md b/docs/quickstart.md index 8ed5221..ef339e6 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -15,7 +15,7 @@ permission, and recovery behavior is still being unified; see the ```bash # 1. Install -npm i -g deepcode-cli +npm i -g @deepcode/cli # 2. Start the REPL — the first run walks you through setting your DeepSeek key deepcode diff --git a/packages/core/src/config/claude-compat.test.ts b/packages/core/src/config/claude-compat.test.ts index 6473d2a..8045d2c 100644 --- a/packages/core/src/config/claude-compat.test.ts +++ b/packages/core/src/config/claude-compat.test.ts @@ -2,7 +2,7 @@ // // The migration guide asked people to `mv ~/.claude/... ~/.deepcode/...` before // DeepCode would see anything they had. That is five steps of grit in front of -// `npm i -g deepcode-cli && deepcode`, and it is the first thing a migrating +// `npm i -g @deepcode/cli && deepcode`, and it is the first thing a migrating // user hits. import { mkdir, mkdtemp, writeFile } from 'node:fs/promises';