__ _______ ____ _ _____ __
\ \/ / ____| _ \| | / _ \ \ / /
\ /| _| | |_) | | | | | \ V /
/ \| |___| __/| |__| |_| || |
/_/\_\_____|_| |_____\___/ |_|
Interactive GitHub release pipeline CLI with multi-environment releases, meta-repo submodules, and branch sync via gh CLI.
xeploy automates the GitHub release lifecycle through a guided interactive CLI:
- Multi-environment releases — select staging, uat, sandbox, and/or production in one run; RC envs share one pre-release tag, final envs share one production tag.
-
Production without RC — create a final production release even when no
-rc.Ntag exists. -
Release branches and PRs — optionally create
release/X.Y.Zbranches and open PRs per environment instead of direct merges. - Meta-repo support — parallel releases across git submodules, then a serial umbrella release.
- Old release management — list past RC releases and promote or re-publish them.
Release notes are auto-generated by the GitHub CLI (when enabled), diffed against the previous release tag.
| Tool | Version |
|---|---|
| Node.js | ≥ 18 |
GitHub CLI (gh)
|
any recent |
| Git | any |
The gh CLI must be authenticated (gh auth login) before running.
npm install --save-dev xeploy
# or
yarn add -D xeploy
# or
pnpm add -D xeploy
# or
bun add -D xeploynpx xeploy
# or
bunx xeployOr add to your package.json scripts:
{
"scripts": {
"deploy": "xeploy"
}
}Then run:
npm run deploy
# or
bun run deploydeploy:
npx xeploy🚀 xeploy
? What would you like to do?
▶ Deploy new release
Deploy old release
Config
→ Deploy new release
? Select repos to bump (mono/meta only; multiselect, umbrella + subprojects all checked by default)
▶ Umbrella (this repo)
frontend
backend
? Select release environment (single choice; only envs with a branch in `environments`)
▶ staging release (RC)
production release (final)
? Select bump type (current latest: 1.2.3-rc.4)
▶ Release Candidate → 1.2.3-rc.5
Bug Fix → 1.2.4-rc.1
Minor → 1.3.0-rc.1
Major → 2.0.0-rc.1
Custom
→ Config
Edit .xeploy.json settings interactively
→ Deploy old release
Lists all previous RC tags
? What to do with 1.2.1-rc.2?
▶ Promote to production → 1.2.1 (final)
Re-publish as same RC → 1.2.1-rc.2
On first run, if .xeploy.json is missing, you are prompted to create one with auto-detected settings.
| Tag format | Meaning |
|---|---|
X.Y.Z-rc.N |
Staging / UAT pre-release |
X.Y.Z |
Final release (sandbox / production) |
No v prefix is used. RC tags sort lower than final tags for the same X.Y.Z.
When no tags exist yet, the first release defaults to 0.1.0-rc.1 (or 1.0.0-rc.1 for a major bump). Release notes cover the full commit history.
Create .xeploy.json in your project root (or let the CLI create it on first run):
{
"type": "default",
"subprojectsDir": null,
"tag_prefix": "v",
"generate_release_notes": true,
"create_production_release_branch": true,
"create_tag": true,
"create_pr": {
"staging": false,
"uat": false,
"sandbox": false,
"production": false
},
"environments": {
"develop": "develop",
"staging": "staging",
"uat": "uat",
"sandbox": "sandbox",
"production": "main"
}
}| Option | Type | Default | Description |
| ---------------------------------- | ----------- | -------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | ---------------------- |
| type | "default" | "mono" | "meta" | auto-detected | Repository layout type |
| subprojectsDir | string | null | auto-detected | Parent directory for mono/meta sub-projects |
| tag_prefix | string | auto-detected | Prefix for git/GitHub tags (e.g. "v" → v1.0.0); package.json stays unprefixed |
| generate_release_notes | boolean | true | Generate GitHub release notes vs previous tag |
| create_production_release_branch | boolean | true | Create release/X.Y.Z before production merge/PR |
| create_tag | boolean | true | Create git tag and GitHub release; when false, only bump package.json |
| create_pr | object | all false | Open PR instead of direct merge per environment |
| environments | object | branch-matched | Maps env names to git branch names (null if missing); only non-null release envs appear in "Select release environments" |
| subprojects | array | auto-detected | Per-subproject config when type is "mono" or "meta" — see below |
The version field of each subproject's own package.json is always what gets bumped — there's no separate versionFiles list to maintain.
| Type | Detection | Behaviour |
|---|---|---|
default |
single package.json
|
standard single-repo release |
mono |
multiple package.json files |
version-bumps all configured packages |
meta |
.gitmodules present |
parallel submodule releases, then umbrella |
When type is "mono" or "meta", each subproject can be disabled from release/bump entirely with "enabled": false — disabled subprojects are skipped and don't show up in the "Select repos to bump" step. "meta" submodules (separate repos) can additionally override create_tag, create_pr, and environments; "mono" subprojects share the umbrella's create_tag/create_pr/environments since they release together as one repo.
{
"type": "mono",
"subprojectsDir": "apps",
"subprojects": [
{ "repo": "frontend", "enabled": true },
{ "repo": "internal-tool", "enabled": false }
]
}{
"type": "meta",
"subprojectsDir": "apps",
"subprojects": [
{
"repo": "frontend",
"enabled": true,
"create_tag": true,
"create_pr": {
"staging": true,
"uat": true,
"sandbox": true,
"production": true
},
"environments": {
"develop": "develop",
"staging": "staging",
"uat": "uat",
"sandbox": "sandbox",
"production": "main"
}
}
]
}When you deploy a mono or meta release and the selected repos are on different latest versions, xeploy shows an extra step after the bump type is chosen:
The latest versions of selected repos are different:
Umbrella (this repo): 1.2.3
frontend: 1.2.2
backend: 1.2.2
How would you like to bump?
- Bump each version separately (Umbrella: 1.2.4, frontend: 1.2.3, backend: 1.2.3)
- Unify them to 1.2.4
- Bump each version separately — every repo is bumped from its own current version (so repos stay on independent version lines).
- Unify them to X.Y.Z — all repos are set to the same version, computed from the highest current version among selected repos.
For meta, the "latest version" is each repo's latest git tag. For mono, it's the version field of each package.json. This step is skipped when versions already match or when a custom version was entered.
All internals are exported for use in custom scripts:
import {
loadConfig,
createDefaultConfig,
flowNewRelease,
flowOldRelease,
executeReleasePlan,
getTags,
getLatestTag,
parseSemVer,
formatSemVer,
bumpVersion,
} from "xeploy";
const config = loadConfig(process.cwd()) ?? createDefaultConfig(process.cwd());
const tags = getTags();
await flowNewRelease(tags, config, process.cwd());git clone https://github.com/Hussain7Abbas/xeploy.git
cd xeploy
make install
make build| Command | Description |
|---|---|
make build |
Compile TypeScript → dist/
|
make dev |
Watch mode |
make clean |
Remove dist/
|
make typecheck |
Type-check without emitting |
make lint |
Biome lint |
make format |
Biome format |
make check |
Biome check + format |
make publish-dry |
Preview npm publish |
make publish |
Build + publish to npm |
make deploy |
Run xeploy CLI, then optional npm publish |
Pull requests are welcome. Please open an issue first to discuss significant changes.
MIT © Hussain Abbas