@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.
Files changed (76) hide show
  1. package/npm/cli.js +5 -1
  2. package/npm/cloud.js +1884 -210
  3. package/npm/codesign-plan.js +68 -0
  4. package/npm/empty-project-dir.js +92 -7
  5. package/npm/fleet-add-failure.js +252 -0
  6. package/npm/fleet-asset-version.js +37 -0
  7. package/npm/fleet-auth-health.js +143 -5
  8. package/npm/fleet-build-data.js +60 -5
  9. package/npm/fleet-cleanup.js +114 -9
  10. package/npm/fleet-destroy-safety.js +230 -13
  11. package/npm/fleet-early-roster.js +87 -0
  12. package/npm/fleet-effect-poll.js +110 -0
  13. package/npm/fleet-env.js +86 -0
  14. package/npm/fleet-ghcr-secret.js +129 -0
  15. package/npm/fleet-git-maintenance.js +142 -0
  16. package/npm/fleet-host-op-limiter.js +68 -0
  17. package/npm/fleet-iap-token.js +165 -0
  18. package/npm/fleet-live-status.js +40 -8
  19. package/npm/fleet-memory.js +245 -9
  20. package/npm/fleet-plans.js +98 -4
  21. package/npm/fleet-pool.js +75 -16
  22. package/npm/fleet-prewarm.js +87 -11
  23. package/npm/fleet-project-drift.js +58 -0
  24. package/npm/fleet-public-url.js +234 -0
  25. package/npm/fleet-queue.js +58 -1
  26. package/npm/fleet-reachability-debounce.js +69 -0
  27. package/npm/fleet-reachability.js +80 -0
  28. package/npm/fleet-reset.js +32 -0
  29. package/npm/fleet-running-plan.js +85 -0
  30. package/npm/fleet-session-token-cache.js +65 -0
  31. package/npm/fleet-setup-steps.js +19 -12
  32. package/npm/fleet-status-refs.js +96 -0
  33. package/npm/fleet-strand.js +39 -8
  34. package/npm/fleet-stuck-status.js +13 -7
  35. package/npm/fleet-vm-access.js +170 -0
  36. package/npm/fleet-vm-probe-target.js +282 -0
  37. package/npm/fleet-vm-readiness.js +136 -3
  38. package/npm/google-ingress.js +15 -0
  39. package/npm/ingress-invoke.js +478 -0
  40. package/npm/launch-config.js +182 -6
  41. package/npm/scenario-check.js +282 -10
  42. package/npm/scenario-handlers.js +85 -27
  43. package/npm/scenario-interactivity.js +145 -6
  44. package/npm/scenario-issues.js +13 -1
  45. package/npm/scenario-mocks.js +129 -26
  46. package/npm/scenario-playwright.js +286 -12
  47. package/npm/script-drift.js +72 -1
  48. package/npm/utils.js +37 -3
  49. package/package.json +5 -5
  50. package/ui/dist/assets/{ContainerInspector-D7VZkkaj.js → ContainerInspector-DgM0pTAx.js} +1 -1
  51. package/ui/dist/assets/EditorShell-B7Q06uJ_.js +252 -0
  52. package/ui/dist/assets/EditorShell-DP5UY20G.css +1 -0
  53. package/ui/dist/assets/{Markdown-BoLHHFXH.js → Markdown-Chgdf3PW.js} +1 -1
  54. package/ui/dist/assets/ProjectLauncherScreen-BRrEr8Ng.js +1 -0
  55. package/ui/dist/assets/ScenarioDataPanel-BeQfa28g.js +9 -0
  56. package/ui/dist/assets/{ScenarioDataPanel-DYP7pi_n.css → ScenarioDataPanel-C1LukYI6.css} +1 -1
  57. package/ui/dist/assets/{ScenarioDataPanelFullPage-CBLTGC4-.js → ScenarioDataPanelFullPage-BLqRCxQX.js} +1 -1
  58. package/ui/dist/assets/ScenarioRenderer-CIAxfHPh.js +21 -0
  59. package/ui/dist/assets/{StepsSimulator-BFldWyoX.js → StepsSimulator-CymGgfbu.js} +1 -1
  60. package/ui/dist/assets/index-BygCiJSb.css +1 -0
  61. package/ui/dist/assets/{index-QsOiOG9x.js → index-DCHqRrwO.js} +4 -4
  62. package/ui/dist/assets/{markdown-v0F0UEt3.js → markdown-C9tbsVHX.js} +1 -1
  63. package/ui/dist/assets/react-nrLBr15I.js +28 -0
  64. package/ui/dist/assets/{useEvents-DHkLJZR4.js → useEvents-S3Qk1Y_d.js} +1 -1
  65. package/ui/dist/assets/useServerIdentity-B7rI0u3O.js +1 -0
  66. package/ui/dist/index.html +4 -4
  67. package/ui/dist/open-source/codeyam-counter.png +0 -0
  68. package/ui/dist/open-source/el-carot.png +0 -0
  69. package/ui/dist/open-source/tabcommand.png +0 -0
  70. package/ui/dist/assets/EditorShell-DHhIaotd.css +0 -1
  71. package/ui/dist/assets/EditorShell-r-796N_W.js +0 -191
  72. package/ui/dist/assets/ProjectLauncherScreen-Ck-XWkQN.js +0 -1
  73. package/ui/dist/assets/ScenarioDataPanel-B9iDSUpA.js +0 -5
  74. package/ui/dist/assets/ScenarioRenderer-BhLfT-nX.js +0 -21
  75. package/ui/dist/assets/index-CIXZkwfB.css +0 -1
  76. package/ui/dist/assets/react-CSS0HapR.js +0 -28
@@ -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
- function cloudUpArgs(cfg, instanceName, project) {
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
- const claudeSink = `docker exec -i ${container} bash -c "umask 077; base64 -d > /root/.claude/.credentials.json && chmod 600 /root/.claude/.credentials.json"`;
703
- const claudeJsonSink = `docker exec -i ${container} bash -c "umask 077; base64 -d > /root/.claude.json && chmod 600 /root/.claude.json"`;
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 docker exec -i ${container} bash -c 'umask 077; cat > /root/.claude.json && chmod 600 /root/.claude.json' < "$${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`,
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,