@elytracms/cli 0.0.91 → 0.0.93
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/package.json +2 -2
- package/src/cli.js +36 -33
- package/src/config-harness.js +3 -2
- package/src/doctor-checks.js +261 -0
- package/src/doctor.js +51 -133
- package/src/engine-version.js +32 -0
- package/src/previews.js +4 -24
- package/src/project.js +2 -1
- package/src/provision.js +8 -6
- package/src/routes-check.js +86 -405
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.93",
|
|
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": ">=22.18.0"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@elytracms/core": "^0.0.
|
|
20
|
+
"@elytracms/core": "^0.0.93"
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"vitest": "^4.1.10"
|
package/src/cli.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process'
|
|
2
2
|
import { stdout } from 'node:process'
|
|
3
|
+
import {
|
|
4
|
+
DEV_FRONTEND_BASE_PORT,
|
|
5
|
+
DEV_FRONTEND_URL_ENV,
|
|
6
|
+
devFrontendUrl,
|
|
7
|
+
} from '@elytracms/core/export-sync/deployment-ready'
|
|
3
8
|
import { resolveInstalledEngineVersion, runDoctor } from './doctor.js'
|
|
9
|
+
import { formatDoctorChecksHelp } from './doctor-checks.js'
|
|
4
10
|
import { appDisplay } from './layout.js'
|
|
5
11
|
import { runPreviews } from './previews.js'
|
|
6
12
|
import { inspectProject, resolveStudioEnv } from './project.js'
|
|
@@ -24,16 +30,10 @@ Commands:
|
|
|
24
30
|
elytra build frontend frontend build only
|
|
25
31
|
init First-run setup: provision Convex, generate the auth signing keys,
|
|
26
32
|
verify readiness. Idempotent — safe to re-run at any point.
|
|
27
|
-
doctor Diagnose the
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
to the endpoint to prove the publish → live chain end to end. Also
|
|
32
|
-
checks your routes for URL conflicts, the project layout
|
|
33
|
-
(package.json#elytra), the AI route your studio's vite.config.ts
|
|
34
|
-
mounts, and reports the generated-previews artifacts (manifest/files
|
|
35
|
-
present; hash staleness lives in \`elytra previews --check\`). Exit
|
|
36
|
-
non-zero if any check fails. Read-only. Optional: --url <convex url>.
|
|
33
|
+
doctor Diagnose the deployment, its env, your config, routes, project layout
|
|
34
|
+
and generated previews, and print each fix. 'elytra doctor --help'
|
|
35
|
+
lists every check. Exit non-zero if any check fails. Read-only.
|
|
36
|
+
Optional: --url <convex url>.
|
|
37
37
|
previews Render every canvas-eligible component's fixtures against YOUR theme
|
|
38
38
|
(headless Chromium via Playwright — an optional install) and write
|
|
39
39
|
committed preview images (studio/public/elytra-previews/) plus an
|
|
@@ -112,27 +112,23 @@ First-run setup: provision Convex, generate the auth signing keys, verify
|
|
|
112
112
|
readiness. Idempotent — safe to re-run at any point. No flags.
|
|
113
113
|
`.trim(),
|
|
114
114
|
|
|
115
|
+
// EC-599: the check list is rendered from the one declaration (`doctor-checks.js`) —
|
|
116
|
+
// this text used to restate it by hand, and had drifted from what the doctor does.
|
|
115
117
|
doctor: `
|
|
116
118
|
Usage: elytra doctor [--url <convex url>]
|
|
117
119
|
|
|
118
|
-
Diagnose the active deployment
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
cms/index.ts. With the config resolved, it also reads the public delivery
|
|
131
|
-
snapshot and warns about published documents whose collection the config
|
|
132
|
-
does not declare. It reports the project layout it resolved
|
|
133
|
-
(package.json#elytra — a declared path that does not exist fails) and the AI
|
|
134
|
-
route your studio's vite.config.ts mounts (which decides whether an AI
|
|
135
|
-
provider key is expected). Exit non-zero if any check fails. Read-only.
|
|
120
|
+
Diagnose the active deployment and this project, and print a copy-pasteable
|
|
121
|
+
fix for every failing or warning check. Read-only: it reads the studio env,
|
|
122
|
+
the deployment's public endpoints and the names of the deployment's env vars;
|
|
123
|
+
no value is ever printed (the revalidate pair's are read in-process, only to
|
|
124
|
+
sign the probe ping). With the studio's dependencies installed it also
|
|
125
|
+
executes elytra.config.ts through your studio's own Vite (a few seconds), so
|
|
126
|
+
the config, collection and route checks get real answers; without them those
|
|
127
|
+
checks say "unknown" with the reason. Exit non-zero if any check fails —
|
|
128
|
+
warnings and unknowns never change the exit code.
|
|
129
|
+
|
|
130
|
+
Checks, in report order:
|
|
131
|
+
${formatDoctorChecksHelp()}
|
|
136
132
|
|
|
137
133
|
Options:
|
|
138
134
|
--url <convex url> Diagnose this deployment instead of the one resolved
|
|
@@ -215,15 +211,22 @@ export function planDev(project) {
|
|
|
215
211
|
const studioDir = appDir(project, 'studio')
|
|
216
212
|
const frontendDir = appDir(project, 'frontend')
|
|
217
213
|
const offset = Number(project.devPortOffset ?? 0) || 0
|
|
218
|
-
// The
|
|
219
|
-
// portable anywhere, so forward the lever as-is.
|
|
220
|
-
const studioEnv = { ELYTRA_DEV_PORT_OFFSET: String(offset) }
|
|
221
|
-
// The frontend's port is resolved HERE instead, and handed over as `PORT` (which
|
|
214
|
+
// The frontend's port is resolved HERE, once, and handed over as `PORT` (which
|
|
222
215
|
// Next reads from the process env natively). That keeps the scaffold's `dev`
|
|
223
216
|
// script a plain `next dev`: computing the port inside the npm script needs POSIX
|
|
224
217
|
// arithmetic (`$(( ))`), which silently breaks `pnpm dev` under cmd.exe on
|
|
225
218
|
// Windows — every other template script is shell-agnostic and this one stays so.
|
|
226
|
-
const
|
|
219
|
+
const frontendPort = DEV_FRONTEND_BASE_PORT + offset
|
|
220
|
+
// The studio's `vite.config.ts` resolves the offset itself, in JS (`loadEnv`) —
|
|
221
|
+
// portable anywhere, so forward the lever as-is. EC-583 — Q15 RATIFIED (EC-565
|
|
222
|
+
// Round 5, 5b): this process starts the frontend and is the one that KNOWS its
|
|
223
|
+
// port, so it hands the studio the dev frontend URL its live links use; the studio
|
|
224
|
+
// prefers it in a dev runtime only, and `elytra.config.ts` keeps reading no env.
|
|
225
|
+
const studioEnv = {
|
|
226
|
+
ELYTRA_DEV_PORT_OFFSET: String(offset),
|
|
227
|
+
...(project.apps.frontend ? { [DEV_FRONTEND_URL_ENV]: devFrontendUrl(offset) } : {}),
|
|
228
|
+
}
|
|
229
|
+
const frontendEnv = { ELYTRA_DEV_PORT_OFFSET: String(offset), PORT: String(frontendPort) }
|
|
227
230
|
|
|
228
231
|
if (project.apps.studio && project.mode === 'connected') {
|
|
229
232
|
if (project.convexProvisioned && !project.isProdDeployment) {
|
package/src/config-harness.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createRequire } from 'node:module'
|
|
|
3
3
|
import { createServer as createNetServer } from 'node:net'
|
|
4
4
|
import { join, relative, resolve, sep } from 'node:path'
|
|
5
5
|
import { pathToFileURL } from 'node:url'
|
|
6
|
+
import { WORKSPACE_CONFIG_FILE } from '@elytracms/core/config'
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* The consumer-Vite harness (EC-495) — the ONE mechanism by which this CLI
|
|
@@ -33,8 +34,8 @@ import { pathToFileURL } from 'node:url'
|
|
|
33
34
|
|
|
34
35
|
// --- Shared plumbing ---------------------------------------------------------------
|
|
35
36
|
|
|
36
|
-
/**
|
|
37
|
-
export const CONFIG_FILE =
|
|
37
|
+
/** The config file name at the project root — core's one declaration (EC-583, RESIDUE-4d). */
|
|
38
|
+
export const CONFIG_FILE = WORKSPACE_CONFIG_FILE
|
|
38
39
|
|
|
39
40
|
/** Resolve a module specifier from a directory's own node_modules world. */
|
|
40
41
|
export function resolveFromDir(dir, specifier) {
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { expectedDeploymentEnv } from '@elytracms/core/export-sync/deployment-ready'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Every check `elytra doctor` can print, declared ONCE (EC-599), in report order.
|
|
5
|
+
*
|
|
6
|
+
* The doctor's capabilities used to be written down three times by hand — the guide's
|
|
7
|
+
* table, `elytra --help` and `elytra doctor --help` — and two of them drifted: the help
|
|
8
|
+
* text still said redirect loops are "checked in the studio" and that the routes are read
|
|
9
|
+
* from `cms/index.ts`, long after EC-590 moved both. Now there is one list and three
|
|
10
|
+
* readers: `elytra doctor --help` ({@link formatDoctorChecksHelp}), the guide's check
|
|
11
|
+
* table (`docs/guide/06-cli-reference.md`, a region `pnpm check:guide` regenerates and
|
|
12
|
+
* fails on when stale — {@link formatDoctorChecksMarkdown}), and the report's bucket
|
|
13
|
+
* headings ({@link DOCTOR_BUCKET_TITLES}). `test/doctor-checks.test.js` runs the doctor
|
|
14
|
+
* and holds the ids it prints equal to this list, both directions, bucket by bucket — a
|
|
15
|
+
* check added without an entry, or an entry left behind by a removed check, fails there.
|
|
16
|
+
*
|
|
17
|
+
* Each entry: `id` (as printed; `env:*` stands for the one row per expected variable),
|
|
18
|
+
* `bucket`, `summary` (what it looks at — one sentence, printed by `--help`), and
|
|
19
|
+
* `outcome` (when it fails / warns / stays unknown — the guide prints it too).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/** The report's buckets, in report order, with the heading each prints under. */
|
|
23
|
+
export const DOCTOR_BUCKETS = [
|
|
24
|
+
{ bucket: 'deployment', title: 'Deployment' },
|
|
25
|
+
{ bucket: 'deployment-env', title: 'Deployment env' },
|
|
26
|
+
{ bucket: 'app', title: 'App' },
|
|
27
|
+
{ bucket: 'user', title: 'Per-user' },
|
|
28
|
+
{ bucket: 'previews', title: 'Previews' },
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
/** Bucket id → its heading in the printed report. */
|
|
32
|
+
export const DOCTOR_BUCKET_TITLES = Object.fromEntries(DOCTOR_BUCKETS.map(({ bucket, title }) => [bucket, title]))
|
|
33
|
+
|
|
34
|
+
export const DOCTOR_CHECKS = [
|
|
35
|
+
// --- deployment -----------------------------------------------------------------
|
|
36
|
+
{
|
|
37
|
+
id: 'backend-url',
|
|
38
|
+
bucket: 'deployment',
|
|
39
|
+
summary:
|
|
40
|
+
'A backend is configured: `VITE_CONVEX_URL` (or `CONVEX_URL`) in the studio env, read by the parser the studio runs, or `--url`.',
|
|
41
|
+
outcome:
|
|
42
|
+
'Fails with neither, and on a `VITE_ELYTRA_MODE` other than `playground`. A configured URL is diagnosed even when the studio itself runs playground.',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'backend-reachable',
|
|
46
|
+
bucket: 'deployment',
|
|
47
|
+
summary: 'The deployment answers its query API as a Convex deployment — not merely any HTTP response.',
|
|
48
|
+
outcome: 'Fails when it does not (a nonexistent deployment’s empty 404 included).',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: 'auth-signing-keys',
|
|
52
|
+
bucket: 'deployment',
|
|
53
|
+
summary:
|
|
54
|
+
'The deployment’s public JWKS endpoint serves a signing key set — definitive from node (the browser cannot read it: the endpoint sets no CORS).',
|
|
55
|
+
outcome: 'Fails when the deployment publishes no key set (`JWKS` / `JWT_PRIVATE_KEY` unset) — every sign-in fails.',
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'deployment-provisioned',
|
|
59
|
+
bucket: 'deployment',
|
|
60
|
+
summary: 'A deployment is wired for the active project.',
|
|
61
|
+
outcome: 'Passes whenever a backend URL is configured (a URL is a wired deployment); `unknown` with none.',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'functions-version',
|
|
65
|
+
bucket: 'deployment',
|
|
66
|
+
summary:
|
|
67
|
+
'The engine version the deployed functions were stamped with, against the `@elytracms/studio` your studio has installed.',
|
|
68
|
+
outcome:
|
|
69
|
+
'Fails when they differ or the stamp is missing; `unknown` when the installed studio version cannot be read here.',
|
|
70
|
+
},
|
|
71
|
+
// --- deployment env -------------------------------------------------------------
|
|
72
|
+
{
|
|
73
|
+
id: 'env:*',
|
|
74
|
+
bucket: 'deployment-env',
|
|
75
|
+
summary:
|
|
76
|
+
'One row per expected variable, by name only — the deployment’s (`npx convex env list` on the linked deployment) and, when the studio mounts an AI route, the studio host’s provider key. No value reaches the report.',
|
|
77
|
+
outcome:
|
|
78
|
+
'A required variable unset fails, a recommended one warns; a half-set group (one of the revalidate pair) fails. `unknown` with no linked deployment, or under `--url`.',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'revalidate-probe',
|
|
82
|
+
bucket: 'deployment-env',
|
|
83
|
+
summary:
|
|
84
|
+
'When the revalidate pair is set: a signed no-op `probe` event POSTed to the endpoint, proving the publish → live chain end to end.',
|
|
85
|
+
outcome:
|
|
86
|
+
'Fails on any answer but the route’s `200 {"revalidated":[]}` — a secret mismatch or an unset frontend secret (401), a frontend on another Elytra version (400), no route (404). Warns when the endpoint cannot be reached from this machine.',
|
|
87
|
+
},
|
|
88
|
+
// --- app ------------------------------------------------------------------------
|
|
89
|
+
{
|
|
90
|
+
id: 'config-valid',
|
|
91
|
+
bucket: 'app',
|
|
92
|
+
summary:
|
|
93
|
+
'`elytra.config.ts` executed through your studio’s own Vite (needs the studio’s dependencies installed) and run through the one resolver the studio boots through.',
|
|
94
|
+
outcome:
|
|
95
|
+
'Fails with every located issue (`projects.0.slug: …`); `unknown`, with the reason, when it cannot be executed here. The checks below that read the config are `unknown` then too.',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: 'asset-collection',
|
|
99
|
+
bucket: 'app',
|
|
100
|
+
summary: 'A project whose collections declare asset fields resolves an asset collection.',
|
|
101
|
+
outcome: 'Fails when none resolves.',
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: 'presentation-targets',
|
|
105
|
+
bucket: 'app',
|
|
106
|
+
summary: 'Every collection’s `presentation.body` names fields the collection declares.',
|
|
107
|
+
outcome: 'Fails on a name the collection does not have.',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: 'published-orphans',
|
|
111
|
+
bucket: 'app',
|
|
112
|
+
summary:
|
|
113
|
+
'Published documents of collections the config does not declare, read from the deployment’s public delivery snapshot.',
|
|
114
|
+
outcome: 'Warns, naming each collection and count. Printed only against a deployment, with the config resolved.',
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: 'project-layout',
|
|
118
|
+
bucket: 'app',
|
|
119
|
+
summary:
|
|
120
|
+
'The studio, frontend and theme locations the CLI resolved from `package.json#elytra`, and whether each was declared or defaulted.',
|
|
121
|
+
outcome: 'Fails on an invalid section or a declared path that does not exist.',
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: 'ai-route',
|
|
125
|
+
bucket: 'app',
|
|
126
|
+
summary:
|
|
127
|
+
'The AI route your studio’s `vite.config.ts` mounts (`createAiDevPlugin`, read from the loaded config) — it decides whether an AI provider key is expected.',
|
|
128
|
+
outcome: 'Fails when the mounted `handlerModulePath` does not exist.',
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: 'route-conflicts',
|
|
132
|
+
bucket: 'app',
|
|
133
|
+
summary:
|
|
134
|
+
'Two routes tied on specificity, locale scope and shape — the one router’s issue set (the same set the studio and the delivery host read), over the routes your config resolved.',
|
|
135
|
+
outcome: 'Fails on a tie; `unknown` when the config could not be resolved here (as are the next three).',
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: 'mount-conflicts',
|
|
139
|
+
bucket: 'app',
|
|
140
|
+
summary: 'Two hierarchy mounts on the same path in the same locale scope.',
|
|
141
|
+
outcome: 'Fails on a tie.',
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: 'unreachable-routes',
|
|
145
|
+
bucket: 'app',
|
|
146
|
+
summary:
|
|
147
|
+
'A declaration that can never serve content — an all-dynamic route such as `/:slug` shadowing a mount’s whole depth, a mount on a collection with no parent role, a catch-all with a dynamic prefix like `/:lang/:path*`.',
|
|
148
|
+
outcome: 'Fails on such a declaration; warns on a precedence that depends on content.',
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: 'redirect-loops',
|
|
152
|
+
bucket: 'app',
|
|
153
|
+
summary:
|
|
154
|
+
'The deployment’s real redirects — the enabled ones its public delivery snapshot serves, read on the same request as `published-orphans`.',
|
|
155
|
+
outcome: 'Fails on a loop or a duplicate `from` (`/old` = `/old/`); `unknown` when no deployment was read.',
|
|
156
|
+
},
|
|
157
|
+
// --- per-user -------------------------------------------------------------------
|
|
158
|
+
{
|
|
159
|
+
id: 'authenticated',
|
|
160
|
+
bucket: 'user',
|
|
161
|
+
summary: 'The signed-in session.',
|
|
162
|
+
outcome: 'Always `unknown` here — the studio boot checks it.',
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: 'member-role',
|
|
166
|
+
bucket: 'user',
|
|
167
|
+
summary: 'The signed-in user’s membership of the project.',
|
|
168
|
+
outcome: 'Always `unknown` here — the studio boot checks it.',
|
|
169
|
+
},
|
|
170
|
+
// --- previews -------------------------------------------------------------------
|
|
171
|
+
{
|
|
172
|
+
id: 'previews-artifacts',
|
|
173
|
+
bucket: 'previews',
|
|
174
|
+
summary: 'Printed alone when the studio holds no generated previews at all (they are optional).',
|
|
175
|
+
outcome: 'Always ok.',
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: 'previews-manifest',
|
|
179
|
+
bucket: 'previews',
|
|
180
|
+
summary: 'The generated previews manifest (`<studio>/elytra-previews.json`) is present and parses.',
|
|
181
|
+
outcome: 'Warns otherwise.',
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: 'previews-files',
|
|
185
|
+
bucket: 'previews',
|
|
186
|
+
summary: 'Every manifest entry’s image file is present.',
|
|
187
|
+
outcome: 'Warns, naming the first missing file.',
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: 'previews-orphans',
|
|
191
|
+
bucket: 'previews',
|
|
192
|
+
summary: 'Image files no manifest entry references.',
|
|
193
|
+
outcome: 'Warns, naming the first.',
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: 'previews-staleness',
|
|
197
|
+
bucket: 'previews',
|
|
198
|
+
summary: 'Input-hash staleness needs the component registry, so it is not judged here.',
|
|
199
|
+
outcome: 'Always `unknown` — run `elytra previews --check`.',
|
|
200
|
+
},
|
|
201
|
+
]
|
|
202
|
+
|
|
203
|
+
/** Is a printed check id the catalogue entry `entry`? (`env:*` covers every `env:<NAME>` row.) */
|
|
204
|
+
export function isCheckOf(entry, id) {
|
|
205
|
+
return entry.id.endsWith(':*') ? id.startsWith(entry.id.slice(0, -1)) : entry.id === id
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** The expected deployment variables, read from core's one declaration (every one the doctor may list). */
|
|
209
|
+
function expectedEnvNames() {
|
|
210
|
+
return expectedDeploymentEnv({ aiRouteConfigured: true }).map((expected) =>
|
|
211
|
+
expected.anyOf ? expected.anyOf.join(' / ') : expected.name,
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Wrap prose to `width` columns, every line prefixed with `indent`. */
|
|
216
|
+
function wrap(text, indent, width = 80) {
|
|
217
|
+
const lines = []
|
|
218
|
+
let line = ''
|
|
219
|
+
for (const word of text.split(/\s+/)) {
|
|
220
|
+
if (line && indent.length + line.length + 1 + word.length > width) {
|
|
221
|
+
lines.push(indent + line)
|
|
222
|
+
line = word
|
|
223
|
+
} else {
|
|
224
|
+
line = line ? `${line} ${word}` : word
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (line) lines.push(indent + line)
|
|
228
|
+
return lines.join('\n')
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** The check list `elytra doctor --help` prints, grouped by bucket. Pure. */
|
|
232
|
+
export function formatDoctorChecksHelp() {
|
|
233
|
+
const out = []
|
|
234
|
+
for (const { bucket, title } of DOCTOR_BUCKETS) {
|
|
235
|
+
out.push(` ${title}:`)
|
|
236
|
+
for (const check of DOCTOR_CHECKS.filter((entry) => entry.bucket === bucket)) {
|
|
237
|
+
out.push(` ${check.id}`)
|
|
238
|
+
const summary =
|
|
239
|
+
check.id === 'env:*' ? `${check.summary} Expected: ${expectedEnvNames().join(', ')}.` : check.summary
|
|
240
|
+
out.push(wrap(summary.replace(/`/g, ''), ' '))
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return out.join('\n')
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** The guide's check table (`docs/guide/06-cli-reference.md`, a generated region). Pure. */
|
|
247
|
+
export function formatDoctorChecksMarkdown() {
|
|
248
|
+
const out = ['| Bucket | Check | What it looks at | Outcome |', '| --- | --- | --- | --- |']
|
|
249
|
+
for (const { bucket, title } of DOCTOR_BUCKETS) {
|
|
250
|
+
DOCTOR_CHECKS.filter((entry) => entry.bucket === bucket).forEach((check, index) => {
|
|
251
|
+
const summary =
|
|
252
|
+
check.id === 'env:*'
|
|
253
|
+
? `${check.summary} Expected: ${expectedEnvNames()
|
|
254
|
+
.map((name) => `\`${name}\``)
|
|
255
|
+
.join(', ')}.`
|
|
256
|
+
: check.summary
|
|
257
|
+
out.push(`| ${index === 0 ? `**${title}**` : ''} | \`${check.id}\` | ${summary} | ${check.outcome} |`)
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
return out.join('\n')
|
|
261
|
+
}
|