@codeyam-editor/codeyam-editor 0.1.4 → 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 (97) hide show
  1. package/npm/cli.js +5 -1
  2. package/npm/cloud-branch-switch.js +1 -1
  3. package/npm/cloud.js +3166 -150
  4. package/npm/codesign-plan.js +68 -0
  5. package/npm/container.js +3 -0
  6. package/npm/disk-pressure.js +78 -0
  7. package/npm/editor-dev.js +96 -15
  8. package/npm/editor.js +85 -37
  9. package/npm/empty-project-dir.js +92 -7
  10. package/npm/fleet-add-failure.js +252 -0
  11. package/npm/fleet-asset-version.js +37 -0
  12. package/npm/fleet-auth-health.js +213 -0
  13. package/npm/fleet-auth.js +6 -1
  14. package/npm/fleet-build-data.js +109 -5
  15. package/npm/fleet-cleanup.js +232 -16
  16. package/npm/fleet-destroy-safety.js +272 -0
  17. package/npm/fleet-early-roster.js +87 -0
  18. package/npm/fleet-effect-poll.js +110 -0
  19. package/npm/fleet-effect-status.js +33 -32
  20. package/npm/fleet-env.js +86 -0
  21. package/npm/fleet-ghcr-secret.js +129 -0
  22. package/npm/fleet-git-maintenance.js +142 -0
  23. package/npm/fleet-host-op-limiter.js +68 -0
  24. package/npm/fleet-host.js +74 -0
  25. package/npm/fleet-iap-token.js +165 -0
  26. package/npm/fleet-job-badge.js +1 -1
  27. package/npm/fleet-launchd.js +171 -0
  28. package/npm/fleet-live-status.js +155 -0
  29. package/npm/fleet-memory.js +245 -9
  30. package/npm/fleet-plans.js +233 -4
  31. package/npm/fleet-pool.js +75 -16
  32. package/npm/fleet-prewarm.js +241 -0
  33. package/npm/fleet-project-drift.js +58 -0
  34. package/npm/fleet-public-url.js +394 -0
  35. package/npm/fleet-queue.js +95 -1
  36. package/npm/fleet-reachability-debounce.js +69 -0
  37. package/npm/fleet-reachability.js +80 -0
  38. package/npm/fleet-reconcile.js +43 -1
  39. package/npm/fleet-reset.js +330 -13
  40. package/npm/fleet-roster.js +6 -8
  41. package/npm/fleet-running-plan.js +85 -0
  42. package/npm/fleet-self-update.js +142 -0
  43. package/npm/fleet-session-token-cache.js +65 -0
  44. package/npm/fleet-setup-steps.js +112 -0
  45. package/npm/fleet-sleep-status.js +33 -0
  46. package/npm/fleet-status-refs.js +96 -0
  47. package/npm/fleet-strand.js +181 -0
  48. package/npm/fleet-stuck-status.js +117 -0
  49. package/npm/fleet-vm-access.js +404 -0
  50. package/npm/fleet-vm-probe-target.js +282 -0
  51. package/npm/fleet-vm-readiness.js +237 -0
  52. package/npm/fleet-vm-slugs.js +116 -0
  53. package/npm/google-ingress.js +226 -0
  54. package/npm/ingress-invoke.js +770 -0
  55. package/npm/launch-config.js +386 -9
  56. package/npm/scenario-check.js +880 -37
  57. package/npm/scenario-handlers.js +154 -30
  58. package/npm/scenario-interactivity.js +145 -6
  59. package/npm/scenario-issues.js +13 -1
  60. package/npm/scenario-metrics.js +116 -1
  61. package/npm/scenario-mocks.js +144 -21
  62. package/npm/scenario-playwright.js +649 -38
  63. package/npm/script-drift.js +152 -0
  64. package/npm/utils.js +365 -52
  65. package/npm/vm-urls.js +69 -8
  66. package/package.json +5 -5
  67. package/ui/dist/assets/ContainerInspector-DgM0pTAx.js +2 -0
  68. package/ui/dist/assets/EditorShell-B7Q06uJ_.js +252 -0
  69. package/ui/dist/assets/EditorShell-DP5UY20G.css +1 -0
  70. package/ui/dist/assets/{Markdown-BHBqz-Mj.js → Markdown-Chgdf3PW.js} +1 -1
  71. package/ui/dist/assets/ProjectLauncherScreen-BRrEr8Ng.js +1 -0
  72. package/ui/dist/assets/ScenarioDataPanel-BeQfa28g.js +9 -0
  73. package/ui/dist/assets/{ScenarioDataPanel-DYP7pi_n.css → ScenarioDataPanel-C1LukYI6.css} +1 -1
  74. package/ui/dist/assets/ScenarioDataPanelFullPage-BLqRCxQX.js +1 -0
  75. package/ui/dist/assets/ScenarioRenderer-CIAxfHPh.js +21 -0
  76. package/ui/dist/assets/StepsSimulator-CymGgfbu.js +1 -0
  77. package/ui/dist/assets/index-BygCiJSb.css +1 -0
  78. package/ui/dist/assets/{index-BuK0lizt.js → index-DCHqRrwO.js} +9 -9
  79. package/ui/dist/assets/{markdown-v0F0UEt3.js → markdown-C9tbsVHX.js} +1 -1
  80. package/ui/dist/assets/react-nrLBr15I.js +28 -0
  81. package/ui/dist/assets/useEvents-S3Qk1Y_d.js +1 -0
  82. package/ui/dist/assets/useServerIdentity-B7rI0u3O.js +1 -0
  83. package/ui/dist/codeyam-mark.svg +4 -0
  84. package/ui/dist/codeyam-wordmark.svg +5 -0
  85. package/ui/dist/index.html +4 -4
  86. package/ui/dist/open-source/codeyam-counter.png +0 -0
  87. package/ui/dist/open-source/el-carot.png +0 -0
  88. package/ui/dist/open-source/tabcommand.png +0 -0
  89. package/ui/dist/assets/ContainerInspector-C8EkH4PP.js +0 -2
  90. package/ui/dist/assets/EditorShell-tMMdarZD.js +0 -187
  91. package/ui/dist/assets/ScenarioDataPanel-xWYGZOpo.js +0 -5
  92. package/ui/dist/assets/ScenarioDataPanelFullPage-5G6KuZgw.js +0 -1
  93. package/ui/dist/assets/ScenarioRenderer-CSdhX1Ak.js +0 -1
  94. package/ui/dist/assets/StepsSimulator-CRtmlyNZ.js +0 -1
  95. package/ui/dist/assets/index-BGqairFV.css +0 -1
  96. package/ui/dist/assets/react-CSS0HapR.js +0 -28
  97. package/ui/dist/assets/useEvents-DggWrtHe.js +0 -1
@@ -0,0 +1,226 @@
1
+ 'use strict';
2
+ // Deterministic Google Cloud HTTPS-LB resource-name derivation for the fleet.
3
+ //
4
+ // The fleet's public browser ingress moves from cloudflared named tunnels to a
5
+ // single Google external HTTPS Load Balancer (one URL map, host-based routing).
6
+ // Reaching a specific port on a specific VM needs a zonal NEG + backend service
7
+ // per (VM, surface); the URL map then routes each `vm-<N>.<surface>.<domain>`
8
+ // host to that backend. This module is the single source of truth for those
9
+ // Google resource NAMES so the bash provisioners (scripts/gcp/google-ingress.sh,
10
+ // scripts/gcp/vm-ingress.sh) and any node caller derive identical strings and a
11
+ // unit test guards them against drift — exactly the contract npm/vm-urls.js and
12
+ // npm/launch-config.js follow.
13
+ //
14
+ // Pure logic only — no I/O, no env reads beyond the explicit `env` argument a
15
+ // caller passes in, so it stays unit-testable in isolation
16
+ // (npm/google-ingress.test.js). The HOSTNAMES it routes to are reused verbatim
17
+ // from npm/vm-urls.js (vmHostnames / dashboardHostname): the Google migration is
18
+ // a serving-layer swap, not a URL-scheme change, so hostnames must never drift
19
+ // from the cert/DNS that vm-urls.js already defines.
20
+ //
21
+ // Stack note: this is fleet-operator infrastructure (the cloud GCE fleet that
22
+ // runs codeyam-editor itself), not a per-app-stack assumption. A project that
23
+ // never joins a Google fleet never sets FLEET_DOMAIN / FLEET_INGRESS=google and
24
+ // never derives a name here; the legacy cloudflared path stays the fallback.
25
+
26
+ const {
27
+ normalizeVmNumber,
28
+ vmHostnames,
29
+ dashboardHostname,
30
+ } = require('./vm-urls');
31
+
32
+ // The two per-VM ingress surfaces. `editor` fronts the in-VM editor control API
33
+ // (a fixed port); `preview` fronts the VM's dev server (a per-project port).
34
+ // Keep this list as the single definition both name derivation and the host-rule
35
+ // builder iterate, so a third surface is one edit, not three.
36
+ const SURFACES = Object.freeze(['editor', 'preview']);
37
+
38
+ // The in-VM editor control API port. Fixed across the fleet (matches
39
+ // named-tunnel-config.sh's EDITOR_PORT default and cloud.js's `-L …:14199`), so
40
+ // the editor backend NEG always targets this port regardless of project.
41
+ const EDITOR_PORT = 14199;
42
+
43
+ /** The fixed in-VM editor control-API port the editor backend NEG targets. A
44
+ * function (not a bare constant export) so callers read it the same way they
45
+ * read the per-VM appPort — a uniform "resolve the port for this surface" shape. */
46
+ function editorPort() {
47
+ return EDITOR_PORT;
48
+ }
49
+
50
+ /** Validate an ingress surface against SURFACES. The NEG / backend-service /
51
+ * host-rule derivations all key off it, so a typo'd surface would mint a
52
+ * resource that nothing routes to — reject it loudly at the boundary, mirroring
53
+ * how vm-urls.js rejects a bad VM number or domain. Returns the surface. */
54
+ function normalizeSurface(surface) {
55
+ if (!SURFACES.includes(surface)) {
56
+ throw new Error(
57
+ `surface must be one of ${SURFACES.join('|')}, got: ${surface}`,
58
+ );
59
+ }
60
+ return surface;
61
+ }
62
+
63
+ /** The zonal NEG name for a (VM, surface): `vm-<N>-<surface>-neg`. One network
64
+ * endpoint group per surface points at the VM's internal IP + that surface's
65
+ * port — the Google-native analog of a cloudflared ingress rule's local origin. */
66
+ function negName(n, surface) {
67
+ const num = normalizeVmNumber(n);
68
+ return `vm-${num}-${normalizeSurface(surface)}-neg`;
69
+ }
70
+
71
+ /** The backend-service name for a (VM, surface): `vm-<N>-<surface>-bes`. One
72
+ * backend service per host rule wraps the matching NEG (with IAP + a health
73
+ * check); the URL map's host rule points at this name. */
74
+ function backendServiceName(n, surface) {
75
+ const num = normalizeVmNumber(n);
76
+ return `vm-${num}-${normalizeSurface(surface)}-bes`;
77
+ }
78
+
79
+ /** The URL-map host rules for a VM: one `{ host, surface, backendService }` per
80
+ * surface, where `host` is the EXACT hostname vm-urls.js derives (so the LB
81
+ * routes the same hostnames the cert covers and DNS resolves) and
82
+ * `backendService` is this module's name for that surface. Reusing
83
+ * `vmHostnames` rather than re-deriving keeps the hostname scheme single-sourced.
84
+ * Returns the surfaces in SURFACES order so the provisioner's output is stable.
85
+ *
86
+ * An optional `slug` overrides the routed `host` with the VM's custom hostname
87
+ * (`<slug>.<surface>.<domain>`) while the NEG / `backendService` names stay
88
+ * keyed off the VM NUMBER — the slug changes only what the URL map matches, not
89
+ * the internal GCP resource names. */
90
+ function urlMapHostRules(n, domain, slug) {
91
+ const hosts = vmHostnames(n, domain, slug); // also validates n, domain, slug
92
+ return SURFACES.map((surface) => ({
93
+ host: hosts[surface],
94
+ surface,
95
+ backendService: backendServiceName(n, surface),
96
+ }));
97
+ }
98
+
99
+ /** Decide which gcloud URL-map operation reconciles a single `(host,
100
+ * pathMatcher)` against the map's CURRENT state, given `hostRules` — the
101
+ * flattened `[{ host, pathMatcher }]` projection of the URL map's host rules
102
+ * (one entry per host, since one host rule can list several hosts). Returns
103
+ * `{ op }`, one of:
104
+ * - `skip` — `host` already routes to `pathMatcher`; nothing to do.
105
+ * - `add-host-rule` — `pathMatcher` already exists (some other host points
106
+ * at it, e.g. the numeric `vmN.<surface>` host) but
107
+ * `host` is not attached: attach it. Fixes the
108
+ * `Duplicate path matcher` failure on a reused slug.
109
+ * - `repoint` — `host` exists but aims at a DIFFERENT matcher (a stale
110
+ * rule left by a destroyed VM): remove it, then attach
111
+ * `host` to the correct matcher. Fixes a reused slug
112
+ * silently skipped as "already configured" while it
113
+ * still routed to a dead backend.
114
+ * - `add-path-matcher`— neither `host` nor `pathMatcher` is present: create
115
+ * the matcher and attach the host (the original path).
116
+ *
117
+ * Pure and side-effect-free — no gcloud, no I/O — so vm-ingress.sh can read the
118
+ * URL-map state in bash, ask this helper for the op, and run the matching
119
+ * gcloud command, keeping the four-way decision unit-testable (the same
120
+ * bash↔node contract the name derivations above follow). Tolerates a
121
+ * missing/empty `hostRules`. */
122
+ function hostRuleOperation({ hostRules, host, pathMatcher } = {}) {
123
+ if (!host) throw new Error('hostRuleOperation requires a host');
124
+ if (!pathMatcher) throw new Error('hostRuleOperation requires a pathMatcher');
125
+ const rules = Array.isArray(hostRules) ? hostRules : [];
126
+ const existing = rules.find((r) => r && r.host === host);
127
+ if (existing) {
128
+ return { op: existing.pathMatcher === pathMatcher ? 'skip' : 'repoint' };
129
+ }
130
+ const matcherExists = rules.some((r) => r && r.pathMatcher === pathMatcher);
131
+ return { op: matcherExists ? 'add-host-rule' : 'add-path-matcher' };
132
+ }
133
+
134
+ /** Decide whether a NEG must be attached as a backend to its backend service,
135
+ * given `backends` — the BES's CURRENT `backends[].group` self-links (what
136
+ * `gcloud compute backend-services describe --format='value(backends[].group)'`
137
+ * emits) — and `neg`, the bare NEG name this surface expects. Returns `{ op }`:
138
+ * - `skip` — some current backend already resolves to `neg`; nothing to do.
139
+ * - `attach` — `neg` is not yet a backend (an empty BES, or one wired to a
140
+ * different group): `add-backend` it.
141
+ *
142
+ * `backends[].group` is a full self-link
143
+ * (`.../networkEndpointGroups/vm-3-editor-neg`), so the match compares by
144
+ * basename — the bare NEG name — not the whole URL. Pure and side-effect-free
145
+ * (no gcloud, no I/O), so vm-ingress.sh reads the BES's backends in bash, asks
146
+ * this helper for the op, and `add-backend`s only when it returns `attach`: the
147
+ * same bash↔node contract `hostRuleOperation` follows. This is what makes `up`
148
+ * *converge* a BES that pre-exists with zero backends (the permanent-503 empty-
149
+ * backend bug) instead of skipping the attach as "already configured". Throws on
150
+ * a missing `neg` (mirrors `hostRuleOperation`'s guard); tolerates a
151
+ * missing/empty `backends`. */
152
+ function backendNegAttachOperation({ backends, neg } = {}) {
153
+ if (!neg) throw new Error('backendNegAttachOperation requires a neg');
154
+ const groups = Array.isArray(backends) ? backends : [];
155
+ const basename = (g) => String(g).split('/').pop();
156
+ const attached = groups.some((g) => g && basename(g) === neg);
157
+ return { op: attached ? 'skip' : 'attach' };
158
+ }
159
+
160
+ /** The dashboard's backend-service name: `dashboard-bes`. The dashboard is a
161
+ * single fleet-wide surface (not per-VM), so its name carries no VM number. The
162
+ * backend itself is only fully wired once the dashboard moves onto GCE
163
+ * (vm-dashboard-on-gce), but the name is defined here so the shared provisioner
164
+ * and that dependent plan agree. */
165
+ function dashboardBackendServiceName() {
166
+ return 'dashboard-bes';
167
+ }
168
+
169
+ /** The dashboard's zonal NEG name: `dashboard-neg`. One network endpoint group
170
+ * points at the operator host's internal IP + the dashboard port (8787) — the
171
+ * Google-native analog of the dashboard's old ngrok upstream. Single fleet-wide
172
+ * surface, so (like the backend-service name) it carries no VM number. Wired by
173
+ * scripts/gcp/dashboard-ingress.sh once the dashboard moves onto GCE. */
174
+ function dashboardNegName() {
175
+ return 'dashboard-neg';
176
+ }
177
+
178
+ /** The dashboard's dedicated health-check name: `dashboard-hc`. The per-VM
179
+ * surfaces share `fleet-ingress-hc` (port 14199), but the dashboard backend
180
+ * health-checks its own port (8787, path /healthz), so it needs its own check
181
+ * rather than reusing the editor-port one. */
182
+ function dashboardHealthCheckName() {
183
+ return 'dashboard-hc';
184
+ }
185
+
186
+ /** The dashboard's single URL-map host rule: `{ host, backendService }`, where
187
+ * `host` is `dashboard.<domain>` from vm-urls.js. Separate from
188
+ * `urlMapHostRules` because the dashboard is fleet-wide, not keyed off a VM
189
+ * number. */
190
+ function dashboardHostRule(domain) {
191
+ return {
192
+ host: dashboardHostname(domain), // validates domain
193
+ backendService: dashboardBackendServiceName(),
194
+ };
195
+ }
196
+
197
+ /** Extract the VM number from an LB-ingress resource name this module mints
198
+ * (`vm-<N>-<surface>-neg` or `vm-<N>-<surface>-bes`, surface ∈ SURFACES).
199
+ * Returns the number, or null for any name that isn't a per-VM ingress resource
200
+ * (the shared `codeyam-dashboard-*` backend, a foreign resource, junk). The
201
+ * inverse of negName / backendServiceName — the orphan sweep uses it to map the
202
+ * live LB backends back to the VM numbers that own them. The surface alternation
203
+ * is built from SURFACES so it stays in lockstep with the name derivations.
204
+ * Pure. */
205
+ function vmNumberFromResourceName(name) {
206
+ const re = new RegExp(`^vm-(\\d+)-(?:${SURFACES.join('|')})-(?:neg|bes)$`);
207
+ const m = re.exec(String(name == null ? '' : name).trim());
208
+ return m ? Number(m[1]) : null;
209
+ }
210
+
211
+ module.exports = {
212
+ SURFACES,
213
+ EDITOR_PORT,
214
+ editorPort,
215
+ normalizeSurface,
216
+ negName,
217
+ backendServiceName,
218
+ vmNumberFromResourceName,
219
+ urlMapHostRules,
220
+ hostRuleOperation,
221
+ backendNegAttachOperation,
222
+ dashboardBackendServiceName,
223
+ dashboardNegName,
224
+ dashboardHealthCheckName,
225
+ dashboardHostRule,
226
+ };