@elytracms/cli 0.0.12 → 0.0.66
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/README.md +49 -11
- package/package.json +2 -2
- package/src/cli.js +3 -1
- package/src/doctor.js +58 -2
package/README.md
CHANGED
|
@@ -27,7 +27,9 @@ elytra init First-run setup: provision Convex, generate the auth signing
|
|
|
27
27
|
elytra doctor Diagnose the active deployment's readiness and print each fix.
|
|
28
28
|
Includes the expected deployment env vars, reported set/missing
|
|
29
29
|
by NAME only — values are never read into the report or printed.
|
|
30
|
-
|
|
30
|
+
When the publish revalidate pair is set, also sends a signed
|
|
31
|
+
no-op ping to the endpoint to prove the publish → live chain
|
|
32
|
+
end to end. Read-only; exits non-zero if any check fails.
|
|
31
33
|
|
|
32
34
|
elytra help Show help
|
|
33
35
|
elytra version Show version
|
|
@@ -87,19 +89,55 @@ set on the wrong plane:
|
|
|
87
89
|
3. **Convex deployment env** — set with `npx convex env set` (from `studio/`);
|
|
88
90
|
lives on the deployment itself, visible in the Convex dashboard but in no
|
|
89
91
|
file: the auth signing keys (`JWT_PRIVATE_KEY`, `JWKS` — set via
|
|
90
|
-
`npx @convex-dev/auth`), `SITE_URL`,
|
|
92
|
+
`npx @convex-dev/auth`), `SITE_URL`, the instant-publish trio
|
|
91
93
|
`CANVAS_PROJECT_SCOPE` / `CANVAS_REVALIDATE_ENDPOINT` /
|
|
92
|
-
`CANVAS_REVALIDATE_SECRET
|
|
93
|
-
frontend-plane counterpart). `elytra doctor` diagnoses
|
|
94
|
-
readiness.
|
|
94
|
+
`CANVAS_REVALIDATE_SECRET`, and `DRAFT_PREVIEW_TOKEN` (each of the latter
|
|
95
|
+
four must match its frontend-plane counterpart). `elytra doctor` diagnoses
|
|
96
|
+
this plane's readiness.
|
|
95
97
|
|
|
96
98
|
Because plane 3 lives in no file, it is the easiest to overlook — `elytra
|
|
97
99
|
doctor` therefore enumerates the expected vars there (auth keys, `SITE_URL`,
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
spot — values are never stored,
|
|
102
|
-
(the auth keys) fails the run
|
|
103
|
-
|
|
100
|
+
the instant-publish trio, the draft-preview token, plus the AI provider key
|
|
101
|
+
on the studio-host plane when the AI route is present) and reports each one
|
|
102
|
+
**set or missing, by name only**: the doctor parses names out of `npx convex
|
|
103
|
+
env list` and discards every value on the spot — values are never stored,
|
|
104
|
+
printed, or logged. A missing required var (the auth keys) fails the run
|
|
105
|
+
with its exact fix line; a missing recommended var (`SITE_URL`, the AI key,
|
|
106
|
+
the draft-preview token) is a named warning that doesn't change the exit
|
|
104
107
|
code. The studio's boot-time "Deployment setup" screen lists the same
|
|
105
108
|
expected vars, so the CLI and the studio tell one story.
|
|
109
|
+
|
|
110
|
+
The **instant-publish trio** — `CANVAS_PROJECT_SCOPE`,
|
|
111
|
+
`CANVAS_REVALIDATE_ENDPOINT`, and `CANVAS_REVALIDATE_SECRET` — gets special
|
|
112
|
+
treatment, because the publish path **silently no-ops** while any of the
|
|
113
|
+
three is unset: `CANVAS_PROJECT_SCOPE` gates the write-chokepoint trigger
|
|
114
|
+
that *schedules* the revalidate webhook (checked FIRST, before the pair is
|
|
115
|
+
even consulted), and the pair then gates whether the scheduled webhook
|
|
116
|
+
actually *delivers*. Either way, publishes never update the live site until
|
|
117
|
+
a redeploy, with no error anywhere. All three missing is a warning carrying
|
|
118
|
+
exactly that consequence (a project without a delivery frontend may
|
|
119
|
+
legitimately leave them unset); **any partial subset — one or two of the
|
|
120
|
+
three set — fails** the run, because a half-configured trio has no
|
|
121
|
+
legitimate reading (documented together in the scaffold's CONNECT.md, set in
|
|
122
|
+
one shell block, so a partial state is far more likely an interrupted setup
|
|
123
|
+
than a deliberate choice). When the pair (`CANVAS_REVALIDATE_ENDPOINT` +
|
|
124
|
+
`CANVAS_REVALIDATE_SECRET`) IS set, the doctor goes one step further and
|
|
125
|
+
proves the chain end to end: it POSTs a **signed no-op ping** (an empty tag
|
|
126
|
+
list — the route revalidates nothing) to the endpoint. A 200
|
|
127
|
+
`{"revalidated":[]}` passes (endpoint reachable, and the frontend's
|
|
128
|
+
`REVALIDATE_SECRET` matches); a 401 fails as a secret mismatch between
|
|
129
|
+
Convex and the frontend host; a network error only warns (unreachable from
|
|
130
|
+
your machine is not the same as broken — Convex may still reach it). The
|
|
131
|
+
pair's values are read in-process for the ping only and never printed. The
|
|
132
|
+
ping proves only the pair's reachability/secret-match, not scheduling — a
|
|
133
|
+
missing `CANVAS_PROJECT_SCOPE` still fails its own row (and the run)
|
|
134
|
+
independently, even when the ping passes.
|
|
135
|
+
|
|
136
|
+
The **draft-preview token** — `DRAFT_PREVIEW_TOKEN` — gates the deployment's
|
|
137
|
+
`draftSnapshot` query and must equal the frontend host's own
|
|
138
|
+
`DRAFT_PREVIEW_TOKEN` (the same secret that gates the frontend's
|
|
139
|
+
preview-enable route); the doctor can only see the Convex-deployment side; it
|
|
140
|
+
has no `pairWith` escalation because its real partner lives on a plane this
|
|
141
|
+
doctor cannot read. Missing it is a warning: draft preview is closed
|
|
142
|
+
everywhere (401 on both sides) until it's set — a project that never uses
|
|
143
|
+
draft preview may legitimately leave it unset.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elytracms/cli",
|
|
3
3
|
"author": "finaldream <hi@finaldream.de>",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.66",
|
|
5
5
|
"description": "The Elytra CLI — run a consuming repo's studio, frontend, and Convex backend under one command.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"type": "module",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"node": ">=20"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@elytracms/core": "^0.0.
|
|
20
|
+
"@elytracms/core": "^0.0.66"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"vitest": "^4.1.10"
|
package/src/cli.js
CHANGED
|
@@ -25,7 +25,9 @@ Commands:
|
|
|
25
25
|
verify readiness. Idempotent — safe to re-run at any point.
|
|
26
26
|
doctor Diagnose the active deployment's readiness (URL, reachability, auth
|
|
27
27
|
signing keys, expected deployment env vars — set/missing by NAME,
|
|
28
|
-
values never shown — config, membership) and print each fix.
|
|
28
|
+
values never shown — config, membership) and print each fix. When the
|
|
29
|
+
instant-publish revalidate pair is set, also sends a signed no-op ping
|
|
30
|
+
to the endpoint to prove the publish → live chain end to end. Exit
|
|
29
31
|
non-zero if any check fails. Read-only. Optional: --url <convex url>.
|
|
30
32
|
help Show this help
|
|
31
33
|
version Show version
|
package/src/doctor.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
+
REVALIDATE_ENV_ENDPOINT,
|
|
3
|
+
REVALIDATE_ENV_SECRET,
|
|
2
4
|
backendSiteUrl,
|
|
3
5
|
checkDeploymentReady,
|
|
4
6
|
expectedDeploymentEnv,
|
|
5
7
|
probeAuthSigningKeys,
|
|
8
|
+
probeRevalidateEndpoint,
|
|
6
9
|
} from '@elytracms/core/export-sync/deployment-ready'
|
|
7
|
-
import { defaultConvexExec } from './provision.js'
|
|
10
|
+
import { convexEnvGet, defaultConvexExec } from './provision.js'
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
13
|
* `elytra doctor` (EC-259) — the pre-boot counterpart to the studio's boot
|
|
@@ -177,6 +180,50 @@ export function gatherDeploymentEnvFacts({ env = {}, target, studioDir, aiRouteC
|
|
|
177
180
|
return facts
|
|
178
181
|
}
|
|
179
182
|
|
|
183
|
+
// --- Signed revalidate ping (EC-421) -------------------------------------------
|
|
184
|
+
//
|
|
185
|
+
// When the presence checks say the instant-publish pair IS set, the doctor proves the
|
|
186
|
+
// chain end to end: a POST to the endpoint signed with the secret, carrying an EMPTY
|
|
187
|
+
// tag list — by the receiving route's own semantics a harmless no-op answering 200
|
|
188
|
+
// `{"revalidated":[]}`. Default-on (no flag): the ping is read-only in effect and the
|
|
189
|
+
// silent-failure class it catches (secret mismatch — publishes rejected with 401
|
|
190
|
+
// forever, no error anywhere) is exactly what the doctor exists for.
|
|
191
|
+
//
|
|
192
|
+
// The ping needs the pair's VALUES — the one sanctioned value read: captured
|
|
193
|
+
// `npx convex env get` subprocess stdout (`convexEnvGet`), held in locals inside this
|
|
194
|
+
// function, passed into the probe, and never attached to a fact, printed, or logged.
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Gather the revalidate-ping fact for the report, or `undefined` when the ping does
|
|
198
|
+
* not apply (pair not fully present / presence not enumerated — the presence checks
|
|
199
|
+
* already tell that story; a probe row would be noise). A pair that is PRESENT but
|
|
200
|
+
* whose values can't be read yields an honest `unknown`, never a fabricated outcome.
|
|
201
|
+
*/
|
|
202
|
+
export async function gatherRevalidateProbe({
|
|
203
|
+
envFacts,
|
|
204
|
+
studioDir,
|
|
205
|
+
exec,
|
|
206
|
+
fetchImpl = fetch,
|
|
207
|
+
probe = probeRevalidateEndpoint,
|
|
208
|
+
} = {}) {
|
|
209
|
+
const names = envFacts?.deploymentSetNames
|
|
210
|
+
if (!names || !names.includes(REVALIDATE_ENV_ENDPOINT) || !names.includes(REVALIDATE_ENV_SECRET)) {
|
|
211
|
+
return undefined
|
|
212
|
+
}
|
|
213
|
+
// In-process value read (the ONLY sanctioned one): captured subprocess stdout.
|
|
214
|
+
const endpoint = convexEnvGet(REVALIDATE_ENV_ENDPOINT, { cwd: studioDir, exec })
|
|
215
|
+
const secret = convexEnvGet(REVALIDATE_ENV_SECRET, { cwd: studioDir, exec })
|
|
216
|
+
if (endpoint === '' || secret === '') {
|
|
217
|
+
return {
|
|
218
|
+
status: 'unknown',
|
|
219
|
+
detail:
|
|
220
|
+
'The revalidate pair is set, but its values could not be read here (`npx convex env get` failed) — ' +
|
|
221
|
+
'the signed ping was not run.',
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return probe(endpoint, secret, fetchImpl)
|
|
225
|
+
}
|
|
226
|
+
|
|
180
227
|
// --- Formatting (pure) -------------------------------------------------------
|
|
181
228
|
|
|
182
229
|
const GLYPH = { ok: '✓', warn: '!', fail: '✗', unknown: '?' }
|
|
@@ -226,7 +273,8 @@ export function formatReport(report) {
|
|
|
226
273
|
* `url`, and `exec` are injectable for tests. `studioDir` (where `convex env list`
|
|
227
274
|
* runs) and `aiRouteConfigured` enable the deployment-env bucket (EC-399); without a
|
|
228
275
|
* studio dir the env checks stay honest `unknown`s. Playground stays untouched — no
|
|
229
|
-
* deployment, no env bucket.
|
|
276
|
+
* deployment, no env bucket. When the instant-publish revalidate pair is present, a
|
|
277
|
+
* signed no-op ping proves the publish → live chain end to end (EC-421, default-on).
|
|
230
278
|
*/
|
|
231
279
|
export async function runDoctor({
|
|
232
280
|
env = {},
|
|
@@ -254,6 +302,14 @@ export async function runDoctor({
|
|
|
254
302
|
const facts = await gatherFacts(target, { fetchImpl })
|
|
255
303
|
if (target.kind === 'convex') {
|
|
256
304
|
facts.deploymentEnv = gatherDeploymentEnvFacts({ env, target, studioDir, aiRouteConfigured, exec })
|
|
305
|
+
// EC-421: default-on signed no-op ping whenever the revalidate pair is present.
|
|
306
|
+
const revalidateProbe = await gatherRevalidateProbe({
|
|
307
|
+
envFacts: facts.deploymentEnv,
|
|
308
|
+
studioDir,
|
|
309
|
+
exec,
|
|
310
|
+
fetchImpl,
|
|
311
|
+
})
|
|
312
|
+
if (revalidateProbe) facts.revalidateProbe = revalidateProbe
|
|
257
313
|
}
|
|
258
314
|
const report = checkDeploymentReady(facts)
|
|
259
315
|
log(formatReport(report))
|