@monster-orange/den 0.2.8 → 0.4.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/CHANGELOG.md +1731 -0
- package/README.md +4 -2
- package/bin/den +2 -1
- package/docs/distributions.md +730 -6
- package/make/den.mk +19 -4
- package/make/journeys.mk +2 -1
- package/make/local_dev.mk +6 -13
- package/make/secrets-1password.mk +18 -0
- package/make/test.mk +1 -1
- package/package.json +12 -2
- package/schemas/den.plugin.v1.json +156 -0
- package/schemas/den.theme.v1.json +269 -0
- package/scripts/auth-configure.ts +16 -8
- package/scripts/heal-config.ts +0 -5
- package/scripts/setup-1password.ts +41 -19
- package/scripts/setup-secrets.ts +67 -101
- package/src/pillars/api/global-setup.ts +1 -1
- package/src/pillars/journeys/sandbox.ts +23 -18
- package/src/pillars/journeys/tools/browser-session.ts +2 -2
- package/src/scripts/setup-default-workspace.ts +2 -2
- package/src/shared/agents/default-pack/be-dev.md +20 -0
- package/src/shared/agents/default-pack/devops.md +14 -0
- package/src/shared/agents/default-pack/fe-dev.md +20 -0
- package/src/shared/agents/default-pack/orchestrator.md +16 -0
- package/src/shared/agents/default-pack/planner.md +17 -0
- package/src/shared/agents/default-pack/reviewer.md +14 -0
- package/src/shared/agents/default-pack/ux-designer.md +15 -0
- package/src/shared/agents/den-claude.ts +173 -0
- package/src/shared/agents/generic.ts +52 -0
- package/src/shared/agents/personas.ts +218 -0
- package/src/shared/agents/records.ts +118 -0
- package/src/shared/agents/runtime.ts +192 -0
- package/src/shared/agents/runtimes.ts +47 -0
- package/src/shared/auth/headless-login.ts +1 -1
- package/src/shared/auth/sudo-vault.ts +93 -31
- package/src/shared/auth/token-providers/index.ts +7 -3
- package/src/shared/brand.ts +7 -0
- package/src/shared/config-error.ts +33 -3
- package/src/shared/constants.ts +3 -24
- package/src/shared/den-config.ts +257 -5
- package/src/shared/edge/config.ts +147 -21
- package/src/shared/edge/exec.ts +31 -0
- package/src/shared/edge/included.ts +169 -0
- package/src/shared/edge/render.ts +69 -14
- package/src/shared/edge/runtime.ts +128 -121
- package/src/shared/edge/status.ts +68 -8
- package/src/shared/edge/tls.ts +328 -0
- package/src/shared/edge/types.ts +25 -9
- package/src/shared/engine.ts +23 -0
- package/src/shared/environment-manager/default-workspace.ts +16 -1
- package/src/shared/environment-manager/health.ts +73 -38
- package/src/shared/environment-manager/scope.ts +168 -0
- package/src/shared/environment-manager/service-runner.ts +789 -43
- package/src/shared/environment-manager/up-targets.ts +132 -0
- package/src/shared/environment-manager/workspaces.ts +19 -0
- package/src/shared/health/rollup.ts +48 -0
- package/src/shared/open-url.ts +21 -2
- package/src/shared/org-sources.ts +78 -59
- package/src/shared/org.ts +53 -5
- package/src/shared/plugins/manifest.ts +406 -0
- package/src/shared/plugins/store.ts +358 -0
- package/src/shared/process/legacy-adopt.ts +285 -0
- package/src/shared/process/process-group.ts +202 -0
- package/src/shared/process/spawn-streaming.ts +7 -0
- package/src/shared/secret-env.ts +70 -0
- package/src/shared/secrets/doppler.ts +1 -1
- package/src/shared/secrets/index.ts +279 -50
- package/src/shared/secrets/none.ts +56 -0
- package/src/shared/secrets/strategies.ts +202 -0
- package/src/shared/secrets/types.ts +1 -1
- package/src/shared/sessions/client.ts +277 -0
- package/src/shared/sessions/den-pty.ts +97 -0
- package/src/shared/sessions/env.ts +79 -0
- package/src/shared/sessions/host.ts +667 -0
- package/src/shared/sessions/names.ts +85 -0
- package/src/shared/sessions/paths.ts +27 -0
- package/src/shared/sessions/protocol.ts +72 -0
- package/src/shared/sessions/pty.ts +141 -0
- package/src/shared/sessions/ring-buffer.ts +160 -0
- package/src/shared/sessions/types.ts +81 -0
- package/src/shared/smoke/git-remote.ts +40 -0
- package/src/shared/stacks/manifest-commands.ts +1 -1
- package/src/shared/stacks/namespace.ts +178 -0
- package/src/shared/stacks/owners.ts +303 -0
- package/src/shared/stacks/render.ts +8 -3
- package/src/shared/stacks/runtime.ts +100 -16
- package/src/shared/stacks/status.ts +63 -12
- package/src/shared/strategies/registry.ts +89 -0
- package/src/shared/theme/contract.ts +538 -0
- package/src/shared/types/index.ts +12 -3
- package/src/ui/cli/agents-command.ts +812 -0
- package/src/ui/cli/create.ts +24 -7
- package/src/ui/cli/doctor-command.ts +260 -0
- package/src/ui/cli/edge-command.ts +56 -10
- package/src/ui/cli/env-probe.ts +266 -0
- package/src/ui/cli/flag-parser.ts +18 -5
- package/src/ui/cli/health-display.ts +25 -0
- package/src/ui/cli/index.ts +580 -60
- package/src/ui/cli/notice.ts +20 -0
- package/src/ui/cli/plugin-command.ts +849 -0
- package/src/ui/cli/sessions-command.ts +718 -0
- package/src/ui/cli/stack-command.ts +63 -2
- package/src/ui/cli/test-runner.ts +11 -9
- package/src/ui/cli/theme-command.ts +136 -0
- package/src/ui/cli/web-command.ts +47 -23
- package/src/ui/mcp/api.ts +28 -2
- package/src/ui/mcp/scope.ts +145 -20
- package/src/ui/mcp/server.ts +27 -2
- package/src/ui/mcp/tools.ts +94 -12
- package/src/ui/server/appearance-prefs.ts +95 -0
- package/src/ui/server/audit.ts +35 -0
- package/src/ui/server/daemon-reaper.ts +29 -1
- package/src/ui/server/engine-stamp.ts +83 -0
- package/src/ui/server/github-service.ts +23 -44
- package/src/ui/server/graph-page.ts +20 -0
- package/src/ui/server/graph-prefs.ts +44 -1
- package/src/ui/server/http-errors.ts +293 -0
- package/src/ui/server/http-helpers.ts +22 -3
- package/src/ui/server/index.ts +539 -51
- package/src/ui/server/public/appearance.js +531 -0
- package/src/ui/server/public/edge-page.js +248 -0
- package/src/ui/server/public/edge.html +251 -0
- package/src/ui/server/public/environments.html +65 -83
- package/src/ui/server/public/error-page.js +247 -0
- package/src/ui/server/public/error.html +260 -0
- package/src/ui/server/public/graph-model.js +332 -14
- package/src/ui/server/public/graph.css +141 -47
- package/src/ui/server/public/graph.js +223 -44
- package/src/ui/server/public/header.js +396 -600
- package/src/ui/server/public/health-fit.js +248 -0
- package/src/ui/server/public/health-rollup.js +128 -0
- package/src/ui/server/public/i18n/es.js +287 -0
- package/src/ui/server/public/i18n/ja.js +276 -0
- package/src/ui/server/public/index.html +139 -438
- package/src/ui/server/public/local-dev.html +3468 -4044
- package/src/ui/server/public/logs.html +71 -208
- package/src/ui/server/public/panel.js +14 -2
- package/src/ui/server/public/release.html +175 -389
- package/src/ui/server/public/services.html +596 -770
- package/src/ui/server/public/settings-page.js +759 -0
- package/src/ui/server/public/settings-registry.js +521 -0
- package/src/ui/server/public/settings.html +590 -0
- package/src/ui/server/public/shell.js +3869 -0
- package/src/ui/server/public/topbar.js +1413 -0
- package/src/ui/server/public/version-label.js +12 -0
- package/src/ui/server/public/workspace-picker.js +74 -34
- package/src/ui/server/readonly-routes.ts +37 -3
- package/src/ui/server/services-list.ts +94 -17
- package/src/ui/server/settings-distribution.ts +74 -0
- package/src/ui/server/shell-prefs.ts +192 -0
- package/src/ui/server/theme-routes.ts +292 -0
- package/src/ui/server/theme-runtime.ts +407 -0
- package/src/ui/server/ui-assets.ts +16 -1
- package/src/ui/server/ui-language.ts +50 -0
- package/src/ui/server/workspace-routes.ts +48 -18
- package/themes/den/den-theme.json +72 -0
- package/themes/den/favicon.ico +0 -0
- package/themes/den/mark.svg +6 -0
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* server's /api/health endpoint and the CLI's status command.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { rollupRepo, type RepoRollup } from "../health/rollup.js";
|
|
8
9
|
import { hasRuntime, repoKind } from "../repo-kind.js";
|
|
9
10
|
import * as https from "https";
|
|
10
11
|
import * as tls from "tls";
|
|
@@ -45,6 +46,7 @@ import type {
|
|
|
45
46
|
ResolvedService,
|
|
46
47
|
ServiceCatalog,
|
|
47
48
|
ServiceHealthConfig,
|
|
49
|
+
WorkspaceConfig,
|
|
48
50
|
} from "../types/index.js";
|
|
49
51
|
import type { Environment } from "../types/index.js";
|
|
50
52
|
|
|
@@ -80,6 +82,13 @@ export interface ServiceHealthResult {
|
|
|
80
82
|
/** Where the service runs for the env checked (R13). */
|
|
81
83
|
runs?: Placement["runs"];
|
|
82
84
|
target?: Placement["target"];
|
|
85
|
+
/** A repo row's rollup of every service (rollupRepoHealth). */
|
|
86
|
+
rollup?: RepoRollup;
|
|
87
|
+
/**
|
|
88
|
+
* The workspaces checked that define this service, when more than one
|
|
89
|
+
* was (checkCatalogHealth's `workspaces`): a shared service, listed once.
|
|
90
|
+
*/
|
|
91
|
+
workspaces?: string[];
|
|
83
92
|
}
|
|
84
93
|
|
|
85
94
|
// ── Name resolution ───────────────────────────────────────────────────────────
|
|
@@ -184,11 +193,13 @@ function extraCa(): string[] {
|
|
|
184
193
|
if (_extraCa) return _extraCa;
|
|
185
194
|
_extraCa = [];
|
|
186
195
|
try {
|
|
187
|
-
// The edge's own CA, and the CA it was pointed at (`edge.
|
|
188
|
-
// laptop's mkcert CA that signs the projects'
|
|
196
|
+
// The edge's own CA, and the CA it was pointed at (`edge.tls.ca`, formerly
|
|
197
|
+
// `edge.caroot`, e.g. the laptop's mkcert CA that signs the projects'
|
|
198
|
+
// certificates).
|
|
189
199
|
const cfg = readEdgeConfig();
|
|
190
200
|
const pems = [path.join(cfg.stateDir, "ca", "rootCA.pem")];
|
|
191
|
-
|
|
201
|
+
const ca = cfg.tls.strategy === "none" ? null : cfg.tls.ca;
|
|
202
|
+
if (ca) pems.push(path.join(ca, "rootCA.pem"));
|
|
192
203
|
for (const pem of pems)
|
|
193
204
|
if (fs.existsSync(pem)) _extraCa.push(fs.readFileSync(pem, "utf-8"));
|
|
194
205
|
} catch {
|
|
@@ -823,32 +834,45 @@ export async function checkServiceHealth(
|
|
|
823
834
|
export async function checkCatalogHealth(
|
|
824
835
|
catalog: ServiceCatalog = readCatalog(),
|
|
825
836
|
env: Environment = "local",
|
|
837
|
+
/**
|
|
838
|
+
* The workspaces to check (focus first), e.g. the view scope's. Default:
|
|
839
|
+
* the focused one. A service several define is checked once, from the
|
|
840
|
+
* first, with `workspaces` naming every one.
|
|
841
|
+
*/
|
|
842
|
+
workspaces?: { name: string; config: WorkspaceConfig }[],
|
|
826
843
|
): Promise<ServiceHealthResult[]> {
|
|
827
844
|
const ws = getActiveWorkspace();
|
|
845
|
+
const list = workspaces ?? (ws ? [ws] : []);
|
|
846
|
+
const byAddress = new Map<string, ResolvedService>();
|
|
847
|
+
const owners = new Map<string, string[]>();
|
|
848
|
+
for (const w of list)
|
|
849
|
+
for (const r of listWorkspaceServices(w.config, catalog, w.name)) {
|
|
850
|
+
// Modules and libraries have no runtime, so no health of their own.
|
|
851
|
+
if (!hasRuntime(repoKind(r.repoPath))) continue;
|
|
852
|
+
if (!byAddress.has(r.address)) byAddress.set(r.address, r);
|
|
853
|
+
const o = owners.get(r.address) ?? [];
|
|
854
|
+
if (!o.includes(w.name)) owners.set(r.address, [...o, w.name]);
|
|
855
|
+
}
|
|
828
856
|
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
857
|
+
return Promise.all(
|
|
858
|
+
[...byAddress.values()].map(async (r) => {
|
|
859
|
+
const h = await checkServiceHealth(r, env);
|
|
860
|
+
return workspaces ? { ...h, workspaces: owners.get(r.address) } : h;
|
|
861
|
+
}),
|
|
833
862
|
);
|
|
834
|
-
|
|
835
|
-
return Promise.all(resolved.map((r) => checkServiceHealth(r, env)));
|
|
836
863
|
}
|
|
837
864
|
|
|
838
865
|
/**
|
|
839
|
-
*
|
|
840
|
-
*
|
|
866
|
+
* One row per repo, its health rolled up from every service's check by the
|
|
867
|
+
* rule the web UI's header row and graph use (src/shared/health/rollup.ts):
|
|
868
|
+
* down when the entry service (the manifest `entrypoint`) is down, degraded
|
|
869
|
+
* when another checked service is, healthy when something is checked and
|
|
870
|
+
* nothing fails, unknown when nothing is checked. Unchecked services never
|
|
871
|
+
* count (0.3.0-dev.10).
|
|
841
872
|
*
|
|
842
|
-
*
|
|
843
|
-
*
|
|
844
|
-
*
|
|
845
|
-
* partially running. Rolling up to the entrypoint answers the real question
|
|
846
|
-
* ("is this repo's service up?") instead.
|
|
847
|
-
*
|
|
848
|
-
* A repo is collapsed only when it has multiple rows AND its manifest declares an
|
|
849
|
-
* `entrypoint` present among them. Repos with no entrypoint (e.g. the
|
|
850
|
-
* infra-stack infra provider, whose subsystems ARE the meaningful services)
|
|
851
|
-
* keep their subsystem breakdown. Single-row repos pass through unchanged.
|
|
873
|
+
* The repo row is the entry service's row under the repo name, with
|
|
874
|
+
* `rollup` set; its `health` is "error" for down or degraded. A remote
|
|
875
|
+
* service keeps its own row too (R13). Single-service repos keep their row.
|
|
852
876
|
*/
|
|
853
877
|
export function rollupRepoHealth(
|
|
854
878
|
results: ServiceHealthResult[],
|
|
@@ -867,23 +891,34 @@ export function rollupRepoHealth(
|
|
|
867
891
|
|
|
868
892
|
const out: ServiceHealthResult[] = [];
|
|
869
893
|
for (const [repoName, rows] of groups) {
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
894
|
+
const entrypoint =
|
|
895
|
+
rows.length > 1 ? getEntrypoint(repoName, rows[0].repoPath) : undefined;
|
|
896
|
+
const rollup = rollupRepo(
|
|
897
|
+
repoName,
|
|
898
|
+
rows.map((r) => ({
|
|
899
|
+
service: r.subsystem,
|
|
900
|
+
ok: r.health === "ok" ? true : r.health === "error" ? false : null,
|
|
901
|
+
isEntrypoint: !!entrypoint && r.subsystem === entrypoint,
|
|
902
|
+
})),
|
|
903
|
+
);
|
|
904
|
+
const lead =
|
|
905
|
+
rows.find((r) => r.subsystem === rollup.entry) ??
|
|
906
|
+
rows.find((r) => r.health === "error") ??
|
|
907
|
+
rows[0];
|
|
908
|
+
out.push({
|
|
909
|
+
...lead,
|
|
910
|
+
address: rows.length > 1 ? repoName : lead.address,
|
|
911
|
+
health:
|
|
912
|
+
rollup.state === "ok"
|
|
913
|
+
? "ok"
|
|
914
|
+
: rollup.state === "unknown"
|
|
915
|
+
? "unknown"
|
|
916
|
+
: "error",
|
|
917
|
+
rollup,
|
|
918
|
+
});
|
|
919
|
+
// A remote service is not a sidecar: it keeps its own row (R13).
|
|
920
|
+
if (rows.length > 1)
|
|
921
|
+
out.push(...rows.filter((r) => r !== lead && r.runs === "remote"));
|
|
887
922
|
}
|
|
888
923
|
return out;
|
|
889
924
|
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The view scope (0.3.0-dev.16): which workspaces' repos are in view, the
|
|
3
|
+
* one source for the header's health row, the pages and `<cli> status`.
|
|
4
|
+
*
|
|
5
|
+
* - **Scope** is a view filter: "all" (every active workspace) or one
|
|
6
|
+
* active workspace. It is a user preference (user.yml `view.scope`) and
|
|
7
|
+
* changes nothing else: no edge route, container or service.
|
|
8
|
+
* - **Focus** is the default target for commands (`up`, `restart`, the
|
|
9
|
+
* Local Dev actions). Scope never changes it.
|
|
10
|
+
*
|
|
11
|
+
* "all" is the default, also with several active workspaces: the health
|
|
12
|
+
* row already covered every active workspace, so a page that showed only
|
|
13
|
+
* the focused one disagreed with the row above it. With one active
|
|
14
|
+
* workspace "all" and that workspace are the same view.
|
|
15
|
+
*/
|
|
16
|
+
import { readUserConfig, type UserConfig } from "../den-config.js";
|
|
17
|
+
import type { WorkspaceConfig } from "../types/index.js";
|
|
18
|
+
import { readWorkspacesFile } from "./workspaces.js";
|
|
19
|
+
|
|
20
|
+
/** "all", or one workspace's name. */
|
|
21
|
+
export type ViewScope = string;
|
|
22
|
+
export const ALL_ACTIVE = "all";
|
|
23
|
+
|
|
24
|
+
const NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
25
|
+
|
|
26
|
+
/** The stored scope, normalised: "all" unless a plausible workspace name. */
|
|
27
|
+
export function storedScope(user: Pick<UserConfig, "view">): ViewScope {
|
|
28
|
+
const s = user.view?.scope;
|
|
29
|
+
return typeof s === "string" && s !== ALL_ACTIVE && NAME.test(s)
|
|
30
|
+
? s
|
|
31
|
+
: ALL_ACTIVE;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* The workspaces in view, focused first, then the other active ones in
|
|
36
|
+
* their file order. A named scope that is no longer active falls back to
|
|
37
|
+
* all active (so a deactivated workspace never empties the view). Pure.
|
|
38
|
+
*/
|
|
39
|
+
export function scopeWorkspaceNames(
|
|
40
|
+
scope: ViewScope,
|
|
41
|
+
ws: { active: string[]; focus: string | null; known: string[] },
|
|
42
|
+
): string[] {
|
|
43
|
+
const active = ws.active.filter((n) => ws.known.includes(n));
|
|
44
|
+
if (scope !== ALL_ACTIVE && active.includes(scope)) return [scope];
|
|
45
|
+
const focus = ws.focus && active.includes(ws.focus) ? ws.focus : null;
|
|
46
|
+
return [...(focus ? [focus] : []), ...active.filter((n) => n !== focus)];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** What a view resolves to: the scope asked for, and what is in it. */
|
|
50
|
+
export interface ScopeView {
|
|
51
|
+
/** The stored scope ("all" or a workspace). */
|
|
52
|
+
scope: ViewScope;
|
|
53
|
+
/** True when a named scope is not active any more: showing all. */
|
|
54
|
+
fellBack: boolean;
|
|
55
|
+
/** The workspaces in view, focused first. */
|
|
56
|
+
workspaces: { name: string; config: WorkspaceConfig }[];
|
|
57
|
+
/** The focused workspace: the default target for commands. */
|
|
58
|
+
focus: string | null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The view for `scope` (default: the user's stored one). */
|
|
62
|
+
export function scopeView(scope?: ViewScope): ScopeView {
|
|
63
|
+
const want = scope ?? storedScope(readUserConfig());
|
|
64
|
+
const file = readWorkspacesFile();
|
|
65
|
+
const known = Object.keys(file.workspaces);
|
|
66
|
+
const focusName =
|
|
67
|
+
process.env.DEN_WORKSPACE_OVERRIDE ?? file.focus ?? file.active[0] ?? null;
|
|
68
|
+
const focus = focusName && file.workspaces[focusName] ? focusName : null;
|
|
69
|
+
const names = scopeWorkspaceNames(want, {
|
|
70
|
+
active: file.active,
|
|
71
|
+
focus,
|
|
72
|
+
known,
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
scope: want,
|
|
76
|
+
fellBack: want !== ALL_ACTIVE && !names.includes(want),
|
|
77
|
+
workspaces: names.map((name) => ({
|
|
78
|
+
name,
|
|
79
|
+
config: file.workspaces[name],
|
|
80
|
+
})),
|
|
81
|
+
focus,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** A cache key for what is in view (the scope's workspaces, in order). */
|
|
86
|
+
export function scopeKey(view: ScopeView): string {
|
|
87
|
+
return view.workspaces.map((w) => w.name).join(",");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Validate a scope a client asks for: "all", or a workspace that is active
|
|
92
|
+
* now. Pure; the caller stores the result in user.yml `view`.
|
|
93
|
+
*/
|
|
94
|
+
export function parseScope(
|
|
95
|
+
raw: unknown,
|
|
96
|
+
active: string[],
|
|
97
|
+
): { ok: true; value: ViewScope } | { ok: false; error: string } {
|
|
98
|
+
if (raw === ALL_ACTIVE) return { ok: true, value: ALL_ACTIVE };
|
|
99
|
+
if (typeof raw !== "string" || !NAME.test(raw))
|
|
100
|
+
return { ok: false, error: `scope must be "all" or a workspace name` };
|
|
101
|
+
if (!active.includes(raw))
|
|
102
|
+
return { ok: false, error: `workspace "${raw}" is not active` };
|
|
103
|
+
return { ok: true, value: raw };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The `view` block to store: none for the default, so user.yml stays lean. */
|
|
107
|
+
export function storedView(scope: ViewScope): UserConfig["view"] | undefined {
|
|
108
|
+
return scope === ALL_ACTIVE ? undefined : { scope };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** What `/api/scope` returns: the scope, what it shows, and the focus. */
|
|
112
|
+
export function scopePayload(view: ScopeView): {
|
|
113
|
+
scope: ViewScope;
|
|
114
|
+
fellBack: boolean;
|
|
115
|
+
inView: string[];
|
|
116
|
+
focus: string | null;
|
|
117
|
+
} {
|
|
118
|
+
return {
|
|
119
|
+
scope: view.fellBack ? ALL_ACTIVE : view.scope,
|
|
120
|
+
fellBack: view.fellBack,
|
|
121
|
+
inView: view.workspaces.map((w) => w.name),
|
|
122
|
+
focus: view.focus,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Take `--scope <all|workspace>` (or `--scope=…`) out of argv. `--all` is
|
|
128
|
+
* not it: on `status` that already means verbose (one row per service).
|
|
129
|
+
*/
|
|
130
|
+
export function takeScopeFlag(argv: string[]): {
|
|
131
|
+
scope: string | null;
|
|
132
|
+
rest: string[];
|
|
133
|
+
error?: string;
|
|
134
|
+
} {
|
|
135
|
+
const rest: string[] = [];
|
|
136
|
+
let scope: string | null = null;
|
|
137
|
+
for (let i = 0; i < argv.length; i++) {
|
|
138
|
+
const a = argv[i];
|
|
139
|
+
if (a === "--scope") {
|
|
140
|
+
const v = argv[i + 1];
|
|
141
|
+
if (v === undefined || v.startsWith("-"))
|
|
142
|
+
return { scope: null, rest, error: "--scope needs all or a workspace" };
|
|
143
|
+
scope = v;
|
|
144
|
+
i++;
|
|
145
|
+
} else if (a.startsWith("--scope=")) scope = a.slice("--scope=".length);
|
|
146
|
+
else rest.push(a);
|
|
147
|
+
}
|
|
148
|
+
return { scope, rest };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* The scope line `status` prints, so a reader sees what is in view and
|
|
153
|
+
* what commands target: "Scope: all active (a, b, c) · focus: a".
|
|
154
|
+
*/
|
|
155
|
+
export function scopeLine(view: ScopeView): string {
|
|
156
|
+
const names = view.workspaces.map((w) => w.name);
|
|
157
|
+
const what =
|
|
158
|
+
view.scope !== ALL_ACTIVE && !view.fellBack
|
|
159
|
+
? view.scope
|
|
160
|
+
: `all active (${names.join(", ") || "none"})`;
|
|
161
|
+
const fell = view.fellBack ? ` (${view.scope} is not active)` : "";
|
|
162
|
+
return `Scope: ${what}${fell} · focus: ${view.focus ?? "none"}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** " · a, b" after a shared service's name; "" when one workspace owns it. */
|
|
166
|
+
export function ownersSuffix(owners: string[] | undefined): string {
|
|
167
|
+
return owners && owners.length > 1 ? ` · ${owners.join(", ")}` : "";
|
|
168
|
+
}
|