simplemdg-dev-cli 2.4.5 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -0
- package/USER_GUIDE.md +57 -0
- package/dist/commands/cache.command.d.ts +2 -0
- package/dist/commands/cache.command.js +129 -0
- package/dist/commands/cache.command.js.map +1 -0
- package/dist/commands/cf.command.js +201 -122
- package/dist/commands/cf.command.js.map +1 -1
- package/dist/commands/gitlab.command.js +33 -23
- package/dist/commands/gitlab.command.js.map +1 -1
- package/dist/core/cache/smart-cache-events.d.ts +3 -0
- package/dist/core/cache/smart-cache-events.js +20 -0
- package/dist/core/cache/smart-cache-events.js.map +1 -0
- package/dist/core/cache/smart-cache-manager.d.ts +20 -0
- package/dist/core/cache/smart-cache-manager.js +148 -0
- package/dist/core/cache/smart-cache-manager.js.map +1 -0
- package/dist/core/cache/smart-cache-store.d.ts +8 -0
- package/dist/core/cache/smart-cache-store.js +74 -0
- package/dist/core/cache/smart-cache-store.js.map +1 -0
- package/dist/core/cache/smart-cache.d.ts +18 -0
- package/dist/core/cache/smart-cache.js +117 -0
- package/dist/core/cache/smart-cache.js.map +1 -0
- package/dist/core/cache/smart-cache.types.d.ts +62 -0
- package/dist/core/cache/smart-cache.types.js +17 -0
- package/dist/core/cache/smart-cache.types.js.map +1 -0
- package/dist/core/cf/cf-target-cache.d.ts +7 -0
- package/dist/core/cf/cf-target-cache.js +58 -0
- package/dist/core/cf/cf-target-cache.js.map +1 -0
- package/dist/core/cf/cf-target.types.d.ts +11 -0
- package/dist/core/cf/cf-target.types.js +11 -0
- package/dist/core/cf/cf-target.types.js.map +1 -0
- package/dist/core/db/db-studio-client.d.ts +1 -1
- package/dist/core/db/db-studio-client.js +173 -44
- package/dist/core/db/db-studio-client.js.map +1 -1
- package/dist/core/db/db-studio-server.js +125 -0
- package/dist/core/db/db-studio-server.js.map +1 -1
- package/dist/core/db/db-studio-styles.d.ts +1 -1
- package/dist/core/db/db-studio-styles.js +36 -0
- package/dist/core/db/db-studio-styles.js.map +1 -1
- package/dist/core/db/db-types.d.ts +54 -0
- package/dist/core/db/studio/sql-formatter.d.ts +25 -0
- package/dist/core/db/studio/sql-formatter.js +139 -0
- package/dist/core/db/studio/sql-formatter.js.map +1 -0
- package/dist/core/db/studio/studio-settings.d.ts +4 -0
- package/dist/core/db/studio/studio-settings.js +39 -0
- package/dist/core/db/studio/studio-settings.js.map +1 -0
- package/dist/core/db/studio/workspace-cache.d.ts +3 -0
- package/dist/core/db/studio/workspace-cache.js +51 -0
- package/dist/core/db/studio/workspace-cache.js.map +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/commands/cache.command.ts +159 -0
- package/src/commands/cf.command.ts +232 -129
- package/src/commands/gitlab.command.ts +37 -21
- package/src/core/cache/smart-cache-events.ts +20 -0
- package/src/core/cache/smart-cache-manager.ts +169 -0
- package/src/core/cache/smart-cache-store.ts +83 -0
- package/src/core/cache/smart-cache.ts +97 -0
- package/src/core/cache/smart-cache.types.ts +79 -0
- package/src/core/cf/cf-target-cache.ts +61 -0
- package/src/core/cf/cf-target.types.ts +17 -0
- package/src/core/db/db-studio-client.ts +173 -44
- package/src/core/db/db-studio-server.ts +109 -1
- package/src/core/db/db-studio-styles.ts +36 -0
- package/src/core/db/db-types.ts +61 -0
- package/src/core/db/studio/sql-formatter.ts +139 -0
- package/src/core/db/studio/studio-settings.ts +36 -0
- package/src/core/db/studio/workspace-cache.ts +51 -0
- package/src/index.ts +3 -1
package/README.md
CHANGED
|
@@ -13,12 +13,35 @@ smdg -V
|
|
|
13
13
|
|
|
14
14
|
Some commands rely on external CLIs: `cf` (Cloud Foundry), `cds` (SAP CAP), and `git`. The CLI checks for these **before** running an interactive flow — so you are not asked for credentials only to fail at the end. If a tool is missing, it offers to install it via a detected package manager (`choco`/`brew` for `cf`, `winget`/`choco`/`scoop`/`brew`/`apt-get` for `git`, `npm -g` for `@sap/cds-dk`), or prints the official install link when no manager is available. After installing a tool, open a new terminal so PATH refreshes.
|
|
15
15
|
|
|
16
|
+
## Smart cache (instant, stale-while-revalidate)
|
|
17
|
+
|
|
18
|
+
Slow BTP/CF/GitLab lookups are cached under `~/.simplemdg/cache/` and served **cache-first**: the CLI shows the last known result immediately, then refreshes in the background and updates the cache for next time. These resources rarely change second-to-second, so this makes the CLI feel instant.
|
|
19
|
+
|
|
20
|
+
- `smdg cf apps` prints cached apps right away (e.g. *"Using cached apps for br10 / single-npi-laidon / app from 4 minutes ago."*) and refreshes in the background; `--refresh` forces a live fetch.
|
|
21
|
+
- `smdg gitlab groups` / `smdg gitlab projects` work the same way.
|
|
22
|
+
- `smdg cf org` is a **CF target switcher**: it lists **★ favorites** first, then **◷ recent** targets, then all cached targets — searchable, switchable, and instant (cached-first, no login needed to browse). Switching auto re-logs-in from saved credentials (no repeated password prompts), targets the org/space, and records the target as recent. Choose *Refresh all regions* to rescan. `--list`, `--switch`, `--org`, `--space`, `--api`, `--refresh` still work.
|
|
23
|
+
- A **failed background refresh never wipes the cache** — you keep working from the last good data with a warning.
|
|
24
|
+
- Background refreshes are **deduplicated**: concurrent requests for the same key share one network call.
|
|
25
|
+
- Default TTLs: CF apps 10m, CF env 5m, CF orgs/spaces 6h, CF regions 7d, GitLab groups 6h, GitLab projects 30m. Secrets are never written to plain cache files.
|
|
26
|
+
|
|
27
|
+
Manage the cache with:
|
|
28
|
+
|
|
29
|
+
```powershell
|
|
30
|
+
smdg cache # interactive: status / clear / refresh / open folder
|
|
31
|
+
smdg cache status # counts + "last updated" per namespace
|
|
32
|
+
smdg cache clear cf # scopes: all | cf | gitlab | db | target | <namespace>
|
|
33
|
+
smdg cache refresh cf # invalidate so the next command fetches fresh
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
DB Studio streams background-refresh events over `GET /api/events` (SSE) so its lists can update silently.
|
|
37
|
+
|
|
16
38
|
## Main commands
|
|
17
39
|
|
|
18
40
|
```powershell
|
|
19
41
|
smdg i
|
|
20
42
|
smdg cf login
|
|
21
43
|
smdg cf apps
|
|
44
|
+
smdg cache
|
|
22
45
|
smdg cf bind
|
|
23
46
|
smdg cf env
|
|
24
47
|
smdg cf logs
|
|
@@ -70,6 +93,16 @@ Studio starts a local web server bound to `127.0.0.1` only (auto-selects a free
|
|
|
70
93
|
- **read-only** mode blocks all writes/DDL; dangerous SQL (DROP/TRUNCATE/ALTER/DELETE-or-UPDATE-without-WHERE) asks for confirmation
|
|
71
94
|
- clear loading states (skeletons, spinners, status bar) for every async action
|
|
72
95
|
|
|
96
|
+
IDE-grade workflow:
|
|
97
|
+
|
|
98
|
+
- **workspace tabs** that drag-to-reorder, pin, and restore on next launch (auto-saved to `~/.simplemdg/db-studio-workspace.json`) — unsaved SQL survives a refresh/restart; right-click a tab for Close / Close Others / Close to Right / Pin / Rename / Duplicate
|
|
99
|
+
- **search highlight** on every list (connections, object tree, saved queries) with debounce, Enter-to-search, Esc-to-clear, and "No results found"
|
|
100
|
+
- **quick-filter SQL preview** in the data grid: **Show SQL** (formatted, dialect-correct, with WHERE/ORDER BY/LIMIT/OFFSET), **Copy**, **Open in SQL Console**
|
|
101
|
+
- **SQL editor**: line numbers, **Run dropdown** (Run Selected / Current Statement / All / Explain), `Ctrl+Enter` runs selected/current, `F5` runs all, server-side **Format**, `Ctrl+S` saves (updates linked file or Save As)
|
|
102
|
+
- **grid editing**: `Ctrl+Z`/`Ctrl+Y` undo/redo of pending edits, `Delete` marks a row, `Enter`/`Tab` confirm-and-move, a sticky **change-summary bar** with **Show Changes** (per-cell old→new review)
|
|
103
|
+
- **command palette** (`Ctrl+Shift+P`), keyboard-shortcut help, and a **Settings** panel (restore-workspace, default row limit/schema, read-only default, query timeout, auto-save delay…) stored in `~/.simplemdg/db-studio-settings.json`
|
|
104
|
+
- **breadcrumbs**, **cell value viewer** (pretty-prints JSON), and **copy helpers** (name / full name / SELECT / INSERT / UPDATE templates)
|
|
105
|
+
|
|
73
106
|
### Commands
|
|
74
107
|
|
|
75
108
|
```powershell
|
package/USER_GUIDE.md
CHANGED
|
@@ -1,5 +1,48 @@
|
|
|
1
1
|
# SimpleMDG CLI User Guide
|
|
2
2
|
|
|
3
|
+
## Smart cache
|
|
4
|
+
|
|
5
|
+
The CLI caches slow BTP/Cloud Foundry/GitLab lookups under `~/.simplemdg/cache/` and serves them **cache-first, then refreshes in the background** (stale-while-revalidate). You see the last known result instantly; fresh data replaces it quietly when the refresh finishes.
|
|
6
|
+
|
|
7
|
+
What it means in practice:
|
|
8
|
+
|
|
9
|
+
- `smdg cf apps`, `smdg gitlab groups`, `smdg gitlab projects` show cached results immediately with a note like *"Using cached apps … from 4 minutes ago. Refreshing in background…"*, then update the cache.
|
|
10
|
+
- Add `--refresh` to any of those to force a live fetch and skip the cache.
|
|
11
|
+
- If a refresh fails (e.g. expired CF session, no network), the **old cache is kept** and you get a warning — you can keep working.
|
|
12
|
+
- Concurrent requests for the same data share a single network call (deduplication).
|
|
13
|
+
|
|
14
|
+
Manage the cache:
|
|
15
|
+
|
|
16
|
+
```powershell
|
|
17
|
+
smdg cache # interactive menu
|
|
18
|
+
smdg cache status # what is cached and how old it is
|
|
19
|
+
smdg cache clear cf # clear a scope: all | cf | gitlab | db | target
|
|
20
|
+
smdg cache refresh cf # invalidate so the next command refetches
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Default freshness windows (configurable later): CF apps 10 min, CF env 5 min, CF orgs/spaces 6 h, CF regions 7 days, GitLab groups 6 h, GitLab projects 30 min. **Secrets are never stored in plain cache files** — passwords/tokens/DB credentials stay in the existing encrypted stores.
|
|
24
|
+
|
|
25
|
+
### CF target switcher (`smdg cf org`)
|
|
26
|
+
|
|
27
|
+
When you work across many regions/orgs, `smdg cf org` is a fast target switcher:
|
|
28
|
+
|
|
29
|
+
```txt
|
|
30
|
+
CF Target Switcher
|
|
31
|
+
Favorites
|
|
32
|
+
★ eu20 / arthrex-qas-simplemdg / app
|
|
33
|
+
Recent
|
|
34
|
+
◷ br10 / single-npi-laidon / app
|
|
35
|
+
◷ us21 / par-pacific-qas-simplemdg / app
|
|
36
|
+
All Targets (243)
|
|
37
|
+
...
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
- Browsing is **instant** — it reads cached targets and doesn't need a CF login.
|
|
41
|
+
- Type to search; favorites (★) appear first, recent (◷) second, then all cached targets.
|
|
42
|
+
- Switching sets the API endpoint, **auto re-logs-in from your saved credentials** (no repeated password prompts), targets the org and space, and saves the target to *recent*. You're offered to mark it a **favorite**.
|
|
43
|
+
- *Refresh all regions* rescans live; *Manage favorites* removes saved favorites.
|
|
44
|
+
- Favorites/recent are stored under `~/.simplemdg/cache/` and show up in `smdg cache status`; clear them with `smdg cache clear target`.
|
|
45
|
+
|
|
3
46
|
## GitLab
|
|
4
47
|
|
|
5
48
|
Login once:
|
|
@@ -53,6 +96,20 @@ The Studio opens on a **Welcome page**. The left side has three collapsible sect
|
|
|
53
96
|
|
|
54
97
|
The bottom status bar shows connection state, last query duration, row count, and pending-change count.
|
|
55
98
|
|
|
99
|
+
### Productivity features
|
|
100
|
+
|
|
101
|
+
- **Workspace restore** — your open tabs (including unsaved "New Query" content) are auto-saved and restored next time you open the Studio. Toggle this in **Settings**.
|
|
102
|
+
- **Tabs** — drag to reorder, right-click for Close / Close Others / Close to Right / Pin / Rename / Duplicate. `Ctrl+Tab` / `Ctrl+Shift+Tab` switch tabs, `Ctrl+W` closes the active tab.
|
|
103
|
+
- **Command palette** — press `Ctrl+Shift+P` to run any action (new SQL, run, save, import, toggle read-only, settings, …).
|
|
104
|
+
- **SQL editor** — `Ctrl+Enter` runs the selection or the statement at the cursor, `F5` runs the whole tab, the Run ▾ menu offers Run Selected / Current / All / Explain, and **Format** pretty-prints. `Ctrl+S` saves to a `.sql` file (or Save As).
|
|
105
|
+
- **Quick-filter SQL** — in a Data tab, type a `WHERE` and click **Show SQL** to see/copy the exact generated query or open it in a SQL console.
|
|
106
|
+
- **Grid editing** — `Ctrl+Z`/`Ctrl+Y` undo/redo pending edits, `Delete` marks the selected row, `Enter`/`Tab` confirm a cell and move. A change-summary bar shows counts and **Show Changes** lists every old→new value before you save.
|
|
107
|
+
- **Search** — every list highlights matches; press Enter to search, Esc to clear.
|
|
108
|
+
- **Cell viewer** — double-click a result cell to open a viewer that pretty-prints JSON.
|
|
109
|
+
- Press `Ctrl+Shift+P` → **Show Keyboard Shortcuts** for the full list.
|
|
110
|
+
|
|
111
|
+
Settings, the workspace, saved queries, and history live under `~/.simplemdg/` (`db-studio-settings.json`, `db-studio-workspace.json`, `db-queries/`, `db-query-history.json`).
|
|
112
|
+
|
|
56
113
|
### Read-only and dangerous SQL
|
|
57
114
|
|
|
58
115
|
Toggle **Read-only** in the top bar to block INSERT/UPDATE/DELETE/DROP/TRUNCATE/ALTER/CREATE/GRANT/REVOKE. Even in read/write mode, dangerous statements (DROP, TRUNCATE, ALTER, DELETE/UPDATE without WHERE) require confirmation. A "Production-like" badge appears for prod-looking orgs/apps.
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.registerCacheCommands = registerCacheCommands;
|
|
7
|
+
const execa_1 = require("execa");
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const prompts_1 = require("../core/prompts");
|
|
10
|
+
const smart_cache_1 = require("../core/cache/smart-cache");
|
|
11
|
+
function resolveScopeNamespaces(scope) {
|
|
12
|
+
if (!scope) {
|
|
13
|
+
return smart_cache_1.CACHE_SCOPES.all;
|
|
14
|
+
}
|
|
15
|
+
const normalized = scope.trim().toLowerCase();
|
|
16
|
+
if (smart_cache_1.CACHE_SCOPES[normalized]) {
|
|
17
|
+
return smart_cache_1.CACHE_SCOPES[normalized];
|
|
18
|
+
}
|
|
19
|
+
if (smart_cache_1.CACHE_NAMESPACES[normalized]) {
|
|
20
|
+
return [normalized];
|
|
21
|
+
}
|
|
22
|
+
throw new Error(`Unknown cache scope: ${scope}. Use one of: ${Object.keys(smart_cache_1.CACHE_SCOPES).join(", ")} or a namespace.`);
|
|
23
|
+
}
|
|
24
|
+
async function printCacheStatus() {
|
|
25
|
+
console.log(chalk_1.default.bold("SimpleMDG Cache"));
|
|
26
|
+
console.log("");
|
|
27
|
+
const namespaces = Object.keys(smart_cache_1.CACHE_NAMESPACES);
|
|
28
|
+
const stats = await Promise.all(namespaces.map((namespace) => (0, smart_cache_1.statNamespace)(namespace)));
|
|
29
|
+
const labelWidth = Math.max(...namespaces.map((namespace) => smart_cache_1.CACHE_NAMESPACES[namespace].length));
|
|
30
|
+
let total = 0;
|
|
31
|
+
for (const stat of stats) {
|
|
32
|
+
const label = smart_cache_1.CACHE_NAMESPACES[stat.namespace].padEnd(labelWidth);
|
|
33
|
+
total += stat.count;
|
|
34
|
+
if (!stat.exists || stat.count === 0) {
|
|
35
|
+
console.log(`${label} ${chalk_1.default.gray("empty")}`);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const countText = `${stat.count} item${stat.count === 1 ? "" : "s"}`.padEnd(13);
|
|
39
|
+
console.log(`${label} ${chalk_1.default.cyan(countText)} ${chalk_1.default.gray(`last updated ${(0, smart_cache_1.formatRelativeTime)(stat.lastUpdatedAt)}`)}`);
|
|
40
|
+
}
|
|
41
|
+
console.log("");
|
|
42
|
+
console.log(chalk_1.default.gray(`${total} cached item(s) total · ${(0, smart_cache_1.getCacheDirectory)()}`));
|
|
43
|
+
}
|
|
44
|
+
async function runClear(scope) {
|
|
45
|
+
const namespaces = resolveScopeNamespaces(scope);
|
|
46
|
+
for (const namespace of namespaces) {
|
|
47
|
+
await (0, smart_cache_1.clearNamespace)(namespace);
|
|
48
|
+
}
|
|
49
|
+
console.log(chalk_1.default.green(`Cleared cache: ${scope ?? "all"} (${namespaces.length} namespace(s)).`));
|
|
50
|
+
}
|
|
51
|
+
async function runRefresh(scope) {
|
|
52
|
+
// "Refresh" invalidates the cache so the next command fetches live data and
|
|
53
|
+
// repopulates under stale-while-revalidate. This avoids needing a live CF/
|
|
54
|
+
// GitLab session inside the cache command itself.
|
|
55
|
+
const namespaces = resolveScopeNamespaces(scope);
|
|
56
|
+
for (const namespace of namespaces) {
|
|
57
|
+
await (0, smart_cache_1.clearNamespace)(namespace);
|
|
58
|
+
}
|
|
59
|
+
console.log(chalk_1.default.green(`Marked for refresh: ${scope ?? "all"}. The next ${scope ?? ""} command will fetch fresh data.`));
|
|
60
|
+
}
|
|
61
|
+
async function openCacheFolder() {
|
|
62
|
+
const directory = (0, smart_cache_1.getCacheDirectory)();
|
|
63
|
+
const command = process.platform === "win32" ? "cmd" : process.platform === "darwin" ? "open" : "xdg-open";
|
|
64
|
+
const args = process.platform === "win32" ? ["/c", "start", "", directory] : [directory];
|
|
65
|
+
await (0, execa_1.execa)(command, args, { reject: false, detached: true, stdio: "ignore" }).catch(() => undefined);
|
|
66
|
+
console.log(chalk_1.default.gray(directory));
|
|
67
|
+
}
|
|
68
|
+
async function runInteractive() {
|
|
69
|
+
for (;;) {
|
|
70
|
+
const action = await (0, prompts_1.searchableSelectChoice)({
|
|
71
|
+
message: "SimpleMDG cache",
|
|
72
|
+
choices: [
|
|
73
|
+
{ title: "View cache status", value: "status" },
|
|
74
|
+
{ title: "Clear a cache scope", value: "clear" },
|
|
75
|
+
{ title: "Refresh a cache scope", value: "refresh" },
|
|
76
|
+
{ title: "Open cache folder", value: "open" },
|
|
77
|
+
{ title: "Exit", value: "exit" },
|
|
78
|
+
],
|
|
79
|
+
allowCustomValue: false,
|
|
80
|
+
});
|
|
81
|
+
if (action === "exit") {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (action === "status") {
|
|
85
|
+
await printCacheStatus();
|
|
86
|
+
console.log("");
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (action === "open") {
|
|
90
|
+
await openCacheFolder();
|
|
91
|
+
console.log("");
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const scope = await (0, prompts_1.searchableSelectChoice)({
|
|
95
|
+
message: `Select scope to ${action}`,
|
|
96
|
+
choices: [
|
|
97
|
+
{ title: "All caches", value: "all" },
|
|
98
|
+
{ title: "Cloud Foundry (cf)", value: "cf" },
|
|
99
|
+
{ title: "GitLab", value: "gitlab" },
|
|
100
|
+
{ title: "Database", value: "db" },
|
|
101
|
+
{ title: "CF targets/favorites/recent", value: "target" },
|
|
102
|
+
],
|
|
103
|
+
allowCustomValue: false,
|
|
104
|
+
});
|
|
105
|
+
if (action === "clear") {
|
|
106
|
+
await runClear(scope);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
await runRefresh(scope);
|
|
110
|
+
}
|
|
111
|
+
console.log("");
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function registerCacheCommands(program) {
|
|
115
|
+
const cache = program
|
|
116
|
+
.command("cache")
|
|
117
|
+
.description("Inspect and manage the SimpleMDG smart cache (cf/gitlab/db)")
|
|
118
|
+
.action(runInteractive);
|
|
119
|
+
cache.command("status").description("Show cache status for all namespaces").action(printCacheStatus);
|
|
120
|
+
cache
|
|
121
|
+
.command("clear [scope]")
|
|
122
|
+
.description("Clear cache. Scope: all | cf | gitlab | db | target | <namespace>")
|
|
123
|
+
.action((scope) => runClear(scope));
|
|
124
|
+
cache
|
|
125
|
+
.command("refresh [scope]")
|
|
126
|
+
.description("Invalidate cache so the next command fetches fresh. Scope: all | cf | gitlab | db | target")
|
|
127
|
+
.action((scope) => runRefresh(scope));
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=cache.command.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.command.js","sourceRoot":"","sources":["../../src/commands/cache.command.ts"],"names":[],"mappings":"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"}
|
|
@@ -20,6 +20,9 @@ const process_1 = require("../core/process");
|
|
|
20
20
|
const repository_1 = require("../core/repository");
|
|
21
21
|
const prompts_2 = require("../core/prompts");
|
|
22
22
|
const tooling_1 = require("../core/tooling");
|
|
23
|
+
const smart_cache_1 = require("../core/cache/smart-cache");
|
|
24
|
+
const cf_target_cache_1 = require("../core/cf/cf-target-cache");
|
|
25
|
+
const cf_target_types_1 = require("../core/cf/cf-target.types");
|
|
23
26
|
function validateRequired(value) {
|
|
24
27
|
return value.trim() ? true : "Value is required";
|
|
25
28
|
}
|
|
@@ -907,152 +910,212 @@ async function getCloudFoundryOrganizationsAcrossRegions(options) {
|
|
|
907
910
|
}
|
|
908
911
|
return fallbackEntries;
|
|
909
912
|
}
|
|
913
|
+
function orgEntryToTarget(entry) {
|
|
914
|
+
return { region: entry.region, apiEndpoint: entry.apiEndpoint, org: entry.org, space: "", lastRefreshedAt: entry.updatedAt };
|
|
915
|
+
}
|
|
916
|
+
function dedupeTargets(targets) {
|
|
917
|
+
const seen = new Set();
|
|
918
|
+
const result = [];
|
|
919
|
+
for (const target of targets) {
|
|
920
|
+
const key = (0, cf_target_types_1.cfTargetKey)(target);
|
|
921
|
+
if (seen.has(key))
|
|
922
|
+
continue;
|
|
923
|
+
seen.add(key);
|
|
924
|
+
result.push(target);
|
|
925
|
+
}
|
|
926
|
+
return result;
|
|
927
|
+
}
|
|
928
|
+
async function switchToCfTarget(target, options) {
|
|
929
|
+
const apiEndpoint = target.apiEndpoint;
|
|
930
|
+
if (!apiEndpoint) {
|
|
931
|
+
throw new Error("Cannot determine CF API endpoint for the selected target.");
|
|
932
|
+
}
|
|
933
|
+
const region = target.region || (0, cf_1.inferCloudFoundryRegionFromApiEndpoint)(apiEndpoint);
|
|
934
|
+
const authenticatedProfile = await ensureCloudFoundryAuthenticatedForApiEndpoint({
|
|
935
|
+
apiEndpoint,
|
|
936
|
+
preferredOrg: target.org,
|
|
937
|
+
preferredSpace: options.space ?? target.space,
|
|
938
|
+
reason: "switch-target",
|
|
939
|
+
});
|
|
940
|
+
const orgExitCode = await (0, cf_1.targetCloudFoundryOrg)(target.org);
|
|
941
|
+
if (orgExitCode !== 0) {
|
|
942
|
+
console.log(chalk_1.default.yellow("Cannot switch to this org after automatic authentication."));
|
|
943
|
+
console.log(chalk_1.default.gray("Run smdg cf login, save the password, then try again."));
|
|
944
|
+
process.exitCode = orgExitCode;
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
let space = options.space?.trim() || target.space?.trim() || "";
|
|
948
|
+
if (!space) {
|
|
949
|
+
const spaces = await (0, cf_1.listCloudFoundrySpaces)().catch(() => []);
|
|
950
|
+
const currentAfter = await (0, cf_1.readCloudFoundryTarget)();
|
|
951
|
+
const preferred = currentAfter.space || (spaces.includes("app") ? "app" : spaces[0]);
|
|
952
|
+
space = spaces.length
|
|
953
|
+
? await (0, prompts_2.searchableSelectChoice)({
|
|
954
|
+
message: "Select CF space",
|
|
955
|
+
choices: [
|
|
956
|
+
...spaces.filter((s) => s === preferred).map((s) => ({ title: `${s} ${chalk_1.default.gray("suggested")}`, value: s })),
|
|
957
|
+
...spaces.filter((s) => s !== preferred).map((s) => ({ title: s, value: s })),
|
|
958
|
+
],
|
|
959
|
+
validateCustomValue: validateRequired,
|
|
960
|
+
customValueTitle: (value) => `Use typed CF space: ${value}`,
|
|
961
|
+
})
|
|
962
|
+
: await (0, prompts_2.selectFromHistoryOrInput)({ message: "Enter CF space", values: [], initialValue: "app" });
|
|
963
|
+
}
|
|
964
|
+
if (space) {
|
|
965
|
+
const spaceExitCode = await (0, cf_1.targetCloudFoundrySpace)(space);
|
|
966
|
+
if (spaceExitCode !== 0) {
|
|
967
|
+
process.exitCode = spaceExitCode;
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
await (0, cf_target_cache_1.addRecentTarget)({ region, apiEndpoint, org: target.org, space });
|
|
972
|
+
if (authenticatedProfile?.password) {
|
|
973
|
+
await (0, cache_1.rememberCloudFoundryLoginProfile)({ ...authenticatedProfile, apiEndpoint, org: target.org, space, updatedAt: new Date().toISOString() });
|
|
974
|
+
}
|
|
975
|
+
console.log(chalk_1.default.green("CF target switched."));
|
|
976
|
+
printTarget(await (0, cf_1.readCloudFoundryTarget)());
|
|
977
|
+
if (!(await (0, cf_target_cache_1.isFavoriteTarget)({ region, apiEndpoint, org: target.org, space }))) {
|
|
978
|
+
const favorite = await (0, prompts_1.default)({ type: "confirm", name: "fav", message: "Mark this target as favorite?", initial: false });
|
|
979
|
+
if (favorite.fav) {
|
|
980
|
+
await (0, cf_target_cache_1.addFavoriteTarget)({ region, apiEndpoint, org: target.org, space });
|
|
981
|
+
console.log(chalk_1.default.gray("Added to favorites."));
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
async function manageFavoriteTargets(favorites) {
|
|
986
|
+
if (!favorites.length) {
|
|
987
|
+
console.log(chalk_1.default.gray("No favorites yet. Switch to a target and choose to favorite it."));
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
const selected = await (0, prompts_2.searchableSelectChoice)({
|
|
991
|
+
message: "Favorites — select one to remove",
|
|
992
|
+
choices: [
|
|
993
|
+
...favorites.map((target, index) => ({ title: `★ ${(0, cf_target_types_1.cfTargetLabel)(target)}`, value: String(index) })),
|
|
994
|
+
{ title: "Cancel", value: "__cancel__" },
|
|
995
|
+
],
|
|
996
|
+
allowCustomValue: false,
|
|
997
|
+
});
|
|
998
|
+
if (selected === "__cancel__") {
|
|
999
|
+
return;
|
|
1000
|
+
}
|
|
1001
|
+
await (0, cf_target_cache_1.removeFavoriteTarget)(favorites[Number(selected)]);
|
|
1002
|
+
console.log(chalk_1.default.green("Removed from favorites."));
|
|
1003
|
+
}
|
|
1004
|
+
function printTargetSections(favorites, recent, orgEntries, current) {
|
|
1005
|
+
console.log(chalk_1.default.bold("CF Target Switcher"));
|
|
1006
|
+
console.log("");
|
|
1007
|
+
if (favorites.length) {
|
|
1008
|
+
console.log(chalk_1.default.yellow("Favorites"));
|
|
1009
|
+
favorites.forEach((target) => console.log(` ${chalk_1.default.yellow("★")} ${(0, cf_target_types_1.cfTargetLabel)(target)}`));
|
|
1010
|
+
console.log("");
|
|
1011
|
+
}
|
|
1012
|
+
if (recent.length) {
|
|
1013
|
+
console.log(chalk_1.default.cyan("Recent"));
|
|
1014
|
+
recent.forEach((target) => console.log(` ${chalk_1.default.gray("◷")} ${(0, cf_target_types_1.cfTargetLabel)(target)}`));
|
|
1015
|
+
console.log("");
|
|
1016
|
+
}
|
|
1017
|
+
console.log(chalk_1.default.gray(`All Targets (${orgEntries.length})`));
|
|
1018
|
+
for (const entry of orgEntries) {
|
|
1019
|
+
const marker = entry.apiEndpoint === current.apiEndpoint && entry.org === current.org ? chalk_1.default.green("*") : " ";
|
|
1020
|
+
console.log(`${marker} ${entry.region} / ${entry.org}`);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
910
1023
|
async function runOrgCommand(options) {
|
|
911
|
-
const
|
|
1024
|
+
const latestTarget = await (0, cf_1.readCloudFoundryTarget)();
|
|
1025
|
+
const favorites = await (0, cf_target_cache_1.listFavoriteTargets)();
|
|
1026
|
+
const recent = await (0, cf_target_cache_1.listRecentTargets)();
|
|
1027
|
+
// Direct switch via flags.
|
|
1028
|
+
if (options.org?.trim()) {
|
|
1029
|
+
await (0, tooling_1.ensureExternalTool)("cf");
|
|
1030
|
+
const apiEndpoint = options.api?.trim() || latestTarget.apiEndpoint || "";
|
|
1031
|
+
const region = (0, cf_1.inferCloudFoundryRegionFromApiEndpoint)(apiEndpoint || "current");
|
|
1032
|
+
await switchToCfTarget({ region, apiEndpoint, org: options.org.trim(), space: options.space?.trim() || "" }, { space: options.space });
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
912
1035
|
const action = options.list
|
|
913
1036
|
? "list"
|
|
914
1037
|
: options.switch
|
|
915
1038
|
? "switch"
|
|
916
1039
|
: await (0, prompts_2.searchableSelectChoice)({
|
|
917
|
-
message: "
|
|
1040
|
+
message: "CF target switcher",
|
|
918
1041
|
choices: [
|
|
919
|
-
{ title: "
|
|
920
|
-
{ title: "
|
|
1042
|
+
{ title: "Switch to a target (favorites, recent, all)", value: "switch" },
|
|
1043
|
+
{ title: "Refresh all regions", value: "refresh" },
|
|
1044
|
+
{ title: "List targets", value: "list" },
|
|
1045
|
+
{ title: "Manage favorites", value: "favorites" },
|
|
1046
|
+
{ title: "Show current target", value: "current" },
|
|
921
1047
|
],
|
|
922
|
-
|
|
923
|
-
const normalizedValue = value.trim().toLowerCase();
|
|
924
|
-
return normalizedValue === "list" || normalizedValue === "switch"
|
|
925
|
-
? true
|
|
926
|
-
: "Type list or switch, or select one option.";
|
|
927
|
-
},
|
|
1048
|
+
allowCustomValue: false,
|
|
928
1049
|
});
|
|
929
|
-
|
|
930
|
-
api: options.api,
|
|
931
|
-
refresh: options.refresh,
|
|
932
|
-
});
|
|
933
|
-
const organizationRegionCount = new Set(organizationEntries.map((entry) => entry.region)).size;
|
|
934
|
-
const latestTarget = await (0, cf_1.readCloudFoundryTarget)();
|
|
935
|
-
if (action === "list") {
|
|
1050
|
+
if (action === "current") {
|
|
936
1051
|
printTarget(latestTarget);
|
|
937
|
-
console.log("");
|
|
938
|
-
if (!organizationEntries.length) {
|
|
939
|
-
console.log(chalk_1.default.yellow("No orgs found for current CF user. Run smdg cf login, save the password, then run smdg cf org again."));
|
|
940
|
-
return;
|
|
941
|
-
}
|
|
942
|
-
console.log(chalk_1.default.gray(`Showing ${organizationEntries.length} org(s) across ${organizationRegionCount} region(s).`));
|
|
943
|
-
console.log("");
|
|
944
|
-
for (const entry of organizationEntries) {
|
|
945
|
-
const marker = entry.apiEndpoint === latestTarget.apiEndpoint && entry.org === latestTarget.org ? chalk_1.default.green("*") : " ";
|
|
946
|
-
console.log(`${marker} ${formatCloudFoundryOrgEntry(entry, latestTarget)}`);
|
|
947
|
-
}
|
|
948
1052
|
return;
|
|
949
1053
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
};
|
|
1054
|
+
if (action === "favorites") {
|
|
1055
|
+
await manageFavoriteTargets(favorites);
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
let orgEntries;
|
|
1059
|
+
if (action === "refresh" || options.refresh) {
|
|
1060
|
+
await (0, tooling_1.ensureExternalTool)("cf");
|
|
1061
|
+
console.log(chalk_1.default.gray("Refreshing CF orgs across regions..."));
|
|
1062
|
+
orgEntries = await getCloudFoundryOrganizationsAcrossRegions({ api: options.api, refresh: true });
|
|
1063
|
+
console.log(chalk_1.default.green(`Refresh completed · ${orgEntries.length} target(s) found.`));
|
|
1064
|
+
console.log("");
|
|
962
1065
|
}
|
|
963
1066
|
else {
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
console.log(chalk_1.default.gray("Run smdg cf login and save the password, then run smdg cf org --list --refresh."));
|
|
967
|
-
return;
|
|
968
|
-
}
|
|
969
|
-
const selectedIndex = await (0, prompts_2.searchableSelectChoice)({
|
|
970
|
-
message: `Search CF org across ${organizationRegionCount} region(s)`,
|
|
971
|
-
choices: organizationEntries.map((entry, index) => ({
|
|
972
|
-
title: formatCloudFoundryOrgEntry(entry, latestTarget),
|
|
973
|
-
value: String(index),
|
|
974
|
-
})),
|
|
975
|
-
validateCustomValue: validateRequired,
|
|
976
|
-
customValueTitle: (value) => `Use typed CF org in current region: ${value}`,
|
|
977
|
-
});
|
|
978
|
-
selectedEntry = organizationEntries[Number(selectedIndex)] ?? {
|
|
979
|
-
apiEndpoint: latestTarget.apiEndpoint ?? "",
|
|
980
|
-
region: (0, cf_1.inferCloudFoundryRegionFromApiEndpoint)(latestTarget.apiEndpoint ?? "current"),
|
|
981
|
-
org: selectedIndex,
|
|
982
|
-
updatedAt: new Date().toISOString(),
|
|
983
|
-
};
|
|
1067
|
+
const cache = await (0, cache_1.readCache)();
|
|
1068
|
+
orgEntries = cache.cloudFoundry.orgsAcrossRegions ?? [];
|
|
984
1069
|
}
|
|
985
|
-
if (
|
|
986
|
-
|
|
1070
|
+
if (action === "list") {
|
|
1071
|
+
printTargetSections(favorites, recent, orgEntries, latestTarget);
|
|
1072
|
+
return;
|
|
987
1073
|
}
|
|
988
|
-
const
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
const orgExitCode = await (0, cf_1.targetCloudFoundryOrg)(selectedEntry.org);
|
|
995
|
-
if (orgExitCode !== 0) {
|
|
996
|
-
console.log(chalk_1.default.yellow("Cannot switch to this org after automatic authentication."));
|
|
997
|
-
console.log(chalk_1.default.gray("Run smdg cf login, save the password, then try again."));
|
|
998
|
-
process.exitCode = orgExitCode;
|
|
1074
|
+
const allTargets = orgEntries.map(orgEntryToTarget);
|
|
1075
|
+
const recentKeys = new Set(recent.map((target) => (0, cf_target_types_1.cfTargetKey)(target)));
|
|
1076
|
+
const combined = dedupeTargets([...favorites, ...recent, ...allTargets]);
|
|
1077
|
+
if (!combined.length) {
|
|
1078
|
+
console.log(chalk_1.default.yellow("No cached targets yet."));
|
|
1079
|
+
console.log(chalk_1.default.gray("Choose 'Refresh all regions', or run smdg cf login and save the password."));
|
|
999
1080
|
return;
|
|
1000
1081
|
}
|
|
1001
|
-
const
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
...spaces
|
|
1008
|
-
.filter((spaceName) => spaceName === preferredSpace)
|
|
1009
|
-
.map((spaceName) => ({ title: `${spaceName} ${spaceName === currentTargetAfterOrgSwitch.space ? chalk_1.default.gray("current") : chalk_1.default.gray("suggested")}`, value: spaceName })),
|
|
1010
|
-
...spaces
|
|
1011
|
-
.filter((spaceName) => spaceName !== preferredSpace)
|
|
1012
|
-
.map((spaceName) => ({ title: spaceName, value: spaceName })),
|
|
1013
|
-
],
|
|
1082
|
+
const selectedIndex = await (0, prompts_2.searchableSelectChoice)({
|
|
1083
|
+
message: `Select CF target (${favorites.length} favorite · ${recent.length} recent · ${allTargets.length} all)`,
|
|
1084
|
+
choices: combined.map((target, index) => {
|
|
1085
|
+
const marker = target.isFavorite ? chalk_1.default.yellow("★ ") : recentKeys.has((0, cf_target_types_1.cfTargetKey)(target)) ? chalk_1.default.gray("◷ ") : " ";
|
|
1086
|
+
return { title: `${marker}${(0, cf_target_types_1.cfTargetLabel)(target)}`, value: String(index) };
|
|
1087
|
+
}),
|
|
1014
1088
|
validateCustomValue: validateRequired,
|
|
1015
|
-
customValueTitle: (value) => `Use typed
|
|
1089
|
+
customValueTitle: (value) => `Use typed org in current region: ${value}`,
|
|
1016
1090
|
});
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
await (0, cache_1.rememberCloudFoundryLoginProfile)({
|
|
1026
|
-
...authenticatedProfile,
|
|
1027
|
-
apiEndpoint: selectedEntry.apiEndpoint,
|
|
1028
|
-
org: selectedEntry.org,
|
|
1029
|
-
space,
|
|
1030
|
-
updatedAt: new Date().toISOString(),
|
|
1031
|
-
});
|
|
1032
|
-
}
|
|
1033
|
-
const switchedTarget = await (0, cf_1.readCloudFoundryTarget)();
|
|
1034
|
-
console.log(chalk_1.default.green("CF org/space switched."));
|
|
1035
|
-
printTarget(switchedTarget);
|
|
1091
|
+
const chosen = combined[Number(selectedIndex)] ?? {
|
|
1092
|
+
region: (0, cf_1.inferCloudFoundryRegionFromApiEndpoint)(latestTarget.apiEndpoint ?? "current"),
|
|
1093
|
+
apiEndpoint: latestTarget.apiEndpoint ?? "",
|
|
1094
|
+
org: selectedIndex,
|
|
1095
|
+
space: "",
|
|
1096
|
+
};
|
|
1097
|
+
await (0, tooling_1.ensureExternalTool)("cf");
|
|
1098
|
+
await switchToCfTarget(chosen, { space: options.space });
|
|
1036
1099
|
}
|
|
1037
1100
|
async function runAppsCommand(options) {
|
|
1038
|
-
const target = await (
|
|
1039
|
-
const targetKey = (0, cf_1.buildCloudFoundryTargetKey)(target);
|
|
1040
|
-
const cache = await (0, cache_1.readCache)();
|
|
1041
|
-
const cachedEntry = cache.cloudFoundry.appListsByTarget[targetKey];
|
|
1101
|
+
const target = await ensureCloudFoundrySessionFromCache();
|
|
1042
1102
|
printTarget(target);
|
|
1043
1103
|
console.log("");
|
|
1044
|
-
const
|
|
1045
|
-
const
|
|
1046
|
-
|
|
1047
|
-
|
|
1104
|
+
const region = target.apiEndpoint ? (0, cf_1.inferCloudFoundryRegionFromApiEndpoint)(target.apiEndpoint) : "current";
|
|
1105
|
+
const cacheKey = (0, smart_cache_1.buildCfAppsKey)(region, target.org ?? "?", target.space ?? "?");
|
|
1106
|
+
const targetLabel = `${region} / ${target.org ?? "?"} / ${target.space ?? "?"}`;
|
|
1107
|
+
const result = await (0, smart_cache_1.smartRead)({
|
|
1108
|
+
namespace: "cf-apps",
|
|
1109
|
+
key: cacheKey,
|
|
1110
|
+
ttlMs: smart_cache_1.DEFAULT_CACHE_TTL.cfApps,
|
|
1111
|
+
mode: options.refresh ? "network-only" : "stale-while-revalidate",
|
|
1112
|
+
fetcher: cf_1.listCloudFoundryApps,
|
|
1048
1113
|
});
|
|
1049
|
-
if (
|
|
1050
|
-
console.log(chalk_1.default.gray(`Using cached
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
else if (options.refresh) {
|
|
1055
|
-
console.log(chalk_1.default.green(`Refreshed cf apps cache for ${targetKey}.`));
|
|
1114
|
+
if (result.fromCache) {
|
|
1115
|
+
console.log(chalk_1.default.gray(`Using cached apps for ${targetLabel} from ${(0, smart_cache_1.formatRelativeTime)(result.updatedAt)}.`));
|
|
1116
|
+
if (result.isRefreshing) {
|
|
1117
|
+
console.log(chalk_1.default.gray("Refreshing apps in background..."));
|
|
1118
|
+
}
|
|
1056
1119
|
console.log("");
|
|
1057
1120
|
}
|
|
1058
1121
|
if (options.select) {
|
|
@@ -1060,9 +1123,25 @@ async function runAppsCommand(options) {
|
|
|
1060
1123
|
console.log(appName);
|
|
1061
1124
|
return;
|
|
1062
1125
|
}
|
|
1063
|
-
for (const app of
|
|
1126
|
+
for (const app of result.data) {
|
|
1064
1127
|
console.log([app.name, app.requestedState, app.processes, app.routes].filter(Boolean).join(" | "));
|
|
1065
1128
|
}
|
|
1129
|
+
// Mirror the smart-cache apps into the legacy per-target cache used by
|
|
1130
|
+
// resolveAppSelection so bind/debug/logs stay in sync.
|
|
1131
|
+
await (0, cache_1.rememberCloudFoundryApps)((0, cf_1.buildCloudFoundryTargetKey)(target), result.data).catch(() => undefined);
|
|
1132
|
+
if (result.refreshPromise) {
|
|
1133
|
+
try {
|
|
1134
|
+
const fresh = await result.refreshPromise;
|
|
1135
|
+
await (0, cache_1.rememberCloudFoundryApps)((0, cf_1.buildCloudFoundryTargetKey)(target), fresh).catch(() => undefined);
|
|
1136
|
+
console.log("");
|
|
1137
|
+
console.log(chalk_1.default.gray("Background refresh completed. Cache updated."));
|
|
1138
|
+
}
|
|
1139
|
+
catch (error) {
|
|
1140
|
+
console.log("");
|
|
1141
|
+
console.log(chalk_1.default.yellow(`Background refresh failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
1142
|
+
console.log(chalk_1.default.gray("Showing cached apps. Run smdg cf login if your session expired."));
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1066
1145
|
}
|
|
1067
1146
|
async function runAppsCacheRefreshCommand() {
|
|
1068
1147
|
await refreshAppsCacheForCurrentTarget();
|