@monster-orange/den 0.2.4 → 0.2.6
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 +195 -0
- package/docs/distributions.md +158 -16
- package/package.json +1 -1
- package/src/pillars/security/discover.ts +1 -1
- package/src/pillars/smoke/setup-global.ts +2 -1
- package/src/scripts/configure-aws-profiles.ts +2 -1
- package/src/scripts/register-repos.ts +4 -3
- package/src/shared/constants.ts +7 -0
- package/src/shared/den-config.ts +130 -12
- package/src/shared/engine.ts +18 -0
- package/src/shared/environment-manager/default-workspace.ts +229 -87
- package/src/shared/environment-manager/health.ts +235 -35
- package/src/shared/manifest/placement.ts +1 -1
- package/src/shared/manifest/schema.ts +47 -0
- package/src/shared/manifest/service-links.ts +1 -1
- package/src/shared/manifest/validate-manifest.ts +1 -1
- package/src/shared/plural.ts +13 -0
- package/src/shared/service-url.ts +2 -5
- package/src/shared/stacks/compose-health.ts +242 -0
- package/src/shared/stacks/runtime.ts +73 -24
- package/src/shared/stacks/status.ts +3 -2
- package/src/shared/types/index.ts +13 -1
- package/src/ui/cli/create.ts +2 -1
- package/src/ui/cli/health-display.ts +1 -0
- package/src/ui/cli/index.ts +86 -41
- package/src/ui/cli/mcp-connect.ts +36 -5
- package/src/ui/cli/server-identity.ts +233 -0
- package/src/ui/cli/service-command.ts +136 -12
- package/src/ui/cli/spinner.ts +48 -0
- package/src/ui/cli/stack-command.ts +40 -12
- package/src/ui/cli/web-command.ts +101 -32
- package/src/ui/mcp/api.ts +94 -1
- package/src/ui/mcp/server.ts +6 -1
- package/src/ui/mcp/tools.ts +6 -2
- package/src/ui/server/health-row.ts +6 -3
- package/src/ui/server/index.ts +120 -40
- package/src/ui/server/public/changelog-view.js +16 -0
- package/src/ui/server/public/header.js +121 -12
- package/src/ui/server/ui-assets.ts +1 -0
- package/src/ui/server/workspace-routes.ts +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,201 @@ All notable changes to Den are documented here. Each release lists the
|
|
|
4
4
|
config and manifest changes a distribution must make to move to it under
|
|
5
5
|
**Migrations**.
|
|
6
6
|
|
|
7
|
+
## [0.2.6] - 2026-11-04
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **`<cli> web restart` and `web down` act on our server from an older
|
|
12
|
+
Den.** A server from before 0.2.6 (no `root` in `/api/server-info`) run
|
|
13
|
+
from our root is ours, and 0.2.6-dev.0 classified it so, but stopping it
|
|
14
|
+
then looked for our pids by command line only: the entry point of this
|
|
15
|
+
engine, or one under the root. An older Den installed elsewhere, or a
|
|
16
|
+
hand-started server, matched neither, so `web restart` printed "OK ... web
|
|
17
|
+
is already running" and restarted nothing, and `web down` printed "OK ...
|
|
18
|
+
web is not running" while the server stayed up and bound. A server the
|
|
19
|
+
identity check finds ours is now stopped by the pid it reports, when that
|
|
20
|
+
pid listens on the port: `restart` replaces it and `down` frees the port,
|
|
21
|
+
with or without a `web.json` for it. web.json's root is compared after
|
|
22
|
+
resolving symlinks (`/var` and `/private/var` are one root).
|
|
23
|
+
- **The MCP server serves nothing from another distribution's server.**
|
|
24
|
+
It read from whatever answered on its port. Before a tool reads, it now
|
|
25
|
+
checks `/api/server-info` as `web up` does; on another distribution's
|
|
26
|
+
server every tool answers `port <N> is in use by <product> at <root>; not
|
|
27
|
+
this distribution's server` and returns none of its data. A positive
|
|
28
|
+
check is trusted for 30s and made again after a connection error, so a
|
|
29
|
+
server that took over the port is noticed. A server that does not say
|
|
30
|
+
whose it is within 5s is not read from either.
|
|
31
|
+
- **A mistyped `service` flag no longer installs.** `<cli> service install
|
|
32
|
+
--anything` ignored the flag and installed; a `service` subcommand now
|
|
33
|
+
refuses an argument it does not take, before running anything.
|
|
34
|
+
- **`<cli> web up` no longer trusts whatever answers on its port.** With
|
|
35
|
+
`web.port` set to a port where another distribution's Den server ran
|
|
36
|
+
(another root, an older Den), `web up` printed "OK ... web is already
|
|
37
|
+
running" and stopped, and `web down` or `web restart` could SIGTERM that
|
|
38
|
+
server: with a linked engine its command line looks like ours.
|
|
39
|
+
`/api/server-info` now also returns `root` (the distribution root the
|
|
40
|
+
server serves) and `product`. `web up`, `web down` and `web restart`
|
|
41
|
+
compare both with their own, and on another distribution's server exit 1
|
|
42
|
+
with `port <N> is in use by <product> at <root>` and a hint to pick
|
|
43
|
+
another port (`--port`, or `web.port`). A server from before 0.2.6 has
|
|
44
|
+
no `root`: it is ours when it runs from our root (its working
|
|
45
|
+
directory), otherwise `port <N> is in use by another server (Den
|
|
46
|
+
<version>)`; anything else is "a non-Den server". `web down` also no
|
|
47
|
+
longer signals a process running from another root, even when the
|
|
48
|
+
server does not answer. `<cli> mcp connect` refuses to point an agent at
|
|
49
|
+
another distribution's server (and `mcp clients` names it), and `<cli>
|
|
50
|
+
service install` no longer reports "running" when another
|
|
51
|
+
distribution's server answers on the port.
|
|
52
|
+
- **A repeat `<cli> workspace seed` says "already seeded".** Seeding a
|
|
53
|
+
workspace that already holds exactly the blueprint printed "Default
|
|
54
|
+
workspace ready" as if it had seeded again; it now prints `✓ Default
|
|
55
|
+
workspace already seeded — N repos (managed): nothing changed.` and
|
|
56
|
+
writes nothing (multi blueprint: `✓ N workspaces already seeded (a, b)`).
|
|
57
|
+
A seed over a workspace the user changed says it was "reset to the
|
|
58
|
+
blueprint".
|
|
59
|
+
- **Counts read "1 repo", not "1 repos".** CLI output that counts repos,
|
|
60
|
+
workspaces, systems, files, profiles and tools uses one plural helper
|
|
61
|
+
(`src/shared/plural.ts`), also in place of "repo(s)".
|
|
62
|
+
- **Spinners no longer loop forever on a 0-column terminal.** On a pty that
|
|
63
|
+
reports 0 columns (one nobody sized, as agent shells give), ora computed
|
|
64
|
+
an infinite number of lines to clear and never finished. A spinner now
|
|
65
|
+
animates only on a TTY with a known, non-zero width; otherwise it prints
|
|
66
|
+
plain lines (its text on start, then the result).
|
|
67
|
+
|
|
68
|
+
### Added
|
|
69
|
+
|
|
70
|
+
- **Health type `container`.** `health: { type: container, project: <compose
|
|
71
|
+
project>, service: <compose service> }` checks a compose container by its
|
|
72
|
+
`com.docker.compose.project` / `com.docker.compose.service` labels, or
|
|
73
|
+
`{ type: container, container_name: <name> }` any container by name. It
|
|
74
|
+
is answered from the one `docker ps` per health cycle the compose checks
|
|
75
|
+
already share (its format gains `{{.Names}}`, and it no longer filters to
|
|
76
|
+
compose containers), so N container checks add no docker call, in the
|
|
77
|
+
web health row and in `<cli> status` (22 checks, 17 compose and 5
|
|
78
|
+
container: 1 docker call per cycle). A running container with a
|
|
79
|
+
healthcheck is its health (healthy up, unhealthy down, starting
|
|
80
|
+
pending), one without is up, and no matching container is down. When
|
|
81
|
+
docker gives no listing it says what a compose check says, through the
|
|
82
|
+
same code: a failed `docker ps` is unknown (`ok: null`, the reason in
|
|
83
|
+
`error`) on every cycle, never down; a listing past its budget is
|
|
84
|
+
pending (`pending: true`), down only when that repeats. Of several matches the best counts:
|
|
85
|
+
healthy, then running. `/api/health` rows show it as `check: "container
|
|
86
|
+
<project>/<service>"`. `<cli> validate` requires `project` + `service` or
|
|
87
|
+
`container_name` (not both, not one of the pair alone, no other type's
|
|
88
|
+
keys) and says which. See docs/distributions.md, "Health checks".
|
|
89
|
+
- **`<cli> service install --plan`.** Read-only: prints the label, the
|
|
90
|
+
plist path, the log file, the roots, whose server answers on `web.port`
|
|
91
|
+
(ours, free, or `port <N> is in use by <product> at <root>`) and the
|
|
92
|
+
plist install would write; writes nothing and never calls launchctl.
|
|
93
|
+
Exits 1 when the port is another distribution's. `<cli> service status`
|
|
94
|
+
also names another distribution's server on the port.
|
|
95
|
+
|
|
96
|
+
### Migrations
|
|
97
|
+
|
|
98
|
+
- None required. Optional: a `type: shell` check that runs `docker ps` to
|
|
99
|
+
find a container (`test -n "$(docker ps -q --filter
|
|
100
|
+
label=com.docker.compose.project=X --filter
|
|
101
|
+
label=com.docker.compose.service=Y)"`) can become `{ type: container,
|
|
102
|
+
project: X, service: Y }`: no process or docker call of its own, and it
|
|
103
|
+
reads the container's healthcheck.
|
|
104
|
+
|
|
105
|
+
## [0.2.5] - 2026-11-04
|
|
106
|
+
|
|
107
|
+
### Fixed
|
|
108
|
+
|
|
109
|
+
- **`<cli> web down` (any SIGTERM) shuts the web server down cleanly
|
|
110
|
+
under load.** `<cli> web up` ran the server under the `tsx` CLI, a
|
|
111
|
+
parent process that relays a signal to the server and, if the server has
|
|
112
|
+
not acknowledged it within about 60ms, SIGKILLs it and exits 143. On a
|
|
113
|
+
loaded machine the server missed that window, so it was killed without
|
|
114
|
+
its shutdown. The server now runs as `node --import <tsx loader>`, one
|
|
115
|
+
process that gets the signal itself, as the launchd service already did.
|
|
116
|
+
- **Compose-stack health no longer starts a CLI per check.** Den's own
|
|
117
|
+
check for a compose repo was a `<cli> stack health <stack> --service X`
|
|
118
|
+
process per manifest service, each a full CLI start running its own
|
|
119
|
+
`docker ps`: 17-19 processes per health cycle for three stacks. On a
|
|
120
|
+
loaded machine (load 35-55) they ran past their budget, and two such
|
|
121
|
+
cycles in a row read every stack down. These checks are now answered
|
|
122
|
+
in the web server and in `<cli> status` from one `docker ps` per cycle,
|
|
123
|
+
shared by every check in it (17 checks: 17 CLI starts and 17 docker
|
|
124
|
+
calls per cycle before, 0 and 1 now). The verdict is the one `stack
|
|
125
|
+
health` gives. A distribution's own shell checks still run as shell
|
|
126
|
+
commands, at most max(2, cpus/2) at a time; a queued check's budget
|
|
127
|
+
starts when it starts.
|
|
128
|
+
- **The health row no longer shows healthy repos down on a cold poll.** A
|
|
129
|
+
check that got no answer within its budget was sent as a finished
|
|
130
|
+
`ok: false`: a compose stack's check (`<cli> stack health`, a full CLI
|
|
131
|
+
start per service, 15-20 of them at once) past its 5s budget on a loaded
|
|
132
|
+
machine, or an HTTP check past its 3s timeout. The row painted it red
|
|
133
|
+
and the 8s health cache replayed that to the next page load, until the
|
|
134
|
+
next poll corrected it. Now a check that has not answered is **pending**
|
|
135
|
+
(`ok: null`, `pending: true`, the reason in `error`), never down. Only a
|
|
136
|
+
check that completed and failed is `ok: false`. A timeout is down only
|
|
137
|
+
when it repeats on the next cycle (`timeout (twice in a row)`), so a hung
|
|
138
|
+
service still shows. `<cli> status` agrees: on a timeout it probes once
|
|
139
|
+
more, and is down only when both probes time out. Health results have a
|
|
140
|
+
new `pending` state (`health: "pending"`), which readiness waits on
|
|
141
|
+
rather than skipping.
|
|
142
|
+
- **"Docker state unknown" is not "not running".** When docker does not
|
|
143
|
+
answer, the health row and `<cli> status` report a compose stack as
|
|
144
|
+
unknown (`ok: null`), not down, and so does `/api/stacks`. `<cli> stack
|
|
145
|
+
health` exits 3 when it could not tell (docker did not answer, the stack
|
|
146
|
+
config could not be read), 1 only when a stack is not healthy.
|
|
147
|
+
- **A health stream and a poll at once run each check once.** A stream
|
|
148
|
+
started its own full run next to an in-flight `/api/health` (or another
|
|
149
|
+
stream), doubling the shell checks' processes. It now joins the run in
|
|
150
|
+
flight, replaying what has landed and then following it.
|
|
151
|
+
- The web header's dot for a multi-service repo without an entrypoint
|
|
152
|
+
turned red when none of its checks were up yet, even with none down. It
|
|
153
|
+
is red now only when a check completed and failed.
|
|
154
|
+
|
|
155
|
+
- **The default workspace is seeded only on a genuine fresh setup.** Any
|
|
156
|
+
command that reads workspaces (`status`, `up`, `graph`, `logs`, …) seeded
|
|
157
|
+
`default` from `defaults.workspace.repos` whenever `default` was empty,
|
|
158
|
+
so moving every repo into per-project workspaces and deactivating
|
|
159
|
+
`default` got it refilled with managed entries on the next command, ready
|
|
160
|
+
to clone duplicates of repos you work on locally. Seeding now requires
|
|
161
|
+
that no workspace but `default` exists and that `default` was never
|
|
162
|
+
seeded (no `seeded_repos`). Declining the offer records that too, so it
|
|
163
|
+
is no longer asked again.
|
|
164
|
+
- **No silent seeding without a TTY.** On a fresh setup a command without
|
|
165
|
+
a TTY now prints a one-line hint to run `<cli> workspace seed` and
|
|
166
|
+
changes nothing; on a TTY the offer stays. New: `<cli>
|
|
167
|
+
workspace seed` seeds explicitly (as `<cli> init` in the root does).
|
|
168
|
+
- **Top-up no longer re-adds repos you moved or removed.** Top-up (adding
|
|
169
|
+
repos that joined the blueprint after seeding) never runs once you have
|
|
170
|
+
other workspaces, and skips a repo any workspace holds. Removing a
|
|
171
|
+
blueprint repo from `default` in the web UI records it, so a `default`
|
|
172
|
+
from before `seeded_repos` no longer gets it back on its one-time
|
|
173
|
+
migration.
|
|
174
|
+
|
|
175
|
+
### Added
|
|
176
|
+
|
|
177
|
+
- **The "What's new" pop-up shows the newest release only** (from
|
|
178
|
+
0.3.0-dev.16). A "Show all versions (N earlier)" button in its footer
|
|
179
|
+
lists every earlier version in the same pop-up, and "Show latest only"
|
|
180
|
+
collapses it again. It always opens collapsed, at the top: the scroll is
|
|
181
|
+
reset once the pop-up is shown (a reset made while it was hidden did not
|
|
182
|
+
stick, so a reopened pop-up kept its old scroll position). It is an
|
|
183
|
+
accessible modal dialog (`role="dialog"`, `aria-modal`, labelled by its
|
|
184
|
+
title). Opening moves focus to its heading, so a screen reader starts at
|
|
185
|
+
the latest notes. Tab stays inside, and closing returns focus to what had
|
|
186
|
+
it, which is the version pill after the first-visit auto-open. Each
|
|
187
|
+
version is a heading (h4) for heading navigation, and the icon buttons
|
|
188
|
+
have spoken names ("Close", "Copy v0.2.5 as Markdown").
|
|
189
|
+
- **The version pill names the running build** (`v0.2.5-dev.2`), not the
|
|
190
|
+
newest changelog heading, so a prerelease under test says what it is.
|
|
191
|
+
- **Multi-workspace blueprint: `defaults.workspaces`.** `{ <name>: {
|
|
192
|
+
repos, branch?, autostart_off?, focus? } }` in the org config. A fresh
|
|
193
|
+
setup creates each workspace with its repos (managed), activates them
|
|
194
|
+
all and focuses the one with `focus: true` (else the first). Setting
|
|
195
|
+
both `defaults.workspace` and `defaults.workspaces` is an error, as is a
|
|
196
|
+
malformed entry. See docs/distributions.md, "Workspace blueprint".
|
|
197
|
+
|
|
198
|
+
### Migrations
|
|
199
|
+
|
|
200
|
+
- None required.
|
|
201
|
+
|
|
7
202
|
## [0.2.4] - 2026-11-04
|
|
8
203
|
|
|
9
204
|
### Fixed
|
package/docs/distributions.md
CHANGED
|
@@ -126,6 +126,83 @@ is never mistaken for another repo's service. An alias that names more than
|
|
|
126
126
|
one service is an error that lists them (`"worker" is ambiguous in workspace
|
|
127
127
|
"default": one/worker, two/worker`); name one of them.
|
|
128
128
|
|
|
129
|
+
## Health checks
|
|
130
|
+
|
|
131
|
+
A service declares its health per environment under `health`, in one of
|
|
132
|
+
three types:
|
|
133
|
+
|
|
134
|
+
```yaml
|
|
135
|
+
services:
|
|
136
|
+
api:
|
|
137
|
+
health:
|
|
138
|
+
local: { url: http://shop-api/healthz } # http (the default type)
|
|
139
|
+
db:
|
|
140
|
+
health:
|
|
141
|
+
local: { type: shell, cmd: pg_isready -h localhost } # exit 0 is up
|
|
142
|
+
cache:
|
|
143
|
+
health:
|
|
144
|
+
local: { type: container, project: shop-main, service: cache }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`label` (optional, any type) is shown in the UI instead of the URL or the
|
|
148
|
+
command. A compose repo with no declared check gets Den's own stack check
|
|
149
|
+
(`<cli> stack health`), answered in-process.
|
|
150
|
+
|
|
151
|
+
**`type: container`** asks whether a container is up, from the one
|
|
152
|
+
`docker ps` listing a health cycle already makes for the compose checks: it
|
|
153
|
+
adds no docker call however many container checks there are, in the web
|
|
154
|
+
health row and in `<cli> status`. It matches a compose container by its
|
|
155
|
+
`com.docker.compose.project` and `com.docker.compose.service` labels
|
|
156
|
+
(`project` + `service`), or any container by name (`container_name`, for one
|
|
157
|
+
compose does not run). One or the other, never both:
|
|
158
|
+
|
|
159
|
+
| What the listing shows | Verdict |
|
|
160
|
+
| ----------------------------------------- | ------------------------------------------------------- |
|
|
161
|
+
| running, healthcheck `healthy` | up |
|
|
162
|
+
| running, healthcheck `unhealthy` | down |
|
|
163
|
+
| running, healthcheck `starting` | pending |
|
|
164
|
+
| running, no healthcheck | up |
|
|
165
|
+
| not running (exited, created, restarting) | down |
|
|
166
|
+
| no matching container | down (`<project>/<service>: no container`) |
|
|
167
|
+
| `docker ps` fails (daemon down) | unknown (`ok: null`, the reason in `error`), never down |
|
|
168
|
+
| `docker ps` past its 5s budget | pending (`ok: null`, `pending: true`) |
|
|
169
|
+
| more than one match | the best one: healthy, then running |
|
|
170
|
+
|
|
171
|
+
When docker gives no listing, a container check says what a compose check
|
|
172
|
+
says, through the same code: docker state unknown is not "not running", so
|
|
173
|
+
a failed `docker ps` is `ok: null` however many cycles it lasts, in the
|
|
174
|
+
health row and in `<cli> status`. Only a listing that runs past its budget
|
|
175
|
+
is `pending`, and like any check with no answer it is down when that
|
|
176
|
+
repeats on the next cycle. A container whose healthcheck is still starting
|
|
177
|
+
stays pending. In
|
|
178
|
+
`/api/health` the row's `check` reads `container <project>/<service>` (or
|
|
179
|
+
`container <name>`).
|
|
180
|
+
|
|
181
|
+
Before, a container check was a shell check that ran docker itself, one
|
|
182
|
+
process and one docker call per check per cycle:
|
|
183
|
+
|
|
184
|
+
```yaml
|
|
185
|
+
health:
|
|
186
|
+
local:
|
|
187
|
+
type: shell
|
|
188
|
+
cmd: test -n "$(docker ps -q --filter label=com.docker.compose.project=shop-main --filter label=com.docker.compose.service=cache)"
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
After:
|
|
192
|
+
|
|
193
|
+
```yaml
|
|
194
|
+
health:
|
|
195
|
+
local: { type: container, project: shop-main, service: cache }
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
which also reads the container's own healthcheck (the shell form only saw
|
|
199
|
+
that a container existed, running or not).
|
|
200
|
+
|
|
201
|
+
**Validation.** `<cli> validate` rejects a container check with neither
|
|
202
|
+
`project` + `service` nor `container_name`, with only one of `project` and
|
|
203
|
+
`service`, with both forms at once, or with a key of another type (`cmd`,
|
|
204
|
+
`url`), and says which.
|
|
205
|
+
|
|
129
206
|
## Placement
|
|
130
207
|
|
|
131
208
|
Each service says where it runs, per environment: `local` (on this machine,
|
|
@@ -327,6 +404,54 @@ brand:
|
|
|
327
404
|
Runtime names (env vars, files, directories) always come from `den.yml`,
|
|
328
405
|
never from the brand.
|
|
329
406
|
|
|
407
|
+
## Workspace blueprint
|
|
408
|
+
|
|
409
|
+
The workspaces a new machine starts with. Read from `defaults:` in the org
|
|
410
|
+
config. Every repo is seeded as a managed clone (`mode: managed`) on the
|
|
411
|
+
blueprint's branch; nothing is cloned until a command needs it. Switch a repo
|
|
412
|
+
to a local checkout afterwards with `acme use-local <repo>`.
|
|
413
|
+
|
|
414
|
+
One workspace, `default`:
|
|
415
|
+
|
|
416
|
+
```yaml
|
|
417
|
+
defaults:
|
|
418
|
+
workspace:
|
|
419
|
+
branch: main # default: main
|
|
420
|
+
repos: [shop-web, shop, infra-stack]
|
|
421
|
+
autostart_off: [shop] # seeded with autostart: false
|
|
422
|
+
entry: shop-web # where the setup script's dependency walk starts
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
Several named workspaces:
|
|
426
|
+
|
|
427
|
+
```yaml
|
|
428
|
+
defaults:
|
|
429
|
+
workspaces:
|
|
430
|
+
storefront:
|
|
431
|
+
repos: [shop-web, shop]
|
|
432
|
+
data:
|
|
433
|
+
repos: [warehouse, infra-stack]
|
|
434
|
+
branch: develop
|
|
435
|
+
focus: true
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Each entry takes `repos`, `branch` and `autostart_off`, the same fields as
|
|
439
|
+
`defaults.workspace`, plus `focus`. Seeding creates every named workspace
|
|
440
|
+
with its repos, activates them all, and focuses the one with `focus: true`
|
|
441
|
+
(else the first). Set `defaults.workspace` or `defaults.workspaces`, not both:
|
|
442
|
+
both at once is an error, as is more than one `focus: true`.
|
|
443
|
+
|
|
444
|
+
The blueprint applies only to a genuine fresh setup: no workspace except an
|
|
445
|
+
empty `default` that was never seeded. On a TTY the first command that needs
|
|
446
|
+
workspaces offers to seed. Without a TTY it prints a one-line hint and changes
|
|
447
|
+
nothing. `acme workspace seed` (or `acme init` in the distribution root) seeds
|
|
448
|
+
explicitly, and resets the blueprint's workspaces if they already hold repos.
|
|
449
|
+
|
|
450
|
+
Once seeded, a repo that later joins `defaults.workspace.repos` is added to
|
|
451
|
+
`default`, but only while `default` is your only workspace, and never a repo
|
|
452
|
+
you removed from it or that another workspace holds. With
|
|
453
|
+
`defaults.workspaces` nothing is topped up.
|
|
454
|
+
|
|
330
455
|
## The bin shim
|
|
331
456
|
|
|
332
457
|
`bin/<cli>` is three lines of sh: it names the distribution root and runs
|
|
@@ -401,6 +526,7 @@ it exits:
|
|
|
401
526
|
|
|
402
527
|
```bash
|
|
403
528
|
./bin/acme service install # install (or refresh) and start it
|
|
529
|
+
./bin/acme service install --plan # what install would do; writes and loads nothing
|
|
404
530
|
./bin/acme service status # launchd's view: state, pid, last exit code
|
|
405
531
|
./bin/acme service print # the LaunchAgent plist it would install; installs nothing
|
|
406
532
|
./bin/acme service uninstall # stop it and remove the LaunchAgent
|
|
@@ -429,6 +555,15 @@ answers empty for a few seconds while it starts). If it has not answered
|
|
|
429
555
|
within 30s, it says `installed but not answering yet: check <logs dir>` and
|
|
430
556
|
exits 1.
|
|
431
557
|
|
|
558
|
+
`install --plan` is read-only: it prints the label, the plist path
|
|
559
|
+
(`~/Library/LaunchAgents/<label>.plist`), the log file, the distribution and
|
|
560
|
+
engine roots, whose server answers on `web.port` (this distribution's, free,
|
|
561
|
+
or `port <N> is in use by <product> at <root>; not this distribution's
|
|
562
|
+
server`), and the plist install would write. It writes nothing, never calls
|
|
563
|
+
launchctl, and exits 1 when the port is another distribution's. Any other
|
|
564
|
+
argument to a `service` subcommand is an error, so a mistyped `--plan` never
|
|
565
|
+
installs. `status` also names another distribution's server on the port.
|
|
566
|
+
|
|
432
567
|
`<cli> service` runs the installer script below, which you can also run
|
|
433
568
|
yourself, from the distribution root:
|
|
434
569
|
|
|
@@ -461,6 +596,13 @@ set of guards. It never starts the web server; if it is not running, every
|
|
|
461
596
|
tool answers with the command that starts it (`./bin/acme web up`). stdout
|
|
462
597
|
carries only the protocol; diagnostics go to stderr.
|
|
463
598
|
|
|
599
|
+
It reads only from this distribution's server. Before serving a tool it
|
|
600
|
+
checks `/api/server-info` (root and product, or for a server from before
|
|
601
|
+
0.2.6 its working directory), trusts a positive answer for 30s, and checks
|
|
602
|
+
again after a connection error. When another distribution's server answers
|
|
603
|
+
on the port, every tool says `port <N> is in use by <product> at <root>; not
|
|
604
|
+
this distribution's server` and returns none of its data.
|
|
605
|
+
|
|
464
606
|
The web server keeps running the Den it started with until it is restarted,
|
|
465
607
|
so after an upgrade it can be older than the MCP server. When a tool's route
|
|
466
608
|
answers 404, the MCP server reads the web server's version from
|
|
@@ -544,22 +686,22 @@ guard off.
|
|
|
544
686
|
|
|
545
687
|
Read tools (always registered):
|
|
546
688
|
|
|
547
|
-
| Tool | Web API | What
|
|
548
|
-
| ------------------- | ------------------------------------------ |
|
|
549
|
-
| `status` | `/api/health` | Service health; `url` (HTTP check) or `check` (shell)
|
|
550
|
-
| `services` | `/api/services` | Services, their mode (managed or dev-managed) and commands
|
|
551
|
-
| `applications` | `/api/applications` | Org > Application > Repo > Services
|
|
552
|
-
| `graph` | `/api/graph` | The derived dependency graph and startup order
|
|
553
|
-
| `workspaces` | `/api/workspaces` | Workspaces, active and focused
|
|
554
|
-
| `environments` | `/api/environments` | The enabled environment names (never their values)
|
|
555
|
-
| `logs_search` | `/api/logs/search` | Search services' full log history
|
|
556
|
-
| `logs_tail` | `/api/service/taillog` | The last lines of a service's log (bounded, no follow)
|
|
557
|
-
| `tests_available` | `/api/modules`, `/api/test-sources` | A pillar's modules and the repos they come from
|
|
558
|
-
| `security_latest` | `/api/security/latest` | The newest security scan per mode
|
|
559
|
-
| `release_status` | `/api/release/runs`, `/api/release/status` | Recent CD runs, and an env's deployed-version table
|
|
560
|
-
| `manifest_validate` | `/api/manifest/validate` | Validate a manifest by path or content
|
|
561
|
-
| `stacks` | `/api/stacks` | What `stack ls/ps/health` show: project, routes, health
|
|
562
|
-
| `edge_status` | `/api/edge` | The edge: up or down, its routes and cert names
|
|
689
|
+
| Tool | Web API | What |
|
|
690
|
+
| ------------------- | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
691
|
+
| `status` | `/api/health` | Service health; `url` (HTTP check) or `check` (shell, or `container <project>/<service>`); `ok` true up, false down, null not known (`pending: true` when a check has not answered) |
|
|
692
|
+
| `services` | `/api/services` | Services, their mode (managed or dev-managed) and commands |
|
|
693
|
+
| `applications` | `/api/applications` | Org > Application > Repo > Services |
|
|
694
|
+
| `graph` | `/api/graph` | The derived dependency graph and startup order |
|
|
695
|
+
| `workspaces` | `/api/workspaces` | Workspaces, active and focused |
|
|
696
|
+
| `environments` | `/api/environments` | The enabled environment names (never their values) |
|
|
697
|
+
| `logs_search` | `/api/logs/search` | Search services' full log history |
|
|
698
|
+
| `logs_tail` | `/api/service/taillog` | The last lines of a service's log (bounded, no follow) |
|
|
699
|
+
| `tests_available` | `/api/modules`, `/api/test-sources` | A pillar's modules and the repos they come from |
|
|
700
|
+
| `security_latest` | `/api/security/latest` | The newest security scan per mode |
|
|
701
|
+
| `release_status` | `/api/release/runs`, `/api/release/status` | Recent CD runs, and an env's deployed-version table |
|
|
702
|
+
| `manifest_validate` | `/api/manifest/validate` | Validate a manifest by path or content |
|
|
703
|
+
| `stacks` | `/api/stacks` | What `stack ls/ps/health` show: project, routes, health |
|
|
704
|
+
| `edge_status` | `/api/edge` | The edge: up or down, its routes and cert names |
|
|
563
705
|
|
|
564
706
|
Action tools (only those named in `mcp.actions`): `service_up`,
|
|
565
707
|
`service_down`, `service_restart` (through `/api/service/exec`),
|
package/package.json
CHANGED
|
@@ -107,7 +107,7 @@ export function autoSecurityTargets(
|
|
|
107
107
|
if (!runsLocally) continue;
|
|
108
108
|
const hosts: SecurityServiceConfig["hosts"] = {};
|
|
109
109
|
for (const [env, h] of Object.entries(s.config?.health ?? {})) {
|
|
110
|
-
if (!h ||
|
|
110
|
+
if (!h || !("url" in h) || !/^https?:\/\//.test(h.url)) continue;
|
|
111
111
|
hosts[env as keyof typeof hosts] = [new URL(h.url).origin + "/"];
|
|
112
112
|
}
|
|
113
113
|
if (Object.keys(hosts).length === 0) continue;
|
|
@@ -11,6 +11,7 @@ import { discoverRepos, resolveRepoPath, resolveService } from "./discover.js";
|
|
|
11
11
|
import { stageSmokeTree } from "./staging.js";
|
|
12
12
|
import type { Environment } from "../../shared/types/index.js";
|
|
13
13
|
import { getDistRoot } from "../../shared/paths.js";
|
|
14
|
+
import { plural } from "../../shared/plural.js";
|
|
14
15
|
|
|
15
16
|
// Den-root anchored, NOT process.cwd() — bulk writer of .smoke-run (see setup.ts).
|
|
16
17
|
const PROJECT_ROOT = getDistRoot();
|
|
@@ -116,7 +117,7 @@ async function main() {
|
|
|
116
117
|
);
|
|
117
118
|
|
|
118
119
|
console.log(
|
|
119
|
-
`\n Total: ${total
|
|
120
|
+
`\n Total: ${plural(total, "smoke test file")} staged across ${plural(repos.length, "repo")}\n`,
|
|
120
121
|
);
|
|
121
122
|
}
|
|
122
123
|
|
|
@@ -17,6 +17,7 @@ import type { Environment } from "../shared/types/index.js";
|
|
|
17
17
|
import { getDistRoot } from "../shared/paths.js";
|
|
18
18
|
|
|
19
19
|
import { product } from "../shared/product.js";
|
|
20
|
+
import { plural } from "../shared/plural.js";
|
|
20
21
|
// Den-root anchored: WRITES config/local/aws-profiles.json, and the reader
|
|
21
22
|
// (shared/environment/aws-profiles.ts) already anchors at getDistRoot().
|
|
22
23
|
const ROOT = getDistRoot();
|
|
@@ -54,7 +55,7 @@ async function main() {
|
|
|
54
55
|
);
|
|
55
56
|
} else {
|
|
56
57
|
console.log(
|
|
57
|
-
` Found ${profiles.length
|
|
58
|
+
` Found ${plural(profiles.length, "profile")}: ${profiles.join(", ")}\n`,
|
|
58
59
|
);
|
|
59
60
|
}
|
|
60
61
|
|
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
} from "../shared/environment-manager/default-workspace.js";
|
|
30
30
|
|
|
31
31
|
import { product } from "../shared/product.js";
|
|
32
|
+
import { plural } from "../shared/plural.js";
|
|
32
33
|
// Anchored to the den root, NOT process.cwd() — this script WRITES
|
|
33
34
|
// config/local/* and a cwd-derived root once put den files in another repo.
|
|
34
35
|
const ROOT = getDistRoot();
|
|
@@ -179,7 +180,7 @@ async function legacyScanAndSelect(): Promise<void> {
|
|
|
179
180
|
fs.readFileSync(REGISTERED_FILE, "utf-8"),
|
|
180
181
|
) as string[];
|
|
181
182
|
console.log(
|
|
182
|
-
` Current: ${existing.length
|
|
183
|
+
` Current: ${plural(existing.length, "repo")} registered for testing\n`,
|
|
183
184
|
);
|
|
184
185
|
} catch {
|
|
185
186
|
/* corrupt file — start fresh */
|
|
@@ -223,7 +224,7 @@ async function legacyScanAndSelect(): Promise<void> {
|
|
|
223
224
|
fs.writeFileSync(REGISTERED_FILE, JSON.stringify(selected, null, 2) + "\n");
|
|
224
225
|
|
|
225
226
|
console.log(
|
|
226
|
-
`\n Registered ${selected.length
|
|
227
|
+
`\n Registered ${plural(selected.length, "repo")} → config/local/registered-repos.json`,
|
|
227
228
|
);
|
|
228
229
|
console.log(" Run `make register-repos` again at any time to update.\n");
|
|
229
230
|
}
|
|
@@ -282,7 +283,7 @@ async function main(): Promise<void> {
|
|
|
282
283
|
console.log(
|
|
283
284
|
" Local Dev is enabled but no workspace exists yet. Your first" +
|
|
284
285
|
` \`${product().cli}\` command\n seeds the default workspace with every standard` +
|
|
285
|
-
` system repo, all managed\n out of the box (${plan.repos.length
|
|
286
|
+
` system repo, all managed\n out of the box (${plural(plan.repos.length, "repo")}):\n`,
|
|
286
287
|
);
|
|
287
288
|
console.log(` ${plan.repos.join(", ")}\n`);
|
|
288
289
|
console.log(
|
package/src/shared/constants.ts
CHANGED
|
@@ -48,6 +48,13 @@ export const HOSTED_HEALTH_CHECK_TIMEOUT_MS = 10_000;
|
|
|
48
48
|
/** Timeout for spawning short-lived CLI subprocesses (open, sudo, git). */
|
|
49
49
|
export const CLI_SPAWN_TIMEOUT_MS = 5_000;
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Exit code of `<cli> stack health` when it could not tell (docker did not
|
|
53
|
+
* answer, the stack config could not be read): the check is unknown, not
|
|
54
|
+
* down. Any other non-zero exit means the containers are not healthy.
|
|
55
|
+
*/
|
|
56
|
+
export const STACK_HEALTH_UNKNOWN_EXIT = 3;
|
|
57
|
+
|
|
51
58
|
/** Timeout for `gh api` calls (branch listing, repo search). */
|
|
52
59
|
export const GH_API_TIMEOUT_MS = 15_000;
|
|
53
60
|
|