# ☁️ `@saptools/cf-sync`
**Map your SAP BTP Cloud Foundry topology and HANA app bindings into package-managed JSON files.**
Walk every region, org, space, and app you have access to, cache the topology, then optionally collect app-level HANA credentials in a background sync — no more juggling `cf target` or hand-running `cf env`.
[](https://www.npmjs.com/package/@saptools/cf-sync)
[](./LICENSE)
[](https://nodejs.org)
[](https://packagephobia.com/result?p=@saptools/cf-sync)
[](https://www.typescriptlang.org)
[Install](#-install) • [Quick Start](#-quick-start) • [CLI](#-cli) • [FAQ](#-faq)
---
## ✨ Features
- 🌍 **Full-landscape sync** — logs into CF once, walks **region → org → space → app** across every region you can reach
- 🟢 **App runtime metadata** — snapshots include requested state, instance counts, and routes from `cf apps`
- ⚡ **Partial + streaming reads** — `read` / `regions` / `region` commands return whatever is already known, even while a long sync is in progress
- 🧭 **Region org refresh** — `orgs` refreshes one region's org list without walking spaces or apps
- 🗄️ **Background DB binding sync** — `db-sync` can collect `VCAP_SERVICES.hana` credentials for every cached app or one app selector in the background
- 🧠 **Smart fallbacks** — runtime state first, last stable snapshot next, on-demand fetch as a last resort
- 🧩 **CLI & typed API** — every command has a zero-config Node.js equivalent with full TypeScript definitions
- 📦 **Drop-in for other saptools** — the output file is the shared source of truth for packages like [`@saptools/cf-xsuaa`](https://www.npmjs.com/package/@saptools/cf-xsuaa)
- 🪶 **Small + boring** — two deps (`commander`, `ora`), one-shot background workers only when requested, no resident daemon
---
## 📦 Install
```bash
# Global CLI
npm install -g @saptools/cf-sync
# Or as a dependency
npm install @saptools/cf-sync
# pnpm add @saptools/cf-sync
# yarn add @saptools/cf-sync
```
> [!NOTE]
> Requires **Node.js ≥ 20** and the official **`cf` CLI** on `PATH` (v8 recommended).
---
## 🚀 Quick Start
```bash
# 1. Export your SAP SSO credentials (only used during sync)
export SAP_EMAIL="you@company.com"
export SAP_PASSWORD="your-sap-password"
# 2. Sync every accessible region in parallel
cf-sync sync --verbose
# 3. Read the topology snapshot back from anywhere — CLI, script, or Node process
cf-sync read | jq '.regions[] | {key, accessible}'
# 4. Optionally collect HANA DB bindings for every cached app in the background
cf-sync db-sync
cf-sync db-read | jq '.metadata'
```
After the first topology sync, `~/.saptools/cf-structure.json` is ready for the rest of your tooling. If you run `db-sync`, the HANA binding snapshot is stored separately.
---
## 🧰 CLI
### 🔄 `cf-sync sync`
Run a live sync and write a topology snapshot. A full sync replaces the snapshot; `--only` refreshes the listed regions and merges them into the existing snapshot without removing other cached regions.
```bash
cf-sync sync
cf-sync sync --verbose
cf-sync sync --no-interactive
cf-sync sync --only ap10,ap11,eu10
```
| Flag | Description |
| --- | --- |
| `--verbose` | Print progress lines |
| `--no-interactive` | Disable the spinner (use in CI) |
| `--only ` | Sync only the listed region keys |
### 📖 `cf-sync read`
Print the best-available full structure as JSON — **always succeeds as long as something has been synced before.**
- Returns runtime state while a sync is in progress
- Falls back to the last stable snapshot otherwise
```bash
cf-sync read
```
### 🗺️ `cf-sync regions`
Print the list of regions (key + label + endpoint + accessibility).
- Returns the default SAP CF catalog while a first sync is still running
- Returns only synced regions with orgs once a snapshot exists
```bash
cf-sync regions
```
### 🎯 `cf-sync region `
Print one region as JSON, fetching it on demand if it's missing.
```bash
cf-sync region eu10
cf-sync region eu10 --no-refresh
```
| Flag | Description |
| --- | --- |
| `--no-refresh` | Read cached data only, never call CF |
> [!TIP]
> `cf-sync region ` is the fastest way to answer *"what's in just this region right now?"* without walking everything.
### 🧭 `cf-sync orgs `
Refresh only the Cloud Foundry org names for one region and merge that list back into the shared topology snapshot.
- Updates only the requested region
- Preserves cached spaces/apps for orgs that still exist
- Removes stale org entries from that region only
- Adds newly discovered orgs with empty `spaces` until a targeted org, space, or full sync fills them in
- Uses an isolated `CF_HOME`, so it does not clobber your interactive CF CLI target
```bash
cf-sync orgs ap10
cf-sync orgs eu10 --verbose
```
### 🧭 `cf-sync org `
Refresh exactly one Cloud Foundry org and merge every refreshed space/app in that org back into the shared topology snapshot.
- Updates only the requested `region/org`
- Preserves sibling orgs and regions already present in the snapshot
- Fails without changing the stable snapshot when the requested org cannot be targeted
- Uses an isolated `CF_HOME`, so it does not clobber your interactive CF CLI target
```bash
cf-sync org ap10 my-org
cf-sync org eu10 my-org --verbose
```
### 🔁 `cf-sync space `
Refresh exactly one Cloud Foundry space and merge the latest app metadata back into the shared topology snapshot.
- Updates only the requested `region/org/space`
- Preserves sibling orgs and spaces already present in the snapshot
- Uses an isolated `CF_HOME`, so it does not clobber your interactive CF CLI target
- Can run while a full `cf-sync sync` is active; the merge is serialized through the same runtime-state lock
```bash
cf-sync space ap10 my-org dev
cf-sync space eu10 my-org app --verbose
```
### 🗄️ `cf-sync db-sync [selector]`
Start a detached background worker that collects `VCAP_SERVICES.hana` credentials.
- with no selector: sync every app in the cached topology snapshot
- with ``: sync one uniquely named app from the cached topology snapshot
- with `region/org/space/app`: sync one explicit app even if no topology snapshot exists yet
```bash
cf-sync db-sync
cf-sync db-sync orders-srv
cf-sync db-sync ap10/my-org/dev/orders-srv
```
> [!IMPORTANT]
> `cf-sync db-sync` persists HANA credentials to local disk under `~/.saptools/`. Treat that file like a secret.
### 📚 `cf-sync db-read [selector]`
Read the best available HANA binding snapshot as JSON.
- `cf-sync db-read` returns the full runtime/stable DB snapshot view
- `cf-sync db-read ` returns one app binding view
```bash
cf-sync db-read
cf-sync db-read orders-srv
cf-sync db-read ap10/my-org/dev/orders-srv
```
---
## 📁 Output Files
All state lives under your home directory:
```text
~/.saptools/cf-structure.json # last successful full sync (stable)
~/.saptools/cf-sync-state.json # active runtime state, partial reads, sync metadata
~/.saptools/cf-sync-history.jsonl # append-only timeline of sync milestones for debugging
~/.saptools/cf-db-bindings.json # last successful HANA binding snapshot (contains credentials)
~/.saptools/cf-db-sync-state.json # active DB binding runtime state
~/.saptools/cf-db-sync-history.jsonl # append-only DB sync milestones
```
`cf-sync-history.jsonl` is newline-delimited JSON. Each entry records a timestamped milestone such as lock acquisition, region traversal, runtime merges, recoveries, and final completion/failure so you can reconstruct where a sync got stuck.
`cf-db-bindings.json` is also newline-free JSON, but unlike the topology files it contains HANA credentials. Do not commit it, attach it to tickets, or paste it into logs.
🔬 Shape of cf-structure.json
```jsonc
{
"syncedAt": "2026-04-18T00:00:00Z",
"regions": [
{
"key": "ap10",
"label": "Singapore",
"apiEndpoint": "https://api.cf.ap10.hana.ondemand.com",
"accessible": true,
"orgs": [
{
"name": "my-org",
"spaces": [
{
"name": "dev",
"apps": [{ "name": "my-srv" }]
}
]
}
]
}
]
}
```
> [!IMPORTANT]
> Prefer the CLI read commands or the exported APIs over parsing these files directly — the on-disk format is an implementation detail.
---
## ❓ FAQ
Do I have to re-enter my SAP credentials for every read?
No. `SAP_EMAIL` / `SAP_PASSWORD` are only used during `sync`, `db-sync`, and `cf-sync region ` when the region is missing locally. Pure read commands work offline.
How often should I sync?
As often as your CF topology changes in a way you care about — usually daily or weekly is plenty. `cf-sync sync --only ap10,eu10` keeps hot regions fresh without walking everything.
Is the output file safe to commit?
`cf-structure.json` does not contain secrets, but it **does** list every org, space, and app you can reach — so it leaks your landscape's structure. Keep it out of public repos.
`cf-db-bindings.json` is more sensitive: it contains HANA credentials by design. Treat it like a secret and never commit it.
How does this compare to cf orgs / cf spaces?
Those commands only act on the **currently targeted** region/org. `cf-sync` walks every region in one pass and gives you a unified, cached view — which is what every other saptools package consumes.
---
## 🛠️ Development
From the monorepo root:
```bash
pnpm install
pnpm --filter @saptools/cf-sync build
pnpm --filter @saptools/cf-sync typecheck
pnpm --filter @saptools/cf-sync test:unit
pnpm --filter @saptools/cf-sync test:e2e
```
The e2e suite hits live SAP BTP CF. Set `CF_SYNC_E2E_ONLY=ap10,eu10` (plus `SAP_EMAIL` / `SAP_PASSWORD`) to restrict the regions it walks in CI.
---
## 🌐 Related
- 🔐 [`@saptools/cf-xsuaa`](https://www.npmjs.com/package/@saptools/cf-xsuaa) — fetch XSUAA credentials and cached OAuth2 tokens for any CF app
- 🗂️ [saptools monorepo](https://github.com/dongitran/saptools) — the full toolbox
---
## 👨💻 Author
**dongtran** ✨
## 📄 License
MIT
---
Made with ❤️ to make your work life easier!