@codeyam-editor/codeyam-editor 0.1.5 → 0.1.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/npm/cli.js +5 -1
- package/npm/cloud.js +1884 -210
- package/npm/codesign-plan.js +68 -0
- package/npm/empty-project-dir.js +92 -7
- package/npm/fleet-add-failure.js +252 -0
- package/npm/fleet-asset-version.js +37 -0
- package/npm/fleet-auth-health.js +143 -5
- package/npm/fleet-build-data.js +60 -5
- package/npm/fleet-cleanup.js +114 -9
- package/npm/fleet-destroy-safety.js +230 -13
- package/npm/fleet-early-roster.js +87 -0
- package/npm/fleet-effect-poll.js +110 -0
- package/npm/fleet-env.js +86 -0
- package/npm/fleet-ghcr-secret.js +129 -0
- package/npm/fleet-git-maintenance.js +142 -0
- package/npm/fleet-host-op-limiter.js +68 -0
- package/npm/fleet-iap-token.js +165 -0
- package/npm/fleet-live-status.js +40 -8
- package/npm/fleet-memory.js +245 -9
- package/npm/fleet-plans.js +98 -4
- package/npm/fleet-pool.js +75 -16
- package/npm/fleet-prewarm.js +87 -11
- package/npm/fleet-project-drift.js +58 -0
- package/npm/fleet-public-url.js +234 -0
- package/npm/fleet-queue.js +58 -1
- package/npm/fleet-reachability-debounce.js +69 -0
- package/npm/fleet-reachability.js +80 -0
- package/npm/fleet-reset.js +32 -0
- package/npm/fleet-running-plan.js +85 -0
- package/npm/fleet-session-token-cache.js +65 -0
- package/npm/fleet-setup-steps.js +19 -12
- package/npm/fleet-status-refs.js +96 -0
- package/npm/fleet-strand.js +39 -8
- package/npm/fleet-stuck-status.js +13 -7
- package/npm/fleet-vm-access.js +170 -0
- package/npm/fleet-vm-probe-target.js +282 -0
- package/npm/fleet-vm-readiness.js +136 -3
- package/npm/google-ingress.js +15 -0
- package/npm/ingress-invoke.js +478 -0
- package/npm/launch-config.js +182 -6
- package/npm/scenario-check.js +282 -10
- package/npm/scenario-handlers.js +85 -27
- package/npm/scenario-interactivity.js +145 -6
- package/npm/scenario-issues.js +13 -1
- package/npm/scenario-mocks.js +129 -26
- package/npm/scenario-playwright.js +286 -12
- package/npm/script-drift.js +72 -1
- package/npm/utils.js +37 -3
- package/package.json +5 -5
- package/ui/dist/assets/{ContainerInspector-D7VZkkaj.js → ContainerInspector-DgM0pTAx.js} +1 -1
- package/ui/dist/assets/EditorShell-B7Q06uJ_.js +252 -0
- package/ui/dist/assets/EditorShell-DP5UY20G.css +1 -0
- package/ui/dist/assets/{Markdown-BoLHHFXH.js → Markdown-Chgdf3PW.js} +1 -1
- package/ui/dist/assets/ProjectLauncherScreen-BRrEr8Ng.js +1 -0
- package/ui/dist/assets/ScenarioDataPanel-BeQfa28g.js +9 -0
- package/ui/dist/assets/{ScenarioDataPanel-DYP7pi_n.css → ScenarioDataPanel-C1LukYI6.css} +1 -1
- package/ui/dist/assets/{ScenarioDataPanelFullPage-CBLTGC4-.js → ScenarioDataPanelFullPage-BLqRCxQX.js} +1 -1
- package/ui/dist/assets/ScenarioRenderer-CIAxfHPh.js +21 -0
- package/ui/dist/assets/{StepsSimulator-BFldWyoX.js → StepsSimulator-CymGgfbu.js} +1 -1
- package/ui/dist/assets/index-BygCiJSb.css +1 -0
- package/ui/dist/assets/{index-QsOiOG9x.js → index-DCHqRrwO.js} +4 -4
- package/ui/dist/assets/{markdown-v0F0UEt3.js → markdown-C9tbsVHX.js} +1 -1
- package/ui/dist/assets/react-nrLBr15I.js +28 -0
- package/ui/dist/assets/{useEvents-DHkLJZR4.js → useEvents-S3Qk1Y_d.js} +1 -1
- package/ui/dist/assets/useServerIdentity-B7rI0u3O.js +1 -0
- package/ui/dist/index.html +4 -4
- package/ui/dist/open-source/codeyam-counter.png +0 -0
- package/ui/dist/open-source/el-carot.png +0 -0
- package/ui/dist/open-source/tabcommand.png +0 -0
- package/ui/dist/assets/EditorShell-DHhIaotd.css +0 -1
- package/ui/dist/assets/EditorShell-r-796N_W.js +0 -191
- package/ui/dist/assets/ProjectLauncherScreen-Ck-XWkQN.js +0 -1
- package/ui/dist/assets/ScenarioDataPanel-B9iDSUpA.js +0 -5
- package/ui/dist/assets/ScenarioRenderer-BhLfT-nX.js +0 -21
- package/ui/dist/assets/index-CIXZkwfB.css +0 -1
- package/ui/dist/assets/react-CSS0HapR.js +0 -28
package/npm/launch-config.js
CHANGED
|
@@ -119,6 +119,52 @@ function validateLaunchConfig(cfg) {
|
|
|
119
119
|
return { ok: true };
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
/** Pre-flight the operator-host CONFIG a dashboard Add depends on, BEFORE
|
|
123
|
+
* spawning the ~20-min cloud:up. Pure classifier: it takes the ALREADY-resolved
|
|
124
|
+
* zone, project, and GitHub token (the side-effecting resolution — env lookups,
|
|
125
|
+
* `gh auth token`, gcloud defaults — is the caller's job) and returns { ok, msg }
|
|
126
|
+
* naming the exact fix for the FIRST blocking gap. This turns a config fault into
|
|
127
|
+
* a seconds-fast, actionable add-rejection instead of a cryptic `cloud:up FAILED
|
|
128
|
+
* (exit 1)` the operator has to dig out of a 20-min job log.
|
|
129
|
+
*
|
|
130
|
+
* It guards the two config faults that silently broke every fleet add on
|
|
131
|
+
* 2026-07-20:
|
|
132
|
+
* - no resolvable compute zone → cloud:up aborts at its own --zone check in ~15s
|
|
133
|
+
* before creating any instance;
|
|
134
|
+
* - no GitHub token → the fresh VM's bootstrap dies cloning the PRIVATE editor
|
|
135
|
+
* repo at `could not read Username for 'https://github.com'`.
|
|
136
|
+
* `allowNoGitCredentials` mirrors cloud:up's --allow-no-git-credentials opt-out
|
|
137
|
+
* for an intentionally read-only VM (no push token needed). Pure; no I/O. */
|
|
138
|
+
function addPreflight({ zone, project, token, allowNoGitCredentials = false } = {}) {
|
|
139
|
+
const blank = (v) => typeof v !== "string" || v.trim() === "";
|
|
140
|
+
if (blank(zone)) {
|
|
141
|
+
return {
|
|
142
|
+
ok: false,
|
|
143
|
+
msg:
|
|
144
|
+
"no compute zone configured — set ZONE in /etc/codeyam/operator-host.env " +
|
|
145
|
+
"(or `gcloud config set compute/zone <zone>`) before adding a VM.",
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (blank(project)) {
|
|
149
|
+
return {
|
|
150
|
+
ok: false,
|
|
151
|
+
msg:
|
|
152
|
+
"no GCP project configured — set GCP_PROJECT (or a non-placeholder " +
|
|
153
|
+
"project in .codeyam/cloud.json) before adding a VM.",
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (!allowNoGitCredentials && blank(token)) {
|
|
157
|
+
return {
|
|
158
|
+
ok: false,
|
|
159
|
+
msg:
|
|
160
|
+
"no GitHub token — set GITHUB_TOKEN/GH_TOKEN in /etc/codeyam/operator-host.env " +
|
|
161
|
+
"(the VM bootstrap needs it to clone the private editor repo), or provision a " +
|
|
162
|
+
"read-only VM with --allow-no-git-credentials.",
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
return { ok: true };
|
|
166
|
+
}
|
|
167
|
+
|
|
122
168
|
/** Gate mirror of cloud.js::assertVariantProvisionable — which editor variants
|
|
123
169
|
* can be provisioned onto a cloud VM. All three variants now provision: `dev`
|
|
124
170
|
* builds the editor image from source, while `staging`/`production` run the
|
|
@@ -186,8 +232,10 @@ function isPlaceholderProject(value) {
|
|
|
186
232
|
* image from editorBranch); the client repo/branch is what lands in
|
|
187
233
|
* /workspace. The build agent rides on `cfg.agent` (normalizeLaunchConfig
|
|
188
234
|
* guarantees a known value, defaulting to claude), so a single config object
|
|
189
|
-
* carries every launch choice. Decoupled exactly as cloud.js consumes them.
|
|
190
|
-
|
|
235
|
+
* carries every launch choice. Decoupled exactly as cloud.js consumes them.
|
|
236
|
+
* `zone` is the resolved compute zone (the dashboard's ZONE); passing it
|
|
237
|
+
* explicitly is what keeps an add off the ambient gcloud default. */
|
|
238
|
+
function cloudUpArgs(cfg, instanceName, project, zone) {
|
|
191
239
|
const args = ["--instance-name", instanceName];
|
|
192
240
|
// Pass the authoritative GCP project (the dashboard's resolved PROJECT)
|
|
193
241
|
// explicitly so cloud:up never relies on the spawning shell's ambient
|
|
@@ -200,6 +248,13 @@ function cloudUpArgs(cfg, instanceName, project) {
|
|
|
200
248
|
// preserve the pre-existing arg shape (cloud.js then falls back to the gcloud
|
|
201
249
|
// default, as before).
|
|
202
250
|
if (project) args.push("--project", project);
|
|
251
|
+
// Pass the resolved compute zone explicitly, for the SAME reason as --project:
|
|
252
|
+
// a bare cloud:up otherwise falls back to `gcloud config get-value
|
|
253
|
+
// compute/zone`, and an operator host with no default compute/zone aborts
|
|
254
|
+
// EVERY add at cloud:up's own --zone check in ~15s, before any instance is
|
|
255
|
+
// created (the 2026-07-20 fleet-wide add failure). Omitted only when the caller
|
|
256
|
+
// resolves no zone, preserving the pre-existing arg shape for legacy callers.
|
|
257
|
+
if (zone) args.push("--zone", zone);
|
|
203
258
|
// Greenfield (new folder) vs clone an existing repo.
|
|
204
259
|
if (cfg.newProjectName) args.push("--new-project", cfg.newProjectName);
|
|
205
260
|
else args.push("--repo", cfg.clientRepo);
|
|
@@ -497,6 +552,61 @@ function stickyAppPort(prevAppPort, info) {
|
|
|
497
552
|
return typeof prevAppPort === 'number' && !Number.isNaN(prevAppPort) ? prevAppPort : null;
|
|
498
553
|
}
|
|
499
554
|
|
|
555
|
+
/** Resolve a VM's preview-tunnel reachability stickily, mirroring `stickyAppPort`.
|
|
556
|
+
* A single flaky preview probe must not blink the 👁 Live Preview link off:
|
|
557
|
+
* an explicit `true`/`false` from the fresh poll passes through (a genuine
|
|
558
|
+
* dead/alive transition always wins), but a missing / null / undefined
|
|
559
|
+
* `info.previewReachable` (transient poll — the probe was skipped, timed out,
|
|
560
|
+
* or the whole probe object is absent) retains the last-known-good
|
|
561
|
+
* `prevPreviewReachable` instead of blanking it to null. `null` results only
|
|
562
|
+
* when neither a fresh boolean nor a previous boolean exists — the true
|
|
563
|
+
* never-probed first-poll case. Pure; no I/O.
|
|
564
|
+
*
|
|
565
|
+
* @param prevPreviewReachable the previous `state.vms[n].previewReachable`
|
|
566
|
+
* @param info the fresh probe result ({ previewReachable } | null)
|
|
567
|
+
* @returns true | false | null per the rules above. */
|
|
568
|
+
function stickyPreviewReachable(prevPreviewReachable, info) {
|
|
569
|
+
const fresh = info && info.previewReachable;
|
|
570
|
+
if (fresh === true || fresh === false) return fresh;
|
|
571
|
+
return prevPreviewReachable === true || prevPreviewReachable === false
|
|
572
|
+
? prevPreviewReachable
|
|
573
|
+
: null;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/** Resolve a VM's preview-SERVING signal — "is the VM's dev server up?" —
|
|
577
|
+
* stickily from the `previewState` field the editor already emits on
|
|
578
|
+
* `/api/session-info`. This is the operator-host-safe replacement for the
|
|
579
|
+
* loopback-only `previewReachable` probe: session-info reaches every VM over
|
|
580
|
+
* the IAP-LB seam on the GCE operator host, where the `127.0.0.1` preview
|
|
581
|
+
* forward `previewReachable` depended on cannot exist (so it read `false`
|
|
582
|
+
* fleet-wide there). It measures the SAME thing the old loopback probe did —
|
|
583
|
+
* "the dev server serves" — not public LB ingress; that is `previewIngress`'s job.
|
|
584
|
+
*
|
|
585
|
+
* `compute_preview_state` classifies it authoritatively from inside the VM
|
|
586
|
+
* (immune to tunnel flakiness, with a recompile-grace debounce):
|
|
587
|
+
* `"forwarding"` → true (dev server up / serving).
|
|
588
|
+
* `"placeholder"` → false (dev server down or never started).
|
|
589
|
+
* anything else → sticky: a missing field (legacy editor predating
|
|
590
|
+
* `previewState`, or `info` null on a transient/failed
|
|
591
|
+
* poll) retains the last-known-good `prevPreviewServing`
|
|
592
|
+
* rather than blinking the 👁 link off. `null` only when
|
|
593
|
+
* neither a fresh state nor a previous boolean exists —
|
|
594
|
+
* the true never-probed first-poll case. Pure; no I/O.
|
|
595
|
+
*
|
|
596
|
+
* Mirrors `stickyPreviewReachable`'s tri-state stickiness exactly.
|
|
597
|
+
*
|
|
598
|
+
* @param prevPreviewServing the previous `state.vms[n].previewServing`
|
|
599
|
+
* @param info the fresh `/api/session-info` result ({ previewState } | null)
|
|
600
|
+
* @returns true | false | null per the rules above. */
|
|
601
|
+
function stickyPreviewServing(prevPreviewServing, info) {
|
|
602
|
+
const state = info && info.previewState;
|
|
603
|
+
if (state === 'forwarding') return true;
|
|
604
|
+
if (state === 'placeholder') return false;
|
|
605
|
+
return prevPreviewServing === true || prevPreviewServing === false
|
|
606
|
+
? prevPreviewServing
|
|
607
|
+
: null;
|
|
608
|
+
}
|
|
609
|
+
|
|
500
610
|
/** Classify a VM's project KIND as `"self-host"` vs `"guest"` from the live
|
|
501
611
|
* identity reported in `/api/session-info` (`clientProject.repo`), with
|
|
502
612
|
* launch-history `clientRepo` as a fallback when live is absent. A guest is
|
|
@@ -674,6 +784,61 @@ function claudeAuthSeeded(mode) {
|
|
|
674
784
|
// the server.js helper so callers don't have to thread one through.
|
|
675
785
|
function _shq(s) { return `'${String(s).replace(/'/g, `'\\''`)}'`; }
|
|
676
786
|
|
|
787
|
+
/** Build a `docker exec … bash -c` sink that stages stdin to a same-directory
|
|
788
|
+
* `.cytmp` sibling and only `mv`s it onto `target` when the staged file is
|
|
789
|
+
* NON-EMPTY — exiting 1 (so the caller's `… || echo CY_DEGRADED` fires) and
|
|
790
|
+
* leaving any existing `target` UNTOUCHED on empty/unreadable input.
|
|
791
|
+
*
|
|
792
|
+
* Closes the truncate-to-0 hazard the plan targets: an empty/unreadable donor's
|
|
793
|
+
* `base64 -d > /root/.claude/.credentials.json` used to clobber the live creds
|
|
794
|
+
* to a 0-byte file AND still report success, because the pipeline's exit status
|
|
795
|
+
* is the sink's, not the empty upstream's. This mirrors the non-empty `-s` guard
|
|
796
|
+
* the git-cred cache path already uses (scripts/cloud-editor-entrypoint.sh).
|
|
797
|
+
*
|
|
798
|
+
* `decode:true` base64-decodes stdin (the donor / host-stash cred copies);
|
|
799
|
+
* `decode:false` copies stdin verbatim (the Rebuild onboarding-state re-seed).
|
|
800
|
+
*
|
|
801
|
+
* `requireCredentialShape:true` adds, ON TOP of the non-empty guard, a Claude
|
|
802
|
+
* `.credentials.json` shape check before the `mv`: a positive plausible
|
|
803
|
+
* `expiresAt` (rejects the observed corrupt-write `expiresAt: 0`) AND non-empty
|
|
804
|
+
* access/refresh tokens. A non-empty-but-malformed donor (valid-looking file
|
|
805
|
+
* with a sentinel expiry or empty tokens) therefore never overwrites the
|
|
806
|
+
* target — closing the remaining gap the non-empty `[ -s ]` guard alone leaves.
|
|
807
|
+
* The checks are quote-free single-quoted ERE patterns (no `"`), which stay
|
|
808
|
+
* intact through the ssh→docker→`bash -c "…"` quoting layers without escaping.
|
|
809
|
+
* The editor-side self-heal (control-api `creds_refresh` / `auth_session`)
|
|
810
|
+
* remains the durable defense against a bad credential from ANY writer; this
|
|
811
|
+
* hardens the in-repo copy path so it is not itself a source.
|
|
812
|
+
*
|
|
813
|
+
* Deliberately uses NO shell variables or command substitution: a fixed
|
|
814
|
+
* `<target>.cytmp` sibling keeps the script free of `$var`, which the VM-host
|
|
815
|
+
* shell that runs the `gcloud … --command` would otherwise expand (to empty)
|
|
816
|
+
* before docker exec ever sees it. The `.cytmp` sibling sits in the same
|
|
817
|
+
* directory as `target`, so the `mv` is an atomic same-filesystem rename. */
|
|
818
|
+
function verifyBeforeMoveSink(
|
|
819
|
+
container,
|
|
820
|
+
target,
|
|
821
|
+
{ decode = true, requireCredentialShape = false } = {},
|
|
822
|
+
) {
|
|
823
|
+
const tmp = `${target}.cytmp`;
|
|
824
|
+
const read = decode ? "base64 -d" : "cat";
|
|
825
|
+
// Positive plausible `expiresAt` (13+ digit millis, first digit non-zero — so
|
|
826
|
+
// `0`/negatives/tiny values fail), plus non-empty access + refresh tokens
|
|
827
|
+
// (≥20 token-ish chars). `[^0-9-]*` / `[^A-Za-z0-9]*` bridge the `":"`
|
|
828
|
+
// punctuation without literal quotes; the value `0` in a corrupt file is a
|
|
829
|
+
// digit that blocks the skip, so the pattern can't false-match a later field.
|
|
830
|
+
const shape = requireCredentialShape
|
|
831
|
+
? ` && grep -Eq 'expiresAt[^0-9-]*[1-9][0-9]{12,}' ${tmp}` +
|
|
832
|
+
` && grep -Eq 'accessToken[^A-Za-z0-9]*[A-Za-z0-9_-]{20,}' ${tmp}` +
|
|
833
|
+
` && grep -Eq 'refreshToken[^A-Za-z0-9]*[A-Za-z0-9_-]{20,}' ${tmp}`
|
|
834
|
+
: "";
|
|
835
|
+
const script =
|
|
836
|
+
`umask 077; ${read} > ${tmp}; ` +
|
|
837
|
+
`if [ -s ${tmp} ]${shape}; then mv ${tmp} ${target} && chmod 600 ${target}; ` +
|
|
838
|
+
`else rm -f ${tmp}; exit 1; fi`;
|
|
839
|
+
return `docker exec -i ${container} bash -c "${script}"`;
|
|
840
|
+
}
|
|
841
|
+
|
|
677
842
|
/** Build the shell steps that seed a freshly-added VM's claude + git credentials,
|
|
678
843
|
* for the resolved `mode` (see `resolveCredsSource`). Returns an array of shell
|
|
679
844
|
* strings the caller newline-joins into the larger provisioning script — each
|
|
@@ -699,15 +864,22 @@ function _shq(s) { return `'${String(s).replace(/'/g, `'\\''`)}'`; }
|
|
|
699
864
|
*
|
|
700
865
|
* Pure; no I/O. */
|
|
701
866
|
function buildCredsSteps({ mode, refInst = "", inst, container, gcloud, zone, n, claudeCredsSrc = "" } = {}) {
|
|
702
|
-
|
|
703
|
-
|
|
867
|
+
// The `.credentials.json` copy additionally validates the donor's credential
|
|
868
|
+
// shape (positive `expiresAt`, non-empty tokens) before overwriting — a
|
|
869
|
+
// non-empty-but-corrupt donor must not clobber a working target. The
|
|
870
|
+
// `.claude.json` (onboarding-state) copy has a different shape, so it keeps
|
|
871
|
+
// the plain non-empty guard.
|
|
872
|
+
const claudeSink = verifyBeforeMoveSink(container, "/root/.claude/.credentials.json", {
|
|
873
|
+
requireCredentialShape: true,
|
|
874
|
+
});
|
|
875
|
+
const claudeJsonSink = verifyBeforeMoveSink(container, "/root/.claude.json");
|
|
704
876
|
const loginHint = `run: docker exec -it ${container} claude /login on ${inst}`;
|
|
705
877
|
if (mode === "donor") {
|
|
706
878
|
return [
|
|
707
879
|
`echo "copying git creds from ${refInst}..."`,
|
|
708
880
|
`${gcloud} compute ssh ${refInst} --tunnel-through-iap --zone=${zone} --command='cat $HOME/.codeyam-cloud-git-credentials' 2>/dev/null | ${gcloud} compute ssh ${inst} --tunnel-through-iap --zone=${zone} --command='umask 077; cat > $HOME/.codeyam-cloud-git-credentials' 2>/dev/null || echo "CY_DEGRADED git creds copy failed on VM-${n} — re-copy creds from a live donor (the agent can't git pull/push until then)"`,
|
|
709
881
|
`echo "copying claude auth from ${refInst} (container->container)..."`,
|
|
710
|
-
`${gcloud} compute ssh ${refInst} --tunnel-through-iap --zone=${zone} --command='docker exec ${container} base64 /root/.claude/.credentials.json' 2>/dev/null | ${gcloud} compute ssh ${inst} --tunnel-through-iap --zone=${zone} --command=${_shq(claudeSink)} 2>/dev/null && echo "claude auth copied" || echo "CY_DEGRADED claude auth copy failed on VM-${n} — ${loginHint}"`,
|
|
882
|
+
`${gcloud} compute ssh ${refInst} --tunnel-through-iap --zone=${zone} --command='docker exec ${container} base64 /root/.claude/.credentials.json' 2>/dev/null | ${gcloud} compute ssh ${inst} --tunnel-through-iap --zone=${zone} --command=${_shq(claudeSink)} 2>/dev/null && echo "claude auth copied" || echo "CY_DEGRADED claude auth copy failed on VM-${n} (empty/unreadable/malformed donor — existing creds left untouched, not overwritten with a corrupt credential) — ${loginHint}"`,
|
|
711
883
|
`echo "copying claude.json (onboarding state) from ${refInst} (container->container)..."`,
|
|
712
884
|
`${gcloud} compute ssh ${refInst} --tunnel-through-iap --zone=${zone} --command='docker exec ${container} base64 /root/.claude.json' 2>/dev/null | ${gcloud} compute ssh ${inst} --tunnel-through-iap --zone=${zone} --command=${_shq(claudeJsonSink)} 2>/dev/null && echo "claude.json copied" || echo "CY_DEGRADED claude.json copy failed on VM-${n} — first interactive claude launch may show 'Select login method' menu; ${loginHint} once to complete onboarding"`,
|
|
713
885
|
];
|
|
@@ -758,7 +930,7 @@ function buildClaudeJsonPreserveSteps({ container, n = "", bakVar = "CY_CLAUDE_J
|
|
|
758
930
|
`if docker exec ${container} cat /root/.claude.json > "$${bakVar}" 2>/dev/null && [ -s "$${bakVar}" ]; then echo "captured /root/.claude.json ($(wc -c < "$${bakVar}") bytes) to preserve onboarding state across recreate"; else echo "no /root/.claude.json to preserve (new container will use the image baseline)"; : > "$${bakVar}"; fi`,
|
|
759
931
|
];
|
|
760
932
|
const restore = [
|
|
761
|
-
`if [ -s "$${bakVar}" ]; then
|
|
933
|
+
`if [ -s "$${bakVar}" ]; then ${verifyBeforeMoveSink(container, "/root/.claude.json", { decode: false })} < "$${bakVar}" && echo "re-seeded /root/.claude.json (onboarding state preserved across recreate)" || echo "CY_DEGRADED claude.json re-seed failed on VM-${n} after Rebuild — first interactive claude launch may show 'Select login method'; ${loginHint} once to complete onboarding"; else echo "no preserved /root/.claude.json; ${loginHint} if the Build tab shows the welcome menu"; fi`,
|
|
762
934
|
`rm -f "$${bakVar}"`,
|
|
763
935
|
];
|
|
764
936
|
return { capture, restore };
|
|
@@ -890,6 +1062,7 @@ module.exports = {
|
|
|
890
1062
|
EMPTY_CONFIG,
|
|
891
1063
|
normalizeLaunchConfig,
|
|
892
1064
|
validateLaunchConfig,
|
|
1065
|
+
addPreflight,
|
|
893
1066
|
isVariantProvisionable,
|
|
894
1067
|
isVariantRebuildable,
|
|
895
1068
|
variantNpmDistTag,
|
|
@@ -909,10 +1082,13 @@ module.exports = {
|
|
|
909
1082
|
resolveCardIdentity,
|
|
910
1083
|
stickyClientProject,
|
|
911
1084
|
stickyAppPort,
|
|
1085
|
+
stickyPreviewReachable,
|
|
1086
|
+
stickyPreviewServing,
|
|
912
1087
|
classifyProjectKind,
|
|
913
1088
|
resolveCredsSource,
|
|
914
1089
|
claudeAuthSeeded,
|
|
915
1090
|
buildCredsSteps,
|
|
1091
|
+
verifyBeforeMoveSink,
|
|
916
1092
|
buildClaudeJsonPreserveSteps,
|
|
917
1093
|
buildEnvApplyCommand,
|
|
918
1094
|
resolveEditorSource,
|