@codeyam-editor/codeyam-editor 0.1.1 → 0.1.3
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/npm/cli.js +5 -2
- package/npm/cloud-branch-switch.js +442 -0
- package/npm/cloud.js +1929 -0
- package/npm/container.js +22 -3
- package/npm/editor-dev.js +368 -37
- package/npm/editor.js +133 -52
- package/npm/empty-project-dir.js +33 -0
- package/npm/fleet-auth.js +111 -0
- package/npm/fleet-branch.js +49 -0
- package/npm/fleet-broker-death.js +44 -0
- package/npm/fleet-broker-debounce.js +63 -0
- package/npm/fleet-broker-status.js +45 -0
- package/npm/fleet-cleanup.js +297 -0
- package/npm/fleet-effect-status.js +179 -0
- package/npm/fleet-env.js +125 -0
- package/npm/fleet-grouping.js +56 -0
- package/npm/fleet-job-badge.js +39 -0
- package/npm/fleet-memory.js +157 -0
- package/npm/fleet-pool-policy.js +78 -0
- package/npm/fleet-pool.js +401 -0
- package/npm/fleet-queue.js +40 -0
- package/npm/fleet-reconcile.js +36 -0
- package/npm/fleet-reset.js +222 -0
- package/npm/fleet-roster.js +81 -0
- package/npm/launch-config.js +622 -0
- package/npm/preflight-guidance.js +77 -0
- package/npm/scenario-check.js +323 -14
- package/npm/scenario-handlers.js +16 -5
- package/npm/scenario-interactivity.js +245 -0
- package/npm/scenario-metrics.js +16 -2
- package/npm/scenario-mocks.js +62 -25
- package/npm/scenario-playwright.js +350 -8
- package/npm/utils.js +562 -22
- package/npm/vm-urls.js +117 -0
- package/package.json +8 -7
- package/ui/dist/assets/ContainerInspector-C8EkH4PP.js +2 -0
- package/ui/dist/assets/EditorShell-CGsBSA6u.js +186 -0
- package/ui/dist/assets/Markdown-BHBqz-Mj.js +1 -0
- package/ui/dist/assets/ScenarioDataPanel-DYP7pi_n.css +32 -0
- package/ui/dist/assets/ScenarioDataPanel-nuLXJzLf.js +5 -0
- package/ui/dist/assets/ScenarioDataPanelFullPage-Bnghtj8a.js +1 -0
- package/ui/dist/assets/ScenarioRenderer-B_Za9jL8.js +1 -0
- package/ui/dist/assets/StepsSimulator-CRtmlyNZ.js +1 -0
- package/ui/dist/assets/index-Bih6HJ_u.css +1 -0
- package/ui/dist/assets/index-KDFAgt2e.js +27 -0
- package/ui/dist/assets/markdown-v0F0UEt3.js +37 -0
- package/ui/dist/assets/react-CSS0HapR.js +28 -0
- package/ui/dist/assets/useEvents-DggWrtHe.js +1 -0
- package/ui/dist/assets/xterm--24IGk-x.js +9 -0
- package/ui/dist/favicon.ico +0 -0
- package/ui/dist/favicon.png +0 -0
- package/ui/dist/index.html +16 -0
- package/npm/postinstall.js +0 -132
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Pure helpers for the fleet dashboard's commit-queue auto-release logic
|
|
3
|
+
// (scripts/operator/fleet-dashboard/server.js requires this). The decision
|
|
4
|
+
// of whether to auto-release a queue head lives here so server.js's
|
|
5
|
+
// orchestration stays thin and this piece can be unit-tested in isolation
|
|
6
|
+
// (npm/fleet-queue.test.js).
|
|
7
|
+
//
|
|
8
|
+
// Auto-release fires only on the deterministic "owning VM left the fleet"
|
|
9
|
+
// signal — a VM that is absent from the roster AND has no in-flight
|
|
10
|
+
// provisioning job. The ambiguous "in roster but unreachable" case is
|
|
11
|
+
// explicitly out of scope: a transient blip could trigger a spurious
|
|
12
|
+
// release. That case is surfaced as a badge for the operator to act on.
|
|
13
|
+
|
|
14
|
+
/** Decide whether the current queue head should be auto-released.
|
|
15
|
+
*
|
|
16
|
+
* Conditions that must ALL hold:
|
|
17
|
+
* 1. The head entry's `vm` field is non-null (the machine name matched the
|
|
18
|
+
* fleet's INSTANCE_PREFIX-…-<n> shape via vmFromMachine). An
|
|
19
|
+
* unattributable head (vm===null) is never auto-released.
|
|
20
|
+
* 2. That VM number is absent from the live `vms` roster array.
|
|
21
|
+
* 3. There is no in-flight or queued provisioning job for that number
|
|
22
|
+
* (guards the Add→addVm gap where a VM is being created but not yet
|
|
23
|
+
* rostered). Also guards known-orphan VM numbers.
|
|
24
|
+
*
|
|
25
|
+
* Returns { release: false } or { release: true, uuid, vm, reason }.
|
|
26
|
+
* Pure; no I/O.
|
|
27
|
+
*/
|
|
28
|
+
function decideAutoReleaseGoneHead({ queue, vms, jobs, orphans }) {
|
|
29
|
+
const head = queue && queue[0];
|
|
30
|
+
if (!head || head.vm == null) return { release: false };
|
|
31
|
+
const n = head.vm;
|
|
32
|
+
if (vms.includes(n)) return { release: false };
|
|
33
|
+
const job = jobs[n];
|
|
34
|
+
const provisioning = job && (job.state === 'queued' || job.state === 'running')
|
|
35
|
+
&& /add|cloud:up/.test(job.action || '');
|
|
36
|
+
if (provisioning || orphans[n]) return { release: false };
|
|
37
|
+
return { release: true, uuid: head.uuid, vm: n, reason: `owning VM-${n} left the fleet` };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { decideAutoReleaseGoneHead };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// npm/fleet-reconcile.js — pure reconciliation helpers for the fleet dashboard's
|
|
4
|
+
// "unrostered VM" safety net (improve38). The dashboard lists running GCP
|
|
5
|
+
// instances and surfaces any that aren't in the roster (provisioned out-of-band
|
|
6
|
+
// — raw `gcloud`, or a `cloud:up` whose roster write failed). The gcloud I/O
|
|
7
|
+
// lives in server.js (the untestable server shell); the pure parse + set
|
|
8
|
+
// difference live here so they're unit-tested without invoking gcloud.
|
|
9
|
+
|
|
10
|
+
// Parse `gcloud compute instances list --format='value(name)'` output into the
|
|
11
|
+
// sorted, de-duped fleet VM numbers it names — instances whose name matches
|
|
12
|
+
// `<prefix><n>`. Non-matching lines (other instances, blank lines, gcloud/SSH
|
|
13
|
+
// warning noise) are ignored.
|
|
14
|
+
function parseRunningFleetVms(stdout, prefix) {
|
|
15
|
+
const re = new RegExp(`^${String(prefix).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(\\d+)$`);
|
|
16
|
+
const ns = String(stdout || '')
|
|
17
|
+
.split('\n')
|
|
18
|
+
.map((line) => {
|
|
19
|
+
const m = line.trim().match(re);
|
|
20
|
+
return m ? Number(m[1]) : null;
|
|
21
|
+
})
|
|
22
|
+
.filter((n) => n != null);
|
|
23
|
+
return [...new Set(ns)].sort((a, b) => a - b);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Set difference: the `running` VM numbers that are NOT already accounted for in
|
|
27
|
+
// `shown` (the roster plus any provisioning/orphan numbers already rendered as a
|
|
28
|
+
// card). Returns a sorted, de-duped array — the numbers that need an Adopt card.
|
|
29
|
+
function computeUnrostered(running, shown) {
|
|
30
|
+
const accounted = new Set(shown);
|
|
31
|
+
return [...new Set(running)]
|
|
32
|
+
.filter((n) => !accounted.has(n))
|
|
33
|
+
.sort((a, b) => a - b);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = { parseRunningFleetVms, computeUnrostered };
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
// Pure bash-script builders for the fleet dashboard's RESET actions
|
|
3
|
+
// (scripts/operator/fleet-dashboard/server.js requires this). String
|
|
4
|
+
// construction only — no I/O, no gcloud/docker exec — so the reset *recipe*
|
|
5
|
+
// (what the remote bash actually does) stays unit-testable in isolation
|
|
6
|
+
// (npm/fleet-reset.test.js), exactly like launch-config.js / fleet-env.js.
|
|
7
|
+
// The dispatch (gcloud ssh → docker exec → startJob) stays in server.js as
|
|
8
|
+
// platform-glue; only the decision-bearing script shape lives here.
|
|
9
|
+
|
|
10
|
+
// The `.codeyam` deep-clean preserve→pause→wipe→restore sequence, shared by every
|
|
11
|
+
// reset that hard-cleans a /workspace. `git reset --hard` + `session-reset`
|
|
12
|
+
// leave the git-IGNORED runtime state behind (workflow step files,
|
|
13
|
+
// scenario-*.js, server-state, logs, caches), so we explicitly wipe and
|
|
14
|
+
// restore the tracked `.codeyam`. Two gitignored files MUST survive the wipe:
|
|
15
|
+
// - editor.local.json — previewOrigin + the agent API key (a blind wipe
|
|
16
|
+
// breaks Live Preview + auth).
|
|
17
|
+
// - server-state.json — the running pid + control port the restart's verify
|
|
18
|
+
// reads.
|
|
19
|
+
// Both are cp'd aside, `.codeyam` is wiped, restored from the branch (tracked,
|
|
20
|
+
// e.g. self-host) or re-`init`'d (greenfield, where `.codeyam` is gitignored),
|
|
21
|
+
// then the two files are copied back. Returned as step strings the caller
|
|
22
|
+
// joins into its own `&&`-chain (the surrounding `( …; true )` guards keep each
|
|
23
|
+
// best-effort cp from breaking the chain when a file is absent).
|
|
24
|
+
//
|
|
25
|
+
// First step: release any commit-queue entries this VM owns. The wipe
|
|
26
|
+
// regenerates `.codeyam/run/queue-origin-id`, which would otherwise orphan
|
|
27
|
+
// the prior session's entry (different origin_id, same machine — invisible
|
|
28
|
+
// to the two-key adoption path until the next enqueue's same-machine guard
|
|
29
|
+
// reaps it). Routing through `branch-queue release-mine` makes the
|
|
30
|
+
// queue-side cleanup intentional and visible in the queue log instead.
|
|
31
|
+
// Best-effort: a no-remote / queue-disabled project legitimately exits
|
|
32
|
+
// non-zero, and we should not block the reset.
|
|
33
|
+
//
|
|
34
|
+
// Before the wipe we QUIESCE ALL WRITERS: park the editor server
|
|
35
|
+
// (`stop-server --hold-for-restart`) AND stop the PTY broker
|
|
36
|
+
// (`pty-broker stop`). Parking only the editor server is insufficient — the
|
|
37
|
+
// broker is a SEPARATE process (and hooks / agent subprocesses can fire), so
|
|
38
|
+
// it keeps re-creating `.codeyam/logs/` and `.codeyam/run/` between when the
|
|
39
|
+
// wipe enumerates the dir and when it removes it. That race surfaced live
|
|
40
|
+
// (VM-1, 2026-06-01) as `rm: cannot remove '.codeyam': Directory not empty`,
|
|
41
|
+
// aborting the whole reset. Both quiesce calls are fail-soft `|| true` so a
|
|
42
|
+
// server/broker that's already dead (e.g. the reset is rerun after a partial
|
|
43
|
+
// failure) doesn't block the chain.
|
|
44
|
+
//
|
|
45
|
+
// The wipe itself is RETRY-RESILIENT instead of a single `rm -rf .codeyam`
|
|
46
|
+
// that aborts the &&-chain on one transient re-creation: a bounded loop of
|
|
47
|
+
// `find .codeyam -mindepth 1 -delete` empties the contents and tolerates a
|
|
48
|
+
// brief re-create, then proceeds even if `.codeyam` is still non-empty after
|
|
49
|
+
// best-effort (a soft retry, never a hard failure — `git checkout`/`init`
|
|
50
|
+
// repopulates over whatever remains). Emptying contents (not removing the dir)
|
|
51
|
+
// also sidesteps the `rmdir` race entirely.
|
|
52
|
+
//
|
|
53
|
+
// The destructive region (park → broker-stop → wipe → restore) is made
|
|
54
|
+
// FAILURE-SAFE with an EXIT trap installed before the park and disarmed after
|
|
55
|
+
// a successful restore. If ANY step in that region aborts the chain, the trap
|
|
56
|
+
// still restores tracked `.codeyam` (or re-inits), copies the two preserved
|
|
57
|
+
// infra files back, and runs `restart-server --force-in-container` before
|
|
58
|
+
// exiting non-zero — so a failed deep-clean self-recovers to a working
|
|
59
|
+
// (un-reset, but UP) VM instead of leaving it parked-and-down with a
|
|
60
|
+
// half-wiped `.codeyam`, and still reports the failure. The happy path is
|
|
61
|
+
// unchanged: the restore steps run, the trap is disarmed, and the caller's
|
|
62
|
+
// own trailing `restart-server` resurrects the parked server.
|
|
63
|
+
function codeyamDeepCleanSteps(E) {
|
|
64
|
+
// Restore primitives shared by the happy path and the trap recovery body.
|
|
65
|
+
const restoreCodeyam = `git checkout HEAD -- .codeyam 2>/dev/null || ${E} init`;
|
|
66
|
+
const restoreElj =
|
|
67
|
+
'test -f /tmp/cy-elj.bak && mkdir -p .codeyam && cp /tmp/cy-elj.bak .codeyam/editor.local.json';
|
|
68
|
+
const restoreSs =
|
|
69
|
+
'test -f /tmp/cy-ss.bak && mkdir -p .codeyam && cp /tmp/cy-ss.bak .codeyam/server-state.json';
|
|
70
|
+
// Recovery body: restore .codeyam + the two infra backups, then resurrect the
|
|
71
|
+
// parked server. Runs ONLY on the failure path (the happy path's own restore
|
|
72
|
+
// steps remove the backups; recovery here intentionally leaves them in place
|
|
73
|
+
// because on a mid-region abort those steps never ran).
|
|
74
|
+
const recover =
|
|
75
|
+
`${restoreCodeyam}; ( ${restoreElj}; true ); ( ${restoreSs}; true ); ` +
|
|
76
|
+
`${E} editor restart-server --force-in-container || true`;
|
|
77
|
+
return [
|
|
78
|
+
`( ${E} editor branch-queue release-mine --yes 2>/dev/null; true )`,
|
|
79
|
+
'( cp .codeyam/editor.local.json /tmp/cy-elj.bak 2>/dev/null; true )',
|
|
80
|
+
'( cp .codeyam/server-state.json /tmp/cy-ss.bak 2>/dev/null; true )',
|
|
81
|
+
// A brace group (NOT a subshell) so the function + trap persist into the
|
|
82
|
+
// current shell where the rest of the &&-chain runs.
|
|
83
|
+
`{ __cy_recover() { ${recover}; }; trap '__cy_recover; exit 1' EXIT; }`,
|
|
84
|
+
`(${E} editor stop-server --hold-for-restart --force-in-container || true)`,
|
|
85
|
+
`(${E} editor pty-broker stop || true)`,
|
|
86
|
+
'( for i in 1 2 3 4 5; do find .codeyam -mindepth 1 -delete 2>/dev/null; [ -z "$(ls -A .codeyam 2>/dev/null)" ] && break; sleep 0.2; done; true )',
|
|
87
|
+
`( ${restoreCodeyam} )`,
|
|
88
|
+
`( ${restoreElj} && rm -f /tmp/cy-elj.bak; true )`,
|
|
89
|
+
`( ${restoreSs} && rm -f /tmp/cy-ss.bak; true )`,
|
|
90
|
+
// Restore succeeded — disarm the failure-safe trap so normal completion
|
|
91
|
+
// (and the caller's trailing session-reset + restart) doesn't re-trigger it.
|
|
92
|
+
'trap - EXIT',
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Belt-and-suspenders broker-ensure, appended after the trailing
|
|
97
|
+
// `restart-server` in every reset recipe. The PRIMARY fix lives in the
|
|
98
|
+
// editor server's `ensure_broker_running`: it now `rm -f`s the stale
|
|
99
|
+
// socket and retries the spawn, so the reexec'd `start` (which
|
|
100
|
+
// restart-server triggers) reliably respawns the broker with no
|
|
101
|
+
// down-window. This step is the safety net for an OLDER in-container
|
|
102
|
+
// binary whose `restart-server` predates that fix — it mirrors the
|
|
103
|
+
// vm-watchdog's recovery so a reset still ends broker-up regardless of
|
|
104
|
+
// which binary is installed.
|
|
105
|
+
//
|
|
106
|
+
// Shape: status-gated and idempotent. `pty-broker status` exits non-zero
|
|
107
|
+
// only when no broker is reachable; ONLY then does the `||` branch fire
|
|
108
|
+
// (`rm -f` the stale socket — mirroring the watchdog — then `nohup` a
|
|
109
|
+
// detached daemon). When a broker is already alive the status succeeds
|
|
110
|
+
// and the respawn branch never runs, so a healthy broker is NEVER killed.
|
|
111
|
+
// Wrapped in `( …; true )` so it can never break the reset's &&-chain
|
|
112
|
+
// (a down broker that fails to respawn must not abort the rest of the
|
|
113
|
+
// reset). The socket glob + `pty-broker daemon` invocation match the
|
|
114
|
+
// watchdog (`scripts/operator/vm-watchdog.sh`) and the per-project
|
|
115
|
+
// `$TMPDIR`/`/tmp` socket layout (`socket_path_for`).
|
|
116
|
+
function codeyamBrokerEnsureStep(E) {
|
|
117
|
+
return (
|
|
118
|
+
`( ${E} editor pty-broker status >/dev/null 2>&1 || ` +
|
|
119
|
+
`( rm -f /tmp/codeyam-pty-broker-*.sock; ` +
|
|
120
|
+
`nohup ${E} editor pty-broker daemon </dev/null >/tmp/broker-spawn.log 2>&1 & disown ); ` +
|
|
121
|
+
'true )'
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Build the in-container bash for "Reset client" — hard-clean the CLIENT
|
|
126
|
+
// project (/workspace) on ANY VM shape. The decisions encoded here:
|
|
127
|
+
// - Reset to HEAD, NOT origin: `git reset --hard` (bare) discards uncommitted
|
|
128
|
+
// churn but PRESERVES committed work. Advancing to origin tip is the
|
|
129
|
+
// separate "Update client" action — this must never `git fetch`/reset to
|
|
130
|
+
// `origin/<branch>`.
|
|
131
|
+
// - `git reset --hard` leaves untracked files, so node_modules + a per-VM
|
|
132
|
+
// .env survive (no forced re-install / lost secrets).
|
|
133
|
+
// - Stop the broker FIRST so abandoned agent WIP can't be re-written to disk
|
|
134
|
+
// mid-reset; drop a stale LOCAL credential.helper (fragility #29) so the
|
|
135
|
+
// re-seed + the agent's git ops fall back to the working system helper.
|
|
136
|
+
// - Re-seed the git credential cache AFTER the restart so the agent can
|
|
137
|
+
// pull/push immediately; the re-seed is a self-contained subshell whose
|
|
138
|
+
// own `|| echo` never masks an earlier &&-chain failure.
|
|
139
|
+
// `E` is the in-container editor binary; `gitReseedBody` is the re-seed bash
|
|
140
|
+
// body (passed in so this stays a pure function of its inputs).
|
|
141
|
+
function resetClientScript({ E, gitReseedBody }) {
|
|
142
|
+
return [
|
|
143
|
+
'cd /workspace',
|
|
144
|
+
`command -v ${E} >/dev/null`,
|
|
145
|
+
'echo ">> [1/5] stopping agent session"',
|
|
146
|
+
`(${E} editor pty-broker stop || true)`,
|
|
147
|
+
'(git config --local --unset-all credential.helper || true)',
|
|
148
|
+
'echo ">> [2/5] discarding uncommitted changes (git reset --hard to HEAD; keeps committed work + untracked files)"',
|
|
149
|
+
'git reset --hard',
|
|
150
|
+
'echo ">> [3/5] deep-cleaning .codeyam (clears ignored runtime state; keeps editor.local.json + server-state.json)"',
|
|
151
|
+
...codeyamDeepCleanSteps(E),
|
|
152
|
+
`${E} editor session-reset`,
|
|
153
|
+
'echo ">> [4/5] restarting server to respawn the broker (no rebuild)"',
|
|
154
|
+
`(${E} editor restart-server --force-in-container || true)`,
|
|
155
|
+
// Belt-and-suspenders: ensure a broker is live even on an older binary
|
|
156
|
+
// whose restart-server doesn't yet respawn it. Idempotent / status-gated.
|
|
157
|
+
codeyamBrokerEnsureStep(E),
|
|
158
|
+
'echo ">> [5/5] re-seeding git credential cache"',
|
|
159
|
+
`( ( ${gitReseedBody} ) || echo "WARN git cache re-seed failed" )`,
|
|
160
|
+
].join(' && ');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Build the in-container bash for "Reset editor" — the editor-state recipe:
|
|
164
|
+
// stop the agent, drop a stale local credential.helper, advance the VM's branch
|
|
165
|
+
// to origin tip (skipped on a greenfield local-only repo), deep-clean .codeyam
|
|
166
|
+
// (preserving editor.local.json + server-state.json via codeyamDeepCleanSteps),
|
|
167
|
+
// run session-reset, and restart the server. This is the SAME &&-chain
|
|
168
|
+
// previously inlined in actionResetEditorSource in scripts/operator/
|
|
169
|
+
// fleet-dashboard/server.js — extracted so the standalone Reset editor action
|
|
170
|
+
// AND the composite Update & Reset Everything action share one definition and
|
|
171
|
+
// it gets unit-test coverage like the other reset builders.
|
|
172
|
+
//
|
|
173
|
+
// Binary-existence gate FIRST so a broken binary aborts BEFORE the destructive
|
|
174
|
+
// `git reset --hard` / `rm -rf .codeyam`. The composite action relies on this
|
|
175
|
+
// same gate as its safety contract — a failed rebuild that leaves no binary
|
|
176
|
+
// will short-circuit the trailing recipe before any wipe happens.
|
|
177
|
+
function resetEditorStateScript({ E }) {
|
|
178
|
+
return [
|
|
179
|
+
'cd /workspace',
|
|
180
|
+
`command -v ${E} >/dev/null`,
|
|
181
|
+
// Tolerate a missing broker: `pty-broker stop` exits non-zero when none is
|
|
182
|
+
// running; the subshell + `|| true` keeps it non-fatal without swallowing
|
|
183
|
+
// the binary-existence gate above.
|
|
184
|
+
'echo ">> [1/4] stopping agent session + clearing workflow state"',
|
|
185
|
+
`(${E} editor pty-broker stop || true)`,
|
|
186
|
+
// Drop a stale LOCAL credential.helper (fragility #29) so `git fetch` falls
|
|
187
|
+
// back to the working system helper. No-op when none is set.
|
|
188
|
+
'(git config --local --unset-all credential.helper || true)',
|
|
189
|
+
// Advance the VM's own branch to origin (skip on a greenfield local-only repo
|
|
190
|
+
// with no remote). fetch + `reset --hard origin/<branch>`, NOT `pull --ff-only`
|
|
191
|
+
// (which dies "Cannot fast-forward to multiple branches" on some VM states).
|
|
192
|
+
'if git remote | grep -qx origin; then ' +
|
|
193
|
+
'BR=$(git branch --show-current); ' +
|
|
194
|
+
'echo ">> [2/4] advancing source on $BR to origin tip"; ' +
|
|
195
|
+
'git reset --hard && git fetch origin "$BR" && git reset --hard "origin/$BR"; ' +
|
|
196
|
+
'else echo ">> [2/4] no origin remote (greenfield project) — skipping source advance"; fi',
|
|
197
|
+
// Deep-clean .codeyam: `git reset --hard` + session-reset leave git-IGNORED /
|
|
198
|
+
// untracked state (workflow + design-step state, server-state, logs, caches).
|
|
199
|
+
// Preserve editor.local.json (previewOrigin + agent API key — a blind wipe
|
|
200
|
+
// breaks Live Preview + auth) and server-state.json (runtime pid + control
|
|
201
|
+
// port the restart's verify reads), wipe .codeyam, restore it from the branch
|
|
202
|
+
// (tracked, e.g. self-host) or re-init (greenfield), then put both files back.
|
|
203
|
+
'echo ">> [3/4] deep-cleaning .codeyam (clears ignored state; keeps editor.local.json + server-state.json)"',
|
|
204
|
+
...codeyamDeepCleanSteps(E),
|
|
205
|
+
`${E} editor session-reset`,
|
|
206
|
+
// No rebuild here (that's Rebuild binary). Restart the server in place to
|
|
207
|
+
// respawn the broker we stopped: restart-server SIGUSR1s pid 1 and the
|
|
208
|
+
// docker-exec returns cleanly, so `|| true` keeps a broker-down VM green.
|
|
209
|
+
'echo ">> [4/4] restarting server to respawn the broker (no rebuild)"',
|
|
210
|
+
`(${E} editor restart-server --force-in-container || true)`,
|
|
211
|
+
// Belt-and-suspenders: ensure a broker is live even on an older binary
|
|
212
|
+
// whose restart-server doesn't yet respawn it. Idempotent / status-gated.
|
|
213
|
+
codeyamBrokerEnsureStep(E),
|
|
214
|
+
].join(' && ');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
module.exports = {
|
|
218
|
+
codeyamDeepCleanSteps,
|
|
219
|
+
codeyamBrokerEnsureStep,
|
|
220
|
+
resetClientScript,
|
|
221
|
+
resetEditorStateScript,
|
|
222
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// npm/fleet-roster.js — the fleet roster (`.codeyam/logs/fleet-vms.json`) is a
|
|
4
|
+
// shared contract: the dashboard, the CLI provisioner (`cloud:up`), the
|
|
5
|
+
// `cloud:branch-switch` orchestrator, the vm-watchdog, and the bash tunnel
|
|
6
|
+
// publishers (`vm-urls.sh` / `cloudflared-monitor.sh`) all read it to learn
|
|
7
|
+
// which VMs exist. Before improve38 the *writer* lived only inside the dashboard
|
|
8
|
+
// process (`server.js::saveVmsState`), so a VM provisioned by the CLI never
|
|
9
|
+
// appeared until someone hand-edited the file. This module is the single
|
|
10
|
+
// read+write seam every Node consumer shares, so the writer is no longer locked
|
|
11
|
+
// inside the dashboard.
|
|
12
|
+
//
|
|
13
|
+
// File format (unchanged): a JSON array of VM-number integers. Readers tolerate
|
|
14
|
+
// a missing or malformed file by treating it as the empty roster.
|
|
15
|
+
//
|
|
16
|
+
// Env override: `VMS_STATE_FILE` relocates the file (honored by server.js and
|
|
17
|
+
// cloud-branch-switch.js already); resolved once at module load.
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
|
|
22
|
+
const { rootDir } = require('./utils');
|
|
23
|
+
|
|
24
|
+
const VMS_STATE_FILE = process.env.VMS_STATE_FILE
|
|
25
|
+
|| path.join(rootDir, '.codeyam', 'logs', 'fleet-vms.json');
|
|
26
|
+
|
|
27
|
+
// Canonical roster shape: a sorted, de-duped set of integers. Every read and
|
|
28
|
+
// write funnels through here so on-disk content stays normalized regardless of
|
|
29
|
+
// which consumer last wrote it (or hand-edited it).
|
|
30
|
+
function normalizeRoster(arr) {
|
|
31
|
+
return [...new Set((arr || []).filter(Number.isInteger))].sort((a, b) => a - b);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Read the roster, returning a normalized integer array. A missing or malformed
|
|
35
|
+
// file is treated as the empty roster `[]` (the documented contract the previous
|
|
36
|
+
// `readRoster`/`loadVmsState` duplicates shared) so a truncated write or an
|
|
37
|
+
// absent file never throws into a consumer's poll loop.
|
|
38
|
+
function readRoster() {
|
|
39
|
+
try {
|
|
40
|
+
const data = JSON.parse(fs.readFileSync(VMS_STATE_FILE, 'utf8'));
|
|
41
|
+
if (Array.isArray(data)) return normalizeRoster(data);
|
|
42
|
+
} catch { /* missing or malformed — treated as empty */ }
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Persist `arr` as the roster, atomically: write a temp file then `rename` it
|
|
47
|
+
// over the target so a concurrent reader never observes a half-written file.
|
|
48
|
+
// The temp name carries the pid so two *processes* writing at once (CLI +
|
|
49
|
+
// dashboard) don't clobber each other's temp file before their renames land.
|
|
50
|
+
// Returns the normalized roster that was written.
|
|
51
|
+
function writeRoster(arr) {
|
|
52
|
+
const next = normalizeRoster(arr);
|
|
53
|
+
fs.mkdirSync(path.dirname(VMS_STATE_FILE), { recursive: true });
|
|
54
|
+
const tmp = `${VMS_STATE_FILE}.${process.pid}.tmp`;
|
|
55
|
+
fs.writeFileSync(tmp, JSON.stringify(next));
|
|
56
|
+
fs.renameSync(tmp, VMS_STATE_FILE); // atomic replace
|
|
57
|
+
return next;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Append `n` to the roster if absent (idempotent), returning the new roster.
|
|
61
|
+
// A double-roster — e.g. the CLI rosters on `cloud:up` and the dashboard's Add
|
|
62
|
+
// onSuccess rosters again — is a harmless no-op, which is exactly what closes
|
|
63
|
+
// the race where one writer's roster write could fail.
|
|
64
|
+
function addToRoster(n) {
|
|
65
|
+
return writeRoster([...readRoster(), n]);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Remove `n` from the roster, returning the new roster. Removing an absent
|
|
69
|
+
// entry is a no-op.
|
|
70
|
+
function removeFromRoster(n) {
|
|
71
|
+
return writeRoster(readRoster().filter((x) => x !== n));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = {
|
|
75
|
+
VMS_STATE_FILE,
|
|
76
|
+
normalizeRoster,
|
|
77
|
+
readRoster,
|
|
78
|
+
writeRoster,
|
|
79
|
+
addToRoster,
|
|
80
|
+
removeFromRoster,
|
|
81
|
+
};
|