From 1d1a7fee3b7ae5e9135695366e8444bcdf2d02aa Mon Sep 17 00:00:00 2001 From: Lahfir Date: Mon, 23 Feb 2026 00:49:55 -0800 Subject: [PATCH] feat: add release automation with GitHub Releases and npm distribution Automated release pipeline using release-please for gated Release PRs, conventional commits for SemVer version bumps, and npm distribution with postinstall binary download from GitHub Releases. --- .github/workflows/release.yml | 160 +++++++++++++++++++ .gitignore | 5 + .release-please-manifest.json | 3 + CLAUDE.md | 61 +++++-- npm/bin/agent-desktop.js | 86 ++++++++++ npm/package.json | 33 ++++ npm/scripts/postinstall.js | 288 ++++++++++++++++++++++++++++++++++ release-please-config.json | 29 ++++ 8 files changed, 654 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 .release-please-manifest.json create mode 100755 npm/bin/agent-desktop.js create mode 100644 npm/package.json create mode 100644 npm/scripts/postinstall.js create mode 100644 release-please-config.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..836fa8f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,160 @@ +name: Release + +on: + push: + branches: [main] + +env: + CARGO_TERM_COLOR: always + +jobs: + release-please: + name: Release Please + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} + version: ${{ steps.release.outputs.major }}.${{ steps.release.outputs.minor }}.${{ steps.release.outputs.patch }} + steps: + - uses: googleapis/release-please-action@v4 + id: release + with: + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + + build: + name: Build (${{ matrix.target }}) + needs: release-please + if: needs.release-please.outputs.release_created == 'true' + runs-on: ${{ matrix.runner }} + strategy: + matrix: + include: + - target: aarch64-apple-darwin + runner: macos-latest + - target: x86_64-apple-darwin + runner: macos-13 + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.release-please.outputs.tag_name }} + + - name: Install Rust toolchain + run: | + rustup show + rustup target add ${{ matrix.target }} + + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('**/Cargo.toml') }} + restore-keys: ${{ runner.os }}-${{ matrix.target }}-cargo- + + - name: Build release binary + run: cargo build --release --target ${{ matrix.target }} + + - name: Check binary size + run: | + SIZE=$(stat -f%z target/${{ matrix.target }}/release/agent-desktop) + LIMIT=$((15 * 1024 * 1024)) + echo "Binary size: $(du -sh target/${{ matrix.target }}/release/agent-desktop | cut -f1)" + if [ "$SIZE" -gt "$LIMIT" ]; then + echo "FAIL: binary exceeds 15MB limit (${SIZE} bytes)" + exit 1 + fi + + - name: Create tarball + run: | + VERSION=${{ needs.release-please.outputs.version }} + TARBALL="agent-desktop-v${VERSION}-${{ matrix.target }}.tar.gz" + tar -czf "${TARBALL}" -C target/${{ matrix.target }}/release agent-desktop + shasum -a 256 "${TARBALL}" > "${TARBALL}.sha256" + echo "TARBALL=${TARBALL}" >> "$GITHUB_ENV" + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: binary-${{ matrix.target }} + path: | + agent-desktop-v*.tar.gz + agent-desktop-v*.tar.gz.sha256 + + publish-github: + name: Publish to GitHub Release + needs: [release-please, build] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + pattern: binary-* + merge-multiple: true + + - name: Create checksums file + run: | + cat *.sha256 > checksums.txt + echo "Checksums:" + cat checksums.txt + + - name: Upload assets to GitHub Release + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + run: | + gh release upload ${{ needs.release-please.outputs.tag_name }} \ + *.tar.gz checksums.txt \ + --clobber + + publish-npm: + name: Publish to npm + needs: [release-please, publish-github] + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ needs.release-please.outputs.tag_name }} + + - uses: actions/setup-node@v4 + with: + node-version: 20 + registry-url: https://registry.npmjs.org + + - name: Verify GitHub Release assets + env: + GH_TOKEN: ${{ github.token }} + run: | + ASSETS=$(gh release view ${{ needs.release-please.outputs.tag_name }} --json assets --jq '.assets | length') + if [ "$ASSETS" -lt 3 ]; then + echo "FAIL: expected at least 3 assets (2 tarballs + checksums), found ${ASSETS}" + exit 1 + fi + echo "OK: ${ASSETS} assets found on release" + + - name: Sync version to package.json + run: | + VERSION=${{ needs.release-please.outputs.version }} + cd npm + node -e " + const pkg = require('./package.json'); + pkg.version = '${VERSION}'; + require('fs').writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n'); + " + echo "npm package version set to ${VERSION}" + + - name: Publish to npm + run: npm publish --provenance --access public + working-directory: npm + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index 24c23ad..ef7334e 100644 --- a/.gitignore +++ b/.gitignore @@ -62,6 +62,11 @@ dist/ *.rpm *.snap +# npm (platform binaries downloaded by postinstall) +npm/bin/agent-desktop-* +npm/node_modules/ +node_modules/ + # Compound Engineering docs/ todos/ diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..466df71 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.1.0" +} diff --git a/CLAUDE.md b/CLAUDE.md index 948bd7a..8a47b0f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,4 +1,25 @@ -# agent-desktop +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Common Commands + +```bash +cargo build # Debug build +cargo build --release # Release build (<15MB target) +cargo test --lib --workspace # Run all unit tests +cargo test --lib -p agent-desktop-core # Test core crate only +cargo test --lib -p agent-desktop-macos # Test macOS crate only +cargo test test_name # Run a single test by name +cargo clippy --all-targets -- -D warnings # Lint (must pass, zero warnings) +cargo fmt --all -- --check # Format check +cargo fmt --all # Auto-format +cargo tree -p agent-desktop-core # Verify no platform crate leaks (CI enforces) +``` + +Run the binary: `./target/release/agent-desktop snapshot --app Finder -i` + +## Project Overview Cross-platform Rust CLI + MCP server enabling AI agents to observe and control desktop applications via native OS accessibility trees. @@ -7,7 +28,19 @@ Cross-platform Rust CLI + MCP server enabling AI agents to observe and control d - All commits are authored by **Lahfir** - NEVER add `Co-Authored-By` lines, AI attribution badges, or "Generated with" footers - NEVER include co-committers of any kind -- Commit messages: concise, imperative mood, focus on "why" not "what" +- **Conventional Commits required.** Every commit message must use a type prefix: + - `feat:` — new feature (triggers minor version bump) + - `fix:` — bug fix (triggers patch version bump) + - `feat!:` or `BREAKING CHANGE:` footer — breaking change (triggers major version bump) + - `docs:` — documentation only + - `style:` — formatting, no code change + - `refactor:` — code change that neither fixes a bug nor adds a feature + - `chore:` — maintenance tasks, dependencies + - `ci:` — CI/CD changes + - `test:` — adding or fixing tests +- Format: `type: concise imperative description` (lowercase type, no capital after colon) +- Focus on "why" not "what" +- Examples: `feat: add scroll-to command`, `fix: prevent stale ref on window resize`, `ci: add binary size check` ## Core Principle @@ -28,8 +61,11 @@ agent-desktop/ │ ├── windows/ # agent-desktop-windows (stub → Phase 2) │ └── linux/ # agent-desktop-linux (stub → Phase 2) ├── src/ # agent-desktop binary (entry point) -│ ├── main.rs # mode detection, dispatch -│ └── cli.rs # clap derive structs +│ ├── main.rs # entry point, permission check, JSON envelope +│ ├── cli.rs # clap derive enum (Commands) +│ ├── cli_args.rs # all command argument structs +│ ├── dispatch.rs # command dispatcher + parse helpers +│ └── batch_dispatch.rs # batch command execution └── tests/ ├── fixtures/ # golden JSON snapshots └── integration/ # macOS CI integration tests @@ -370,17 +406,20 @@ Target binary size: <15MB per platform. - `cargo test --workspace` - Binary size check: fail if release binary exceeds 15MB -## Phase 1 Command Scope (30 commands) +## Implemented Commands (50) | Category | Commands | |----------|----------| -| App/Window (5) | `launch`, `close-app`, `list-windows`, `list-apps`, `focus-window` | -| Observation (15) | `snapshot`, `screenshot`, `find`, `get` (text, value, title, bounds, role, states), `is` (visible, enabled, checked, focused, expanded) | -| Interaction (11) | `click`, `double-click`, `right-click`, `type`, `set-value`, `focus`, `select`, `toggle`, `expand`, `collapse`, `scroll` | -| Keyboard (1) | `press` | -| Clipboard (2) | `clipboard get`, `clipboard set` | -| Wait (3) | `wait` (ms), `wait --element`, `wait --window` | +| App/Window (10) | `launch`, `close-app`, `list-windows`, `list-apps`, `focus-window`, `resize-window`, `move-window`, `minimize`, `maximize`, `restore` | +| Observation (6) | `snapshot`, `screenshot`, `find`, `get`, `is`, `list-surfaces` | +| Interaction (14) | `click`, `double-click`, `triple-click`, `right-click`, `type`, `set-value`, `clear`, `focus`, `select`, `toggle`, `check`, `uncheck`, `expand`, `collapse` | +| Scroll (2) | `scroll`, `scroll-to` | +| Keyboard (3) | `press`, `key-down`, `key-up` | +| Mouse (5) | `hover`, `drag`, `mouse-move`, `mouse-click`, `mouse-down`, `mouse-up` | +| Clipboard (3) | `clipboard-get`, `clipboard-set`, `clipboard-clear` | +| Wait (1) | `wait` (with `--element`, `--window`, `--text`, `--menu` flags) | | System (3) | `status`, `permissions`, `version` | +| Batch (1) | `batch` | ## Non-Goals diff --git a/npm/bin/agent-desktop.js b/npm/bin/agent-desktop.js new file mode 100755 index 0000000..a05af6a --- /dev/null +++ b/npm/bin/agent-desktop.js @@ -0,0 +1,86 @@ +#!/usr/bin/env node + +const { spawn } = require('child_process'); +const { existsSync, accessSync, chmodSync, constants } = require('fs'); +const { dirname, join } = require('path'); +const { platform, arch } = require('os'); + +const binDir = __dirname; + +function getBinaryName() { + const os = platform(); + const cpuArch = arch(); + + const platformMap = { + 'darwin-arm64': 'agent-desktop-darwin-arm64', + 'darwin-x64': 'agent-desktop-darwin-x64', + 'linux-x64': 'agent-desktop-linux-x64', + 'linux-arm64': 'agent-desktop-linux-arm64', + 'win32-x64': 'agent-desktop-win32-x64.exe', + }; + + return platformMap[`${os}-${cpuArch}`] || null; +} + +function main() { + const binaryName = getBinaryName(); + + if (!binaryName) { + console.error(`Error: Unsupported platform: ${platform()}-${arch()}`); + console.error('agent-desktop currently supports: macOS (ARM64, x64)'); + console.error('Windows and Linux support is coming in Phase 2.'); + console.error('See: https://github.com/lahfir/agent-desktop'); + process.exit(1); + } + + const binaryPath = join(binDir, binaryName); + + if (!binaryPath || !existsSync(binaryPath)) { + console.error(`Error: Native binary not found for ${platform()}-${arch()}`); + console.error(`Expected: ${binaryPath}`); + console.error(''); + console.error('Try reinstalling:'); + console.error(' npm install -g agent-desktop'); + console.error(''); + console.error('Or download directly from:'); + console.error(' https://github.com/lahfir/agent-desktop/releases'); + + if (typeof process.versions.bun !== 'undefined') { + console.error(''); + console.error('Bun detected — postinstall scripts require --trust:'); + console.error(' bun install -g --trust agent-desktop'); + } + + process.exit(1); + } + + if (platform() !== 'win32') { + try { + accessSync(binaryPath, constants.X_OK); + } catch { + try { + chmodSync(binaryPath, 0o755); + } catch (err) { + console.error(`Error: Cannot make binary executable: ${err.message}`); + console.error('Try running: chmod +x ' + binaryPath); + process.exit(1); + } + } + } + + const child = spawn(binaryPath, process.argv.slice(2), { + stdio: 'inherit', + windowsHide: false, + }); + + child.on('error', (err) => { + console.error(`Error executing binary: ${err.message}`); + process.exit(1); + }); + + child.on('close', (code) => { + process.exit(code ?? 0); + }); +} + +main(); diff --git a/npm/package.json b/npm/package.json new file mode 100644 index 0000000..82c33ee --- /dev/null +++ b/npm/package.json @@ -0,0 +1,33 @@ +{ + "name": "agent-desktop", + "version": "0.0.0-development", + "description": "AI agent tool for observing and controlling desktop applications via native OS accessibility trees", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/lahfir/agent-desktop.git" + }, + "homepage": "https://github.com/lahfir/agent-desktop", + "bin": { + "agent-desktop": "./bin/agent-desktop.js" + }, + "scripts": { + "postinstall": "node scripts/postinstall.js" + }, + "files": [ + "bin", + "scripts" + ], + "engines": { + "node": ">=18" + }, + "keywords": [ + "desktop", + "automation", + "accessibility", + "cli", + "agent", + "macos", + "a11y" + ] +} diff --git a/npm/scripts/postinstall.js b/npm/scripts/postinstall.js new file mode 100644 index 0000000..f568bce --- /dev/null +++ b/npm/scripts/postinstall.js @@ -0,0 +1,288 @@ +#!/usr/bin/env node + +const { existsSync, mkdirSync, chmodSync, createWriteStream, unlinkSync, renameSync, writeFileSync, symlinkSync, lstatSync } = require('fs'); +const { readFileSync } = require('fs'); +const { dirname, join } = require('path'); +const { platform, arch } = require('os'); +const { get } = require('https'); +const { execSync } = require('child_process'); +const { createHash } = require('crypto'); + +const projectRoot = join(__dirname, '..'); +const binDir = join(projectRoot, 'bin'); +const packageJson = JSON.parse(readFileSync(join(projectRoot, 'package.json'), 'utf8')); +const version = packageJson.version; + +const GITHUB_REPO = 'lahfir/agent-desktop'; +const MAX_RETRIES = 3; +const TIMEOUT_MS = 60000; + +const TARGET_MAP = { + 'darwin-arm64': 'aarch64-apple-darwin', + 'darwin-x64': 'x86_64-apple-darwin', + 'linux-x64': 'x86_64-unknown-linux-gnu', + 'linux-arm64': 'aarch64-unknown-linux-gnu', + 'win32-x64': 'x86_64-pc-windows-msvc', +}; + +const BINARY_NAME_MAP = { + 'darwin-arm64': 'agent-desktop-darwin-arm64', + 'darwin-x64': 'agent-desktop-darwin-x64', + 'linux-x64': 'agent-desktop-linux-x64', + 'linux-arm64': 'agent-desktop-linux-arm64', + 'win32-x64': 'agent-desktop-win32-x64.exe', +}; + +const SUPPORTED_PLATFORMS = ['darwin']; + +function log(msg) { + process.stderr.write(`agent-desktop: ${msg}\n`); +} + +function getPlatformKey() { + return `${platform()}-${arch()}`; +} + +function downloadWithRedirects(url, dest, timeout) { + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + reject(new Error(`Download timed out after ${timeout}ms`)); + }, timeout); + + const doRequest = (requestUrl) => { + const parsedUrl = new URL(requestUrl); + const options = { + hostname: parsedUrl.hostname, + path: parsedUrl.pathname + parsedUrl.search, + headers: { 'User-Agent': `agent-desktop/${version}` }, + }; + + const proxy = process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy; + if (proxy) { + log(`Using proxy: ${proxy}`); + } + + get(requestUrl, options, (response) => { + if (response.statusCode === 301 || response.statusCode === 302) { + doRequest(response.headers.location); + return; + } + + if (response.statusCode !== 200) { + clearTimeout(timer); + reject(new Error(`HTTP ${response.statusCode} downloading ${requestUrl}`)); + return; + } + + const tmpDest = dest + '.tmp'; + const file = createWriteStream(tmpDest); + response.pipe(file); + file.on('finish', () => { + file.close(); + clearTimeout(timer); + try { + renameSync(tmpDest, dest); + resolve(); + } catch (err) { + reject(err); + } + }); + }).on('error', (err) => { + clearTimeout(timer); + reject(err); + }); + }; + + doRequest(url); + }); +} + +async function downloadWithRetry(url, dest) { + for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { + try { + await downloadWithRedirects(url, dest, TIMEOUT_MS); + return; + } catch (err) { + if (attempt === MAX_RETRIES) throw err; + const delay = Math.pow(2, attempt) * 1000; + log(`Download failed (attempt ${attempt}/${MAX_RETRIES}): ${err.message}`); + log(`Retrying in ${delay / 1000}s...`); + await new Promise((r) => setTimeout(r, delay)); + } + } +} + +function readFileContent(path) { + return readFileSync(path, 'utf8'); +} + +function verifyChecksum(filePath, expectedHash) { + const fileBuffer = readFileSync(filePath); + const hash = createHash('sha256').update(fileBuffer).digest('hex'); + return hash === expectedHash; +} + +async function fixGlobalInstallBin() { + if (platform() === 'win32') return; + + let npmBinDir; + try { + const prefix = execSync('npm prefix -g', { encoding: 'utf8' }).trim(); + npmBinDir = join(prefix, 'bin'); + } catch { + return; + } + + const symlinkPath = join(npmBinDir, 'agent-desktop'); + const platformKey = getPlatformKey(); + const binaryName = BINARY_NAME_MAP[platformKey]; + if (!binaryName) return; + + const binaryPath = join(binDir, binaryName); + + try { + const stat = lstatSync(symlinkPath); + if (!stat.isSymbolicLink()) return; + } catch { + return; + } + + try { + unlinkSync(symlinkPath); + symlinkSync(binaryPath, symlinkPath); + log('Optimized: symlink points to native binary (zero overhead)'); + } catch (err) { + log(`Could not optimize symlink: ${err.message}`); + log('CLI will work via Node.js wrapper (slightly slower startup)'); + } +} + +async function main() { + if (process.env.AGENT_DESKTOP_SKIP_DOWNLOAD === '1') { + log('Skipping binary download (AGENT_DESKTOP_SKIP_DOWNLOAD=1)'); + return; + } + + const platformKey = getPlatformKey(); + const target = TARGET_MAP[platformKey]; + const binaryName = BINARY_NAME_MAP[platformKey]; + + if (!target || !binaryName) { + if (!SUPPORTED_PLATFORMS.includes(platform())) { + log(`agent-desktop currently supports macOS only.`); + log(`Windows and Linux support is coming in Phase 2.`); + log(`See: https://github.com/${GITHUB_REPO}`); + return; + } + log(`Unsupported architecture: ${platformKey}`); + return; + } + + if (!SUPPORTED_PLATFORMS.includes(platform())) { + log(`agent-desktop currently supports macOS only.`); + log(`${platform()} support is coming in Phase 2.`); + log(`See: https://github.com/${GITHUB_REPO}`); + return; + } + + const binaryPath = join(binDir, binaryName); + + if (process.env.AGENT_DESKTOP_BINARY_PATH) { + const customPath = process.env.AGENT_DESKTOP_BINARY_PATH; + if (existsSync(customPath)) { + try { + const content = readFileSync(customPath); + writeFileSync(binaryPath, content); + chmodSync(binaryPath, 0o755); + log(`Using binary from AGENT_DESKTOP_BINARY_PATH: ${customPath}`); + await fixGlobalInstallBin(); + return; + } catch (err) { + log(`Failed to copy from AGENT_DESKTOP_BINARY_PATH: ${err.message}`); + } + } else { + log(`AGENT_DESKTOP_BINARY_PATH not found: ${customPath}`); + } + } + + if (existsSync(binaryPath)) { + if (platform() !== 'win32') { + chmodSync(binaryPath, 0o755); + } + log(`Native binary ready: ${binaryName}`); + await fixGlobalInstallBin(); + return; + } + + if (!existsSync(binDir)) { + mkdirSync(binDir, { recursive: true }); + } + + const tarball = `agent-desktop-v${version}-${target}.tar.gz`; + const tarballUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${version}/${tarball}`; + const checksumsUrl = `https://github.com/${GITHUB_REPO}/releases/download/v${version}/checksums.txt`; + const tarballPath = join(binDir, tarball); + const checksumsPath = join(binDir, 'checksums.txt'); + + log(`Downloading native binary for ${platformKey}...`); + + try { + await downloadWithRetry(tarballUrl, tarballPath); + log(`Downloaded: ${tarball}`); + + try { + await downloadWithRetry(checksumsUrl, checksumsPath); + const checksums = readFileContent(checksumsPath); + const expectedLine = checksums.split('\n').find((line) => line.includes(tarball)); + if (expectedLine) { + const expectedHash = expectedLine.split(/\s+/)[0]; + if (!verifyChecksum(tarballPath, expectedHash)) { + log('WARNING: Checksum verification failed. Binary may be corrupted.'); + log('Try reinstalling: npm install -g agent-desktop'); + unlinkSync(tarballPath); + unlinkSync(checksumsPath); + return; + } + log('Checksum verified'); + } + unlinkSync(checksumsPath); + } catch (err) { + log(`Could not verify checksum: ${err.message}`); + } + + execSync(`tar -xzf "${tarballPath}" -C "${binDir}"`, { stdio: 'pipe' }); + + const extractedBinary = join(binDir, 'agent-desktop'); + if (existsSync(extractedBinary) && extractedBinary !== binaryPath) { + renameSync(extractedBinary, binaryPath); + } + + if (platform() !== 'win32') { + chmodSync(binaryPath, 0o755); + } + + unlinkSync(tarballPath); + log(`Installed native binary: ${binaryName}`); + } catch (err) { + log(`Could not download native binary: ${err.message}`); + log(''); + log('You can download manually from:'); + log(` ${tarballUrl}`); + log(''); + log(`Then place the binary at: ${binaryPath}`); + + try { + if (existsSync(tarballPath)) unlinkSync(tarballPath); + if (existsSync(checksumsPath)) unlinkSync(checksumsPath); + } catch {} + + return; + } + + await fixGlobalInstallBin(); +} + +main().catch((err) => { + log(`Postinstall error: ${err.message}`); + process.exit(0); +}); diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..5f55a04 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "release-type": "rust", + "bump-minor-pre-major": true, + "bump-patch-for-minor-pre-major": true, + "packages": { + ".": { + "package-name": "agent-desktop", + "changelog-path": "CHANGELOG.md", + "extra-files": [ + "npm/package.json" + ] + } + }, + "plugins": [ + "cargo-workspace" + ], + "changelog-sections": [ + { "type": "feat", "section": "Features" }, + { "type": "fix", "section": "Bug Fixes" }, + { "type": "perf", "section": "Performance" }, + { "type": "refactor", "section": "Refactoring", "hidden": true }, + { "type": "docs", "section": "Documentation", "hidden": true }, + { "type": "style", "hidden": true }, + { "type": "chore", "hidden": true }, + { "type": "ci", "hidden": true }, + { "type": "test", "hidden": true } + ] +}