{"id":49896466,"url":"https://github.com/lacymorrow/shipx","last_synced_at":"2026-05-21T00:10:11.561Z","repository":{"id":357274263,"uuid":"1231642646","full_name":"lacymorrow/shipx","owner":"lacymorrow","description":"Interactive release CLI — bump, tag, publish, ship. npm · Cargo · Homebrew · GitHub.","archived":false,"fork":false,"pushed_at":"2026-05-16T00:46:35.000Z","size":601,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-16T00:52:57.992Z","etag":null,"topics":["cargo","clack","devtools","homebrew","interactive-cli","npm-publish","release-cli","semver","tauri","typescript"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/@lacymorrow/shipx","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lacymorrow.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"lacymorrow","patreon":"lacymorrow","ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":["buymeacoffee.com/lm","lacymorrow.com/donate"]}},"created_at":"2026-05-07T06:40:11.000Z","updated_at":"2026-05-16T00:45:42.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/lacymorrow/shipx","commit_stats":null,"previous_names":["lacymorrow/shipx"],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/lacymorrow/shipx","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lacymorrow%2Fshipx","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lacymorrow%2Fshipx/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lacymorrow%2Fshipx/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lacymorrow%2Fshipx/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lacymorrow","download_url":"https://codeload.github.com/lacymorrow/shipx/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lacymorrow%2Fshipx/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33281501,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-20T15:12:43.734Z","status":"ssl_error","status_checked_at":"2026-05-20T15:12:42.300Z","response_time":356,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cargo","clack","devtools","homebrew","interactive-cli","npm-publish","release-cli","semver","tauri","typescript"],"created_at":"2026-05-16T00:12:33.891Z","updated_at":"2026-05-21T00:10:11.556Z","avatar_url":"https://github.com/lacymorrow.png","language":"TypeScript","funding_links":["https://github.com/sponsors/lacymorrow","https://patreon.com/lacymorrow","buymeacoffee.com/lm","lacymorrow.com/donate","https://buymeacoffee.com/lm"],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003ca href=\"https://github.com/lacymorrow/shipx\"\u003e\n    \u003cpicture\u003e\n      \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://raw.githubusercontent.com/lacymorrow/shipx/HEAD/.github/assets/logo-horizontal-dark.svg\"\u003e\n      \u003cimg src=\"https://raw.githubusercontent.com/lacymorrow/shipx/HEAD/.github/assets/logo-horizontal.svg\" alt=\"shipx\" width=\"320\"\u003e\n    \u003c/picture\u003e\n  \u003c/a\u003e\n\n  \u003cp\u003e\u003cstrong\u003eInteractive release CLI\u003c/strong\u003e ➔ bump, tag, publish, and ship — npm · Cargo · Homebrew · GitHub.\u003c/p\u003e\n\n  \u003cp\u003e\n    \u003ca href=\"https://www.npmjs.com/package/@lacymorrow/shipx\"\u003e\u003cimg alt=\"npm version\" src=\"https://img.shields.io/npm/v/@lacymorrow/shipx?style=flat\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://www.npmjs.com/package/@lacymorrow/shipx\"\u003e\u003cimg alt=\"npm downloads\" src=\"https://img.shields.io/npm/dm/@lacymorrow/shipx?style=flat\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://github.com/lacymorrow/shipx/actions/workflows/ci.yml\"\u003e\u003cimg alt=\"CI\" src=\"https://img.shields.io/github/actions/workflow/status/lacymorrow/shipx/ci.yml?style=flat\u0026label=CI\"\u003e\u003c/a\u003e\n    \u003ca href=\"./LICENSE\"\u003e\u003cimg alt=\"License\" src=\"https://img.shields.io/npm/l/@lacymorrow/shipx?style=flat\"\u003e\u003c/a\u003e\n    \u003ca href=\"https://nodejs.org\"\u003e\u003cimg alt=\"Node\" src=\"https://img.shields.io/node/v/@lacymorrow/shipx?style=flat\"\u003e\u003c/a\u003e\n  \u003c/p\u003e\n\n  \u003cimg src=\"https://raw.githubusercontent.com/lacymorrow/shipx/HEAD/.github/assets/demo.gif\" alt=\"shipx running an interactive release in the terminal\" width=\"900\"\u003e\n\u003c/div\u003e\n\n---\n\n\u003e [!NOTE]\n\u003e shipx is a thin, opinionated wrapper around `npm publish`, `git tag`, `gh release`, `cargo set-version`, and a Homebrew formula update. It's a beautiful pipeline for the release ritual you already know — not a magic black box.\n\n## Why shipx\n\nReleasing a package is the same nine commands every time, in the same order, and you don't want to forget any of them or run them out of order.\n\n- **Beautiful interactive UI** built on [@clack/prompts](https://github.com/bombshell-dev/clack) — spinners, prompts, and confirms that you actually enjoy looking at.\n- **One config, every channel.** npm, GitHub releases, Cargo workspaces (Tauri-friendly), and Homebrew tap formula — all from a single `shipx.config.ts`.\n- **Stop on red.** Preflight refuses to run on a dirty tree or the wrong branch. Each step is a single shell-out — no hidden state, no surprises.\n- **Beta path.** `shipx --beta` increments `-beta.N` and publishes with the `beta` dist-tag. Homebrew is skipped automatically.\n- **Recoverable.** If `npm publish` fails (auth, OTP, network), shipx drops into an interactive retry loop instead of aborting the whole pipeline.\n- **Zero install.** `npx @lacymorrow/shipx` runs against the project in your current directory.\n\n## Install\n\n```bash\n# Global\nnpm install -g @lacymorrow/shipx\nshipx\n\n# One-off\nnpx @lacymorrow/shipx\n\n# In your repo\nnpm install --save-dev @lacymorrow/shipx\n```\n\nRequires Node ≥20. `gh` CLI is required for `githubRelease`. `cargo-edit` is required if you bump Cargo workspaces.\n\n## Usage\n\n```bash\n# Interactive — prompts for patch / minor / major\nshipx\n\n# Skip the prompt\nshipx patch\nshipx minor\nshipx major\n\n# Explicit version\nshipx 2.0.0\n\n# Beta release (publishes with --tag beta, skips Homebrew)\nshipx --beta\n\n# Multi-project deploy — scan parent dir, batch-publish with one OTP\ncd ~/repo \u0026\u0026 shipx --multi\n```\n\n\u003e [!TIP]\n\u003e Set `SHIPX_ROOT=/path/to/project` to run shipx against a project other than the current directory — useful in monorepo automation.\n\n## Pipeline\n\n```mermaid\nflowchart LR\n    A[preflight] --\u003e B[bump version]\n    B --\u003e C[changelog]\n    C --\u003e D[commit + tag]\n    D --\u003e E[push]\n    E --\u003e F[GitHub release]\n    F --\u003e G[npm publish]\n    G --\u003e H[Homebrew]\n\n    style A fill:#c026d3,stroke:#c026d3,color:#fff\n    style H fill:#c026d3,stroke:#c026d3,color:#fff\n```\n\nEach step is independently toggleable. Set `steps.\u003cname\u003e: false` to skip it.\n\n## Configuration\n\nshipx looks for config in this order, first hit wins:\n\n1. `shipx.config.ts` / `shipx.config.js`\n2. `.shipxrc.json` / `.shipxrc`\n3. `\"shipx\"` key in `package.json`\n4. Defaults (auto-detects `package.json`, `src-tauri/Cargo.toml`, and sibling `../homebrew-tap`)\n\n### Example `shipx.config.ts`\n\n```ts\nimport type { ShipConfig } from \"@lacymorrow/shipx\";\n\nexport default {\n  packageJsonPaths: [\"package.json\", \"packages/core/package.json\"],\n  bumpFiles: [\n    {\n      path: \"bin/cli\",\n      pattern: /^VERSION=\"[^\"]*\"/m,\n      replacement: (v) =\u003e `VERSION=\"${v}\"`,\n    },\n  ],\n  cargoWorkspaces: [\"src-tauri\"],\n  steps: {\n    homebrew: true,\n    githubRelease: true,\n  },\n  git: {\n    releaseBranch: \"main\",\n    tagPrefix: \"v\",\n    extraTags: [\"cua-{tag}\"],\n    commitMessage: \"release: {tag}\",\n  },\n  npm: {\n    access: \"public\",\n  },\n  homebrew: {\n    tapPath: \"../homebrew-tap\",\n    formulaFile: \"Formula/mytool.rb\",\n    repoSlug: \"user/repo\",\n  },\n} satisfies ShipConfig;\n```\n\n### All options\n\n| Option | Type | Default | What it does |\n|---|---|---|---|\n| `packageJsonPaths` | `string[]` | Auto (`[\"package.json\"]`) | Paths to `package.json` files to bump |\n| `bumpFiles` | `BumpFileConfig[]` | `[]` | Additional files with regex-based version bumping |\n| `cargoWorkspaces` | `string[]` | Auto (`[\"src-tauri\"]` if exists) | Cargo workspace dirs to bump via `cargo set-version --workspace`. Use `[]` to opt out. |\n| `steps.preflight` | `boolean` | `true` | Require clean tree + correct branch |\n| `steps.bumpVersion` | `boolean` | `true` | Update version in `package.json` + bump files |\n| `steps.changelog` | `boolean` | `true` | Generate changelog from `git log` since last tag |\n| `steps.commit` | `boolean` | `true` | Single commit for all bumped files |\n| `steps.tag` | `boolean` | `true` | Create git tag (plus any `extraTags`) |\n| `steps.push` | `boolean` | `true` | Push commit + tag(s) to `origin` |\n| `steps.githubRelease` | `boolean` | `true` | Create GitHub release via `gh` CLI |\n| `steps.npm` | `boolean` | `true` | Publish to npm (with interactive retry) |\n| `steps.homebrew` | `boolean` | `true` | Update Homebrew formula SHA + URL |\n| `git.releaseBranch` | `string` | `\"main\"` | Branch required for stable releases |\n| `git.tagPrefix` | `string` | `\"v\"` | Prefix prepended to the version |\n| `git.extraTags` | `string[]` | `[]` | Additional tags. Templates support `{tag}` (full, e.g. `v0.5.3`) and `{version}` (bare) |\n| `git.commitMessage` | `string` | `\"release: {tag}\"` | Commit message template |\n| `git.commitFlags` | `string` | `\"--no-verify\"` | Flags passed to `git commit` |\n| `git.pushFlags` | `string` | `\"--no-verify\"` | Flags passed to `git push` |\n| `npm.cwd` | `string` | Project root | Working directory for `npm publish` |\n| `npm.access` | `\"public\" \\| \"restricted\"` | `\"public\"` | npm publish access |\n| `homebrew.tapPath` | `string` | Auto (sibling `../homebrew-tap`) | Path to your tap repo |\n| `homebrew.formulaFile` | `string` | Auto-derived | Formula file, relative to `tapPath` |\n| `homebrew.repoSlug` | `string` | Auto-derived from `origin` | `owner/repo` for the tarball URL |\n| `homebrew.commitMessage` | `string` | `\"{formula}: update to {tag}\"` | Tap commit message template |\n\n## Recipes\n\n### Tauri / Cargo workspace\n\nshipx auto-detects `src-tauri/Cargo.toml` and adds it to `cargoWorkspaces`. Requires `cargo install cargo-edit`.\n\n```ts\n// shipx.config.ts\nexport default {\n  cargoWorkspaces: [\"src-tauri\"], // explicit; or omit for auto-detection\n  steps: { npm: false }, // Tauri apps usually don't publish to npm\n} satisfies ShipConfig;\n```\n\n### Monorepo with coupled versioning\n\nAll packages bump to the same version:\n\n```ts\nexport default {\n  packageJsonPaths: [\n    \"package.json\",\n    \"packages/core/package.json\",\n    \"packages/cli/package.json\",\n  ],\n} satisfies ShipConfig;\n```\n\nFor *independently*-versioned monorepos, use [changesets](https://github.com/changesets/changesets) instead — shipx isn't built for that.\n\n### Homebrew tap\n\nDrop your tap repo next to your project as a sibling (`../homebrew-tap`) and shipx finds it. Or configure it explicitly:\n\n```ts\nexport default {\n  homebrew: {\n    tapPath: \"../homebrew-tap\",\n    formulaFile: \"Formula/mytool.rb\",\n    repoSlug: \"lacymorrow/mytool\",\n  },\n} satisfies ShipConfig;\n```\n\nshipx downloads the tarball, computes SHA256, updates `url`/`sha256` in the formula, commits, and pushes from the tap.\n\n### Multi-project deploy\n\nGot a bunch of repos in `~/repo/`? Deploy them all at once:\n\n```bash\ncd ~/repo\nshipx --multi\n```\n\nshipx scans for subdirectories with a `package.json`, detects which have unreleased commits, and lets you pick which to release. The killer feature: **npm publishes are batched** — enter your OTP once and it's reused across all packages, so your 2FA code doesn't expire mid-deploy.\n\nThe flow:\n1. **Select projects** — sorted by change count, with dirty/private indicators\n2. **Pick versions** — individually, or apply the same bump type to all\n3. **Prepare** — each project gets its own bump → commit → tag → push → GitHub release\n4. **Batch publish** — all npm publishes happen back-to-back with a shared OTP\n5. **Homebrew** — formulas updated for non-beta releases\n\nCombine with `--beta` for beta batch releases: `shipx --multi --beta`.\n\n### Beta release\n\n```bash\nshipx --beta\n```\n\n- If current version is `1.0.0`, becomes `1.0.0-beta.0`\n- If current version is `1.0.0-beta.0`, becomes `1.0.0-beta.1`\n- Publishes to npm with `--tag beta` (your `latest` dist-tag is untouched)\n- Skips the branch check in preflight (release from any branch)\n- Skips Homebrew automatically\n\n## Comparison\n\n| | shipx | [np](https://github.com/sindresorhus/np) | [release-it](https://github.com/release-it/release-it) | [changesets](https://github.com/changesets/changesets) |\n|---|:-:|:-:|:-:|:-:|\n| Interactive UI | ✅ ([@clack](https://github.com/bombshell-dev/clack)) | ✅ (Listr) | partial | ❌ |\n| npm publish | ✅ | ✅ | ✅ | ✅ |\n| GitHub release | ✅ | ✅ | ✅ | via Action |\n| **Cargo workspaces** | ✅ | ❌ | via plugin | ❌ |\n| **Homebrew formula** | ✅ | ❌ | via plugin | ❌ |\n| Beta / pre-release | ✅ | ✅ | ✅ | ✅ |\n| **Multi-project batch deploy** | ✅ | ❌ | ❌ | ❌ |\n| Multi-package monorepo (coupled) | ✅ | ❌ | ✅ | ✅ |\n| Multi-package monorepo (independent) | ❌ | ❌ | ✅ | ✅ |\n| Changelog from PR labels | ❌ | ❌ | via plugin | ✅ |\n| Zero plugins required | ✅ | ✅ | ❌ | ✅ |\n\n**TL;DR** — Use **shipx** for single-package or coupled-version projects that ship to *multiple* registries (especially Cargo + Homebrew). Use **changesets** for independently-versioned monorepos. Use **np** if you want a smaller, npm-only tool.\n\n## FAQ\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eHow is shipx different from \u003ccode\u003enp\u003c/code\u003e?\u003c/strong\u003e\u003c/summary\u003e\n\n[np](https://github.com/sindresorhus/np) is excellent and the spiritual predecessor of shipx. The differences:\n\n- shipx uses [@clack/prompts](https://github.com/bombshell-dev/clack) instead of Listr — the UI feels more modern.\n- shipx bumps **Cargo workspaces** and updates **Homebrew formulas** out of the box. np is npm-only.\n- shipx is intentionally tiny (~600 lines of TS, two runtime deps). np is more battle-tested with more options.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eWhat if \u003ccode\u003enpm publish\u003c/code\u003e fails?\u003c/strong\u003e\u003c/summary\u003e\n\nshipx drops into an interactive retry loop with four options:\n\n1. **Enter OTP** — for 2FA accounts (validates that you typed 6 digits)\n2. **Log in to npm** — runs `npm login`, then retries\n3. **Retry** — just try again (good for transient errors)\n4. **Skip** — abandon `npm publish` and continue to remaining steps\n\nEverything before `npm publish` (the bump, commit, tag, push, GitHub release) is already done — you can always re-publish manually.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eCan I disable a step?\u003c/strong\u003e\u003c/summary\u003e\n\nYes. Every step in the pipeline has a `steps.\u003cname\u003e` boolean flag. Set it to `false` to skip:\n\n```ts\nexport default {\n  steps: { homebrew: false, githubRelease: false },\n} satisfies ShipConfig;\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eDoes shipx sign tags or commits?\u003c/strong\u003e\u003c/summary\u003e\n\nshipx delegates to your local `git` config. Set `commit.gpgsign=true` / `tag.gpgsign=true` and your tags will be signed. The default `commitFlags` / `pushFlags` of `--no-verify` is overrideable via config if you have hooks you actually want to run.\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eCan I extract the changelog before shipping?\u003c/strong\u003e\u003c/summary\u003e\n\nshipx generates a changelog from `git log \u003clast-tag\u003e..HEAD --pretty=format:\"- %s (%h)\"` and uses it as the GitHub release body. It's printed to the terminal during the release. If you need a `CHANGELOG.md` file, write your own bumpFile entry — or use [git-cliff](https://github.com/orhun/git-cliff) before invoking shipx.\n\u003c/details\u003e\n\n## Related\n\nOther projects by the author:\n\n- [album-art](https://github.com/lacymorrow/album-art) — Fetch an album or artist image URL.\n- [crossover](https://github.com/lacymorrow/crossover) — A crosshair overlay for any screen.\n- [cinematic](https://github.com/lacymorrow/cinematic) — Gorgeous desktop movie collections.\n\n## Acknowledgments\n\nshipx stands on the shoulders of:\n\n- [@clack/prompts](https://github.com/bombshell-dev/clack) by [Nate Moore](https://github.com/natemoo-re) — the prompt UI that makes shipx feel delightful.\n- [np](https://github.com/sindresorhus/np) by [Sindre Sorhus](https://github.com/sindresorhus) — the original prior art for a \"better `npm publish`\".\n- [picocolors](https://github.com/alexeyraspopov/picocolors) by [Alexey Raspopov](https://github.com/alexeyraspopov) — fast, tiny ANSI colors.\n- [Bun](https://bun.com) — the bundler that ships shipx itself.\n- [Charmbracelet VHS](https://github.com/charmbracelet/vhs) — used to record the demo above.\n\n## Contributing\n\nBug reports and pull requests welcome. See [CONTRIBUTING.md](.github/CONTRIBUTING.md) and the [security policy](.github/SECURITY.md). For a high-level architecture overview, see [CLAUDE.md](./CLAUDE.md).\n\n## License\n\n[MIT](./LICENSE) © [Lacy Morrow](https://lacymorrow.com)\n\n\u003cdiv align=\"center\"\u003e\n  \u003csub\u003eIf shipx saved you time, consider \u003ca href=\"https://github.com/sponsors/lacymorrow\"\u003esponsoring on GitHub\u003c/a\u003e, \u003ca href=\"https://patreon.com/lacymorrow\"\u003esupporting on Patreon\u003c/a\u003e, or \u003ca href=\"https://buymeacoffee.com/lm\"\u003ebuying a coffee\u003c/a\u003e.\u003c/sub\u003e\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flacymorrow%2Fshipx","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flacymorrow%2Fshipx","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flacymorrow%2Fshipx/lists"}