@reddoorla/maintenance 0.9.0 → 0.10.1
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 +81 -10
- package/dist/cli/bin.js +180 -7
- package/dist/cli/bin.js.map +1 -1
- package/dist/index.d.ts +60 -3
- package/dist/index.js +124 -9
- package/dist/index.js.map +1 -1
- package/dist/recipes/sync-configs.d.ts +1 -1
- package/dist/{sync-configs-pMPW4wTq.d.ts → sync-configs-Dn6KhjrW.d.ts} +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,19 +15,26 @@ pnpm reddoor-maint --help
|
|
|
15
15
|
|
|
16
16
|
A reddoor site goes through this sequence the first time you adopt the package. Each recipe is idempotent — running it again on an already-onboarded site is a `noop`.
|
|
17
17
|
|
|
18
|
+
```bash
|
|
19
|
+
pnpm reddoor-maint init
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
`init` runs the whole chain in order against the current directory (or `init [site]` for an explicit path). It's a thin orchestrator — every underlying recipe still creates its own branch and stops on a dirty tree, so the operator ends up with a stack of `maint/<recipe>-<ts>` branches to PR.
|
|
23
|
+
|
|
18
24
|
```text
|
|
19
|
-
convert-to-pnpm → onboard → sync-configs → svelte-codemods → audit
|
|
25
|
+
convert-to-pnpm → onboard → sync-configs → svelte-codemods → a11y-fixtures-page → audit
|
|
20
26
|
```
|
|
21
27
|
|
|
22
|
-
| Step | Recipe
|
|
23
|
-
| ---- |
|
|
24
|
-
| 1 | `convert-to-pnpm`
|
|
25
|
-
| 2 | `onboard`
|
|
26
|
-
| 3 | `sync-configs`
|
|
27
|
-
| 4 | `svelte-codemods`
|
|
28
|
-
| 5 | `
|
|
28
|
+
| Step | Recipe | What it does |
|
|
29
|
+
| ---- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
30
|
+
| 1 | `convert-to-pnpm` | Removes `package-lock.json` / `yarn.lock`, pins `packageManager: pnpm@…`, rewrites `npm` references in scripts, runs `pnpm install` to materialise `pnpm-lock.yaml`. |
|
|
31
|
+
| 2 | `onboard` | Installs `@reddoorla/maintenance` + the audit deps (`@lhci/cli`, `@playwright/test`, `@axe-core/playwright`) on the site. Pins the maintenance dep to a caret range against this package's own version at runtime. |
|
|
32
|
+
| 3 | `sync-configs` | Writes the canonical config templates into the site (eslint, prettier, lighthouserc, playwright config, svelte config) and merges canonical entries into `.gitignore`. |
|
|
33
|
+
| 4 | `svelte-codemods` | Optional cleanup pass applying the Svelte 5 gotcha codemods (`export let` → `$props()`, `on:event` → `onevent`, `$:` → `$derived`/`$effect`, etc.) for sites that surface new strictness warnings after the original upgrade. |
|
|
34
|
+
| 5 | `a11y-fixtures-page` | Writes a starter `src/routes/dev/a11y-fixtures/+page.svelte` if the route doesn't already exist. The hardcoded URL in `lighthouse` and `playwright-a11y` configs targets this route. Operator edits are never clobbered. |
|
|
35
|
+
| 6 | `audit` | Runs `deps`, `lighthouse`, `a11y`, `security`, `lint` — see [Audits](#audits). |
|
|
29
36
|
|
|
30
|
-
Each recipe refuses to run on a dirty working tree, creates a fresh `maint/<recipe>-<UTC-ms-timestamp>` branch, and emits one or more atomic commits.
|
|
37
|
+
Each recipe refuses to run on a dirty working tree, creates a fresh `maint/<recipe>-<UTC-ms-timestamp>` branch, and emits one or more atomic commits. Running individual steps standalone (e.g. `reddoor-maint sync-configs`) still works — `init` is for when you want the whole chain in one command.
|
|
31
38
|
|
|
32
39
|
---
|
|
33
40
|
|
|
@@ -37,6 +44,7 @@ Each recipe refuses to run on a dirty working tree, creates a fresh `maint/<reci
|
|
|
37
44
|
reddoor-maint list-audits # audit descriptions
|
|
38
45
|
reddoor-maint list-recipes # recipe descriptions
|
|
39
46
|
|
|
47
|
+
reddoor-maint init [site] # full onboarding chain (preferred entrypoint)
|
|
40
48
|
reddoor-maint audit [site] # run audits
|
|
41
49
|
reddoor-maint sync-configs [site]
|
|
42
50
|
reddoor-maint bump-deps [site]
|
|
@@ -72,11 +80,13 @@ Each recipe is `(site, opts?) => Promise<RecipeResult>` and is exported from the
|
|
|
72
80
|
|
|
73
81
|
```ts
|
|
74
82
|
import {
|
|
83
|
+
init,
|
|
75
84
|
syncConfigs,
|
|
76
85
|
bumpDeps,
|
|
77
86
|
onboard,
|
|
78
87
|
convertToPnpm,
|
|
79
88
|
svelteCodemods,
|
|
89
|
+
a11yFixturesPage,
|
|
80
90
|
upgradeSvelte4to5,
|
|
81
91
|
} from "@reddoorla/maintenance";
|
|
82
92
|
```
|
|
@@ -113,6 +123,14 @@ Standalone codemod pass for sites already on Svelte 5. Applies the same gotcha c
|
|
|
113
123
|
|
|
114
124
|
The full 7-step Svelte 4 → 5 migration: bump framework versions, migrate `svelte.config.js`, run the official `svelte-migrate` codemod, run `@tailwindcss/upgrade`, apply gotcha codemods over `src/**/*.svelte`, verify with `pnpm install` + `pnpm run check`, and write a `MIGRATION_SVELTE_5.md` summary. Each step is its own commit; the file leaves a record of what ran and what may need manual review.
|
|
115
125
|
|
|
126
|
+
### `a11y-fixtures-page`
|
|
127
|
+
|
|
128
|
+
Writes a starter `src/routes/dev/a11y-fixtures/+page.svelte` if the route doesn't already exist. The `lighthouse` and `playwright-a11y` configs both target this URL — newly-onboarded sites need the route to exist for either audit to pass. The template is intentionally generic (semantic landmarks + headings + a relative link); operator edits to an existing page are never clobbered.
|
|
129
|
+
|
|
130
|
+
### `init`
|
|
131
|
+
|
|
132
|
+
One-shot guided onboarding: runs `convert-to-pnpm → onboard → sync-configs → svelte-codemods → a11y-fixtures-page → audit` in sequence against a site. Each underlying recipe still creates its own branch — `init` is a thin orchestrator, not a branch-collapser. Stops the chain on the first `failed` recipe or uncaught error; `noop` results continue the chain. Exit code is 1 if any step failed _or_ the final audit pass reports a `fail`.
|
|
133
|
+
|
|
116
134
|
---
|
|
117
135
|
|
|
118
136
|
## Audits
|
|
@@ -296,7 +314,60 @@ RESEND_WEBHOOK_SECRET=whsec_XXXX # only for the deployed webhook
|
|
|
296
314
|
|
|
297
315
|
Renders + sends every Reports row with `Draft ready=true && Approved to send=true && Sent at IS NULL`. Stamps `Sent at` + `Delivery status=pending` on each.
|
|
298
316
|
|
|
299
|
-
5. **Delivery status updates automatically** via the Resend webhook (Netlify Function at `netlify/functions/resend-webhook.mts`) — `Delivery status` flips to `delivered` / `bounced` / `complained` as events arrive.
|
|
317
|
+
5. **Delivery status updates automatically** via the Resend webhook (Netlify Function at `netlify/functions/resend-webhook.mts`) — `Delivery status` flips to `delivered` / `bounced` / `complained` as events arrive. Deploy procedure: see [Webhook deployment](#webhook-deployment-netlify--resend) below.
|
|
318
|
+
|
|
319
|
+
### Webhook deployment (Netlify + Resend)
|
|
320
|
+
|
|
321
|
+
The Resend delivery webhook lives at `netlify/functions/resend-webhook.mts` in this repo. Deploying it is a one-time operation per environment: connect the repo to a Netlify site, set three env vars, register the deployed URL with Resend.
|
|
322
|
+
|
|
323
|
+
**1. Create the Netlify site:**
|
|
324
|
+
|
|
325
|
+
- New site → Import from Git → pick `tucksravin/reddoor-maintenance`.
|
|
326
|
+
- Branch to deploy: `main`. Build settings are read from [`netlify.toml`](netlify.toml) (no build command needed — functions-only).
|
|
327
|
+
- Site name: pick something stable (e.g. `reddoor-webhooks`) since it ends up in the public webhook URL.
|
|
328
|
+
|
|
329
|
+
**2. Set env vars** in Site settings → Environment variables:
|
|
330
|
+
|
|
331
|
+
| Variable | Value |
|
|
332
|
+
| ----------------------- | --------------------------------------------------------------------------- |
|
|
333
|
+
| `AIRTABLE_PAT` | Same PAT used by the CLI (read+write on the Websites + Reports tables) |
|
|
334
|
+
| `AIRTABLE_BASE_ID` | `appHG8nLOzULzXOER` |
|
|
335
|
+
| `RESEND_WEBHOOK_SECRET` | Generated by Resend in step 4 — paste back here after creating the endpoint |
|
|
336
|
+
|
|
337
|
+
**3. Trigger a deploy** (Deploys → Trigger deploy → Deploy site). When it goes green, curl the health endpoint:
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
curl https://<your-site>.netlify.app/.netlify/functions/resend-webhook
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Expected:
|
|
344
|
+
|
|
345
|
+
```json
|
|
346
|
+
{
|
|
347
|
+
"status": "ok",
|
|
348
|
+
"service": "reddoor-resend-webhook",
|
|
349
|
+
"env": {
|
|
350
|
+
"RESEND_WEBHOOK_SECRET": true,
|
|
351
|
+
"AIRTABLE_PAT": true,
|
|
352
|
+
"AIRTABLE_BASE_ID": true
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
Any `false` means the env var didn't land — re-check step 2 and re-deploy. The health endpoint reports presence-only and never returns secret values, so the output is safe to share in a support ticket.
|
|
358
|
+
|
|
359
|
+
**4. Register the webhook in Resend:** [resend.com/webhooks](https://resend.com/webhooks) → Add Endpoint → paste the function URL from step 3 → select events `email.delivered`, `email.bounced`, `email.complained` → save. Copy the generated **Signing Secret** (`whsec_…`) into Netlify's `RESEND_WEBHOOK_SECRET` env var (step 2) and trigger another deploy so the secret takes effect.
|
|
360
|
+
|
|
361
|
+
**5. End-to-end smoke test (using ERP Industrials):**
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
# from your CLI:
|
|
365
|
+
reddoor-maint report erp-industrials # drafts a Maintenance report → Airtable
|
|
366
|
+
# approve the draft in Airtable, then:
|
|
367
|
+
reddoor-maint report --send-ready
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
Within a few minutes, watch the ERP Reports row's `Delivery status` flip from `pending` → `delivered` as Resend pings the webhook. Function logs in Netlify show each event with its messageId + new status. If the webhook returns 5xx, svix retries; check the Netlify function log for the error envelope.
|
|
300
371
|
|
|
301
372
|
### Frequency math
|
|
302
373
|
|
package/dist/cli/bin.js
CHANGED
|
@@ -735,7 +735,7 @@ var init_orchestrate = __esm({
|
|
|
735
735
|
});
|
|
736
736
|
|
|
737
737
|
// src/cli/bin.ts
|
|
738
|
-
import { dirname as
|
|
738
|
+
import { dirname as dirname5 } from "path";
|
|
739
739
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
740
740
|
import { cac } from "cac";
|
|
741
741
|
|
|
@@ -744,7 +744,7 @@ import { resolve as resolve2 } from "path";
|
|
|
744
744
|
|
|
745
745
|
// src/audits/util/spawn.ts
|
|
746
746
|
import { spawn } from "child_process";
|
|
747
|
-
var defaultSpawn = (cmd, args, opts = {}) => new Promise((
|
|
747
|
+
var defaultSpawn = (cmd, args, opts = {}) => new Promise((resolve10, reject) => {
|
|
748
748
|
const streaming = opts.streaming === true;
|
|
749
749
|
const child = spawn(cmd, [...args], {
|
|
750
750
|
cwd: opts.cwd,
|
|
@@ -767,7 +767,7 @@ var defaultSpawn = (cmd, args, opts = {}) => new Promise((resolve9, reject) => {
|
|
|
767
767
|
});
|
|
768
768
|
child.on("close", (code) => {
|
|
769
769
|
if (timer) clearTimeout(timer);
|
|
770
|
-
|
|
770
|
+
resolve10({ code: code ?? -1, stdout, stderr });
|
|
771
771
|
});
|
|
772
772
|
});
|
|
773
773
|
|
|
@@ -3322,12 +3322,174 @@ async function runSingleSiteDraft(slug, opts) {
|
|
|
3322
3322
|
return { output: `Draft created: ${result.reportRow?.reportId}`, code: 0 };
|
|
3323
3323
|
}
|
|
3324
3324
|
|
|
3325
|
+
// src/cli/commands/init.ts
|
|
3326
|
+
import { resolve as resolve9 } from "path";
|
|
3327
|
+
|
|
3328
|
+
// src/recipes/a11y-fixtures-page/index.ts
|
|
3329
|
+
import { access, mkdir as mkdir3, writeFile as writeFile10 } from "fs/promises";
|
|
3330
|
+
import { dirname as dirname4, join as join22 } from "path";
|
|
3331
|
+
|
|
3332
|
+
// src/recipes/a11y-fixtures-page/template.ts
|
|
3333
|
+
var A11Y_FIXTURES_PAGE_RELATIVE = "src/routes/dev/a11y-fixtures/+page.svelte";
|
|
3334
|
+
var A11Y_FIXTURES_PAGE_TEMPLATE = `<svelte:head>
|
|
3335
|
+
<title>a11y fixtures \u2014 Reddoor</title>
|
|
3336
|
+
<meta
|
|
3337
|
+
name="description"
|
|
3338
|
+
content="Reddoor accessibility fixtures \u2014 semantic landmarks, heading hierarchy, and a stable target for @lhci/cli and Playwright + axe-core coverage. Not linked from the public site."
|
|
3339
|
+
/>
|
|
3340
|
+
</svelte:head>
|
|
3341
|
+
|
|
3342
|
+
<main>
|
|
3343
|
+
<header>
|
|
3344
|
+
<h1>Accessibility fixtures</h1>
|
|
3345
|
+
<p>
|
|
3346
|
+
This page exists so <code>@lhci/cli</code> and Playwright + axe-core have a
|
|
3347
|
+
stable target with predictable a11y characteristics. It is not linked from
|
|
3348
|
+
the public site.
|
|
3349
|
+
</p>
|
|
3350
|
+
</header>
|
|
3351
|
+
|
|
3352
|
+
<section aria-labelledby="landmarks-heading">
|
|
3353
|
+
<h2 id="landmarks-heading">Landmarks</h2>
|
|
3354
|
+
<p>
|
|
3355
|
+
A single <code>main</code> wraps the page; sections each declare
|
|
3356
|
+
<code>aria-labelledby</code> matched to their heading id so screen readers
|
|
3357
|
+
and axe both see a clean outline.
|
|
3358
|
+
</p>
|
|
3359
|
+
</section>
|
|
3360
|
+
|
|
3361
|
+
<section aria-labelledby="links-heading">
|
|
3362
|
+
<h2 id="links-heading">Links</h2>
|
|
3363
|
+
<p>
|
|
3364
|
+
<a href="/">Back to home</a> \u2014 relative link with descriptive visible text,
|
|
3365
|
+
so no <code>aria-label</code> override is needed.
|
|
3366
|
+
</p>
|
|
3367
|
+
</section>
|
|
3368
|
+
</main>
|
|
3369
|
+
`;
|
|
3370
|
+
|
|
3371
|
+
// src/recipes/a11y-fixtures-page/index.ts
|
|
3372
|
+
async function fileExists(path) {
|
|
3373
|
+
try {
|
|
3374
|
+
await access(path);
|
|
3375
|
+
return true;
|
|
3376
|
+
} catch {
|
|
3377
|
+
return false;
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3380
|
+
async function a11yFixturesPage(site) {
|
|
3381
|
+
const target = join22(site.path, A11Y_FIXTURES_PAGE_RELATIVE);
|
|
3382
|
+
return withRecipe({
|
|
3383
|
+
name: "a11y-fixtures-page",
|
|
3384
|
+
site,
|
|
3385
|
+
plan: async () => {
|
|
3386
|
+
if (await fileExists(target)) {
|
|
3387
|
+
return { kind: "noop", notes: `${A11Y_FIXTURES_PAGE_RELATIVE} already exists` };
|
|
3388
|
+
}
|
|
3389
|
+
return { kind: "apply", plan: { target } };
|
|
3390
|
+
},
|
|
3391
|
+
apply: async (planned, { commit: commit2 }) => {
|
|
3392
|
+
await mkdir3(dirname4(planned.target), { recursive: true });
|
|
3393
|
+
await writeFile10(planned.target, A11Y_FIXTURES_PAGE_TEMPLATE, "utf-8");
|
|
3394
|
+
await commit2("feat: add /dev/a11y-fixtures starter route");
|
|
3395
|
+
return { kind: "ok" };
|
|
3396
|
+
}
|
|
3397
|
+
});
|
|
3398
|
+
}
|
|
3399
|
+
|
|
3400
|
+
// src/recipes/init.ts
|
|
3401
|
+
function recipeStep(name, fn) {
|
|
3402
|
+
return {
|
|
3403
|
+
name,
|
|
3404
|
+
run: async (site) => ({ kind: "recipe", result: await fn(site) })
|
|
3405
|
+
};
|
|
3406
|
+
}
|
|
3407
|
+
var DEFAULT_INIT_STEPS = [
|
|
3408
|
+
recipeStep("convert-to-pnpm", convertToPnpm),
|
|
3409
|
+
recipeStep("onboard", onboard),
|
|
3410
|
+
recipeStep("sync-configs", syncConfigs),
|
|
3411
|
+
recipeStep("svelte-codemods", svelteCodemods),
|
|
3412
|
+
recipeStep("a11y-fixtures-page", a11yFixturesPage),
|
|
3413
|
+
{
|
|
3414
|
+
name: "audit",
|
|
3415
|
+
run: async (site) => ({ kind: "audit", results: await runAudits(site) })
|
|
3416
|
+
}
|
|
3417
|
+
];
|
|
3418
|
+
async function init(site, opts = {}) {
|
|
3419
|
+
const steps = opts.steps ?? DEFAULT_INIT_STEPS;
|
|
3420
|
+
const out = [];
|
|
3421
|
+
for (const step of steps) {
|
|
3422
|
+
let result;
|
|
3423
|
+
try {
|
|
3424
|
+
result = await step.run(site);
|
|
3425
|
+
} catch (err) {
|
|
3426
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
3427
|
+
out.push({ name: step.name, result: { kind: "error", message } });
|
|
3428
|
+
return { site: siteLabel(site), steps: out, complete: false };
|
|
3429
|
+
}
|
|
3430
|
+
out.push({ name: step.name, result });
|
|
3431
|
+
if (result.kind === "recipe" && result.result.status === "failed") {
|
|
3432
|
+
return { site: siteLabel(site), steps: out, complete: false };
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
return { site: siteLabel(site), steps: out, complete: true };
|
|
3436
|
+
}
|
|
3437
|
+
|
|
3438
|
+
// src/cli/commands/init.ts
|
|
3439
|
+
function formatStep(name, r) {
|
|
3440
|
+
if (r.kind === "error") return `${name.padEnd(20)} error: ${r.message}`;
|
|
3441
|
+
if (r.kind === "audit") {
|
|
3442
|
+
const lines = r.results.map(
|
|
3443
|
+
(a) => ` ${a.audit.padEnd(12)} ${a.status.padEnd(5)} ${a.summary}`
|
|
3444
|
+
);
|
|
3445
|
+
return `${name.padEnd(20)} ${r.results.length} audit(s):
|
|
3446
|
+
${lines.join("\n")}`;
|
|
3447
|
+
}
|
|
3448
|
+
const rec = r.result;
|
|
3449
|
+
if (rec.status === "noop") return `${name.padEnd(20)} noop${rec.notes ? ` \u2014 ${rec.notes}` : ""}`;
|
|
3450
|
+
if (rec.status === "failed")
|
|
3451
|
+
return `${name.padEnd(20)} failed${rec.notes ? ` \u2014 ${rec.notes}` : ""}`;
|
|
3452
|
+
return `${name.padEnd(20)} applied (${rec.commits.length} commit${rec.commits.length === 1 ? "" : "s"})${rec.notes ? ` \u2014 ${rec.notes}` : ""}`;
|
|
3453
|
+
}
|
|
3454
|
+
function formatResult7(r) {
|
|
3455
|
+
const header = `[${r.site}] init \u2014 ${r.complete ? "complete" : "STOPPED"}`;
|
|
3456
|
+
const body = r.steps.map((s) => formatStep(s.name, s.result)).join("\n");
|
|
3457
|
+
return `${header}
|
|
3458
|
+
${body}`;
|
|
3459
|
+
}
|
|
3460
|
+
function exitCodeFor(r) {
|
|
3461
|
+
if (!r.complete) return 1;
|
|
3462
|
+
for (const step of r.steps) {
|
|
3463
|
+
if (step.result.kind === "audit" && step.result.results.some((a) => a.status === "fail")) {
|
|
3464
|
+
return 1;
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
return 0;
|
|
3468
|
+
}
|
|
3469
|
+
async function runInitCommand(site, opts) {
|
|
3470
|
+
const cwd = opts.cwd ? resolve9(opts.cwd) : process.cwd();
|
|
3471
|
+
let sites = await resolveSites({
|
|
3472
|
+
...site !== void 0 ? { site } : {},
|
|
3473
|
+
...opts.fleet !== void 0 ? { fleet: opts.fleet } : {},
|
|
3474
|
+
cwd
|
|
3475
|
+
});
|
|
3476
|
+
if (opts.fleet) {
|
|
3477
|
+
const workdir = opts.workdir ?? `${process.env.HOME ?? ""}/.reddoor-maint/sites`;
|
|
3478
|
+
sites = await Promise.all(sites.map((s) => cloneIfNeeded(s, { workdir })));
|
|
3479
|
+
}
|
|
3480
|
+
const results = [];
|
|
3481
|
+
for (const s of sites) results.push(await init(s));
|
|
3482
|
+
const output = results.map(formatResult7).join("\n\n");
|
|
3483
|
+
const code = results.some((r) => exitCodeFor(r) !== 0) ? 1 : 0;
|
|
3484
|
+
return { output, code };
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3325
3487
|
// src/cli/version.ts
|
|
3326
3488
|
import { readFileSync as readFileSync2 } from "fs";
|
|
3327
|
-
import { join as
|
|
3489
|
+
import { join as join23 } from "path";
|
|
3328
3490
|
function resolvePackageVersion(fromDir) {
|
|
3329
3491
|
try {
|
|
3330
|
-
const raw = readFileSync2(
|
|
3492
|
+
const raw = readFileSync2(join23(fromDir, "..", "..", "package.json"), "utf-8");
|
|
3331
3493
|
const pkg = JSON.parse(raw);
|
|
3332
3494
|
return pkg.version ?? "unknown";
|
|
3333
3495
|
} catch {
|
|
@@ -3336,7 +3498,7 @@ function resolvePackageVersion(fromDir) {
|
|
|
3336
3498
|
}
|
|
3337
3499
|
|
|
3338
3500
|
// src/cli/bin.ts
|
|
3339
|
-
var here2 =
|
|
3501
|
+
var here2 = dirname5(fileURLToPath3(import.meta.url));
|
|
3340
3502
|
var version = resolvePackageVersion(here2);
|
|
3341
3503
|
var AUDIT_DESCRIPTIONS = {
|
|
3342
3504
|
deps: "Diff site package.json against the bundled baseline version map.",
|
|
@@ -3351,7 +3513,9 @@ var RECIPE_DESCRIPTIONS = {
|
|
|
3351
3513
|
"svelte-4-to-5": "Run the 7-commit Svelte 4 \u2192 5 upgrade recipe.",
|
|
3352
3514
|
"svelte-codemods": "Apply Svelte 5 gotcha codemods to an already-migrated site (state_referenced_locally, etc.).",
|
|
3353
3515
|
"convert-to-pnpm": "Convert an npm/yarn site to pnpm (lockfile, packageManager, scripts).",
|
|
3354
|
-
onboard: "Install @reddoorla/maintenance + audit deps on a site (preferred first step)."
|
|
3516
|
+
onboard: "Install @reddoorla/maintenance + audit deps on a site (preferred first step).",
|
|
3517
|
+
"a11y-fixtures-page": "Write src/routes/dev/a11y-fixtures/+page.svelte (stub for lhci + axe targets).",
|
|
3518
|
+
init: "Run the full onboarding chain (convert-to-pnpm \u2192 onboard \u2192 sync-configs \u2192 svelte-codemods \u2192 a11y-fixtures-page \u2192 audit)."
|
|
3355
3519
|
};
|
|
3356
3520
|
async function runOrExit(fn, opts) {
|
|
3357
3521
|
try {
|
|
@@ -3428,6 +3592,15 @@ cli.command(
|
|
|
3428
3592
|
).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
|
|
3429
3593
|
async (site, opts) => runOrExit(() => runOnboardCommand(site, opts), opts)
|
|
3430
3594
|
);
|
|
3595
|
+
cli.command(
|
|
3596
|
+
"init [site]",
|
|
3597
|
+
"One-shot guided onboarding: convert-to-pnpm \u2192 onboard \u2192 sync-configs \u2192 svelte-codemods \u2192 a11y-fixtures-page \u2192 audit."
|
|
3598
|
+
).option(
|
|
3599
|
+
"--fleet <inventory>",
|
|
3600
|
+
'Inventory file (.json or .mjs/.js), or "airtable" to read from Websites table'
|
|
3601
|
+
).option("--workdir <path>", "Clone target for fleet mode (default ~/.reddoor-maint/sites)").action(
|
|
3602
|
+
async (site, opts) => runOrExit(() => runInitCommand(site, opts), opts)
|
|
3603
|
+
);
|
|
3431
3604
|
cli.command("report [site]", "Draft or send maintenance/testing reports.").option("--due", "Scan all Websites and draft overdue reports.").option(
|
|
3432
3605
|
"--preview",
|
|
3433
3606
|
"Single-site dry run; writes reports/<slug>/draft.html, never touches Airtable."
|