@reddoorla/maintenance 0.54.0 → 0.54.2
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 +14 -9
- package/dist/cli/bin.js +24 -281
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.js +1 -1
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/forms/index.d.ts +8 -3
- package/dist/forms/index.js +2 -2
- package/dist/forms/index.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -294,6 +294,8 @@ RESEND_API_KEY=re_XXXX
|
|
|
294
294
|
RESEND_WEBHOOK_SECRET=whsec_XXXX # only for the deployed webhook
|
|
295
295
|
```
|
|
296
296
|
|
|
297
|
+
> The above is the **report CLI** env. The **deployed Netlify functions** (dashboard + form-ingest) additionally require `TURSO_DATABASE_URL` (+ optional `TURSO_AUTH_TOKEN`) — submissions live in libSQL/Turso, not Airtable. See [Site deployment](#site-deployment-netlify--resend) for the full env table.
|
|
298
|
+
|
|
297
299
|
### Operator flow
|
|
298
300
|
|
|
299
301
|
0. **Prereq: refresh Lighthouse scores on each Websites row.** From each site's checkout:
|
|
@@ -334,25 +336,28 @@ RESEND_WEBHOOK_SECRET=whsec_XXXX # only for the deployed webhook
|
|
|
334
336
|
|
|
335
337
|
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.
|
|
336
338
|
|
|
337
|
-
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 [
|
|
339
|
+
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 [Site deployment](#site-deployment-netlify--resend) below.
|
|
338
340
|
|
|
339
|
-
###
|
|
341
|
+
### Site deployment (Netlify + Resend)
|
|
340
342
|
|
|
341
|
-
|
|
343
|
+
This repo's Netlify site hosts the whole dashboard + forms surface: the Resend delivery webhook (`netlify/functions/resend-webhook.mts`), the cockpit/per-site dashboard, the `/submissions` page, and the form-ingest + submission-status endpoints. Deploying is a one-time operation per environment: connect the repo to a Netlify site, set its env vars, register the deployed URL with Resend.
|
|
342
344
|
|
|
343
345
|
**1. Create the Netlify site:**
|
|
344
346
|
|
|
345
|
-
- New site → Import from Git → pick `
|
|
347
|
+
- New site → Import from Git → pick `reddoorla/reddoor-maintenance`.
|
|
346
348
|
- Branch to deploy: `main`. Build settings are read from [`netlify.toml`](netlify.toml) (no build command needed — functions-only).
|
|
347
349
|
- Site name: pick something stable (e.g. `reddoor-webhooks`) since it ends up in the public webhook URL.
|
|
348
350
|
|
|
349
351
|
**2. Set env vars** in Site settings → Environment variables:
|
|
350
352
|
|
|
351
|
-
| Variable | Value
|
|
352
|
-
| ----------------------- |
|
|
353
|
-
| `AIRTABLE_PAT` | Same PAT used by the CLI (read+write on the Websites + Reports tables)
|
|
354
|
-
| `AIRTABLE_BASE_ID` | `appHG8nLOzULzXOER`
|
|
355
|
-
| `RESEND_WEBHOOK_SECRET` | Generated by Resend in step 4 — paste back here after creating the endpoint
|
|
353
|
+
| Variable | Value |
|
|
354
|
+
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
355
|
+
| `AIRTABLE_PAT` | Same PAT used by the CLI (read+write on the Websites + Reports tables) |
|
|
356
|
+
| `AIRTABLE_BASE_ID` | `appHG8nLOzULzXOER` |
|
|
357
|
+
| `RESEND_WEBHOOK_SECRET` | Generated by Resend in step 4 — paste back here after creating the endpoint |
|
|
358
|
+
| `TURSO_DATABASE_URL` | `libsql://<db>-<org>.turso.io` — submissions + spam counters live here; the dashboard + forms functions 500 without it |
|
|
359
|
+
| `TURSO_AUTH_TOKEN` | Auth token for that Turso url (required for a remote `libsql://` url) |
|
|
360
|
+
| `FORMS_INGEST_TOKEN` | Shared secret gating `POST /api/forms/:slug` — the SAME value is set on each fleet site |
|
|
356
361
|
|
|
357
362
|
**3. Trigger a deploy** (Deploys → Trigger deploy → Deploy site). When it goes green, curl the health endpoint:
|
|
358
363
|
|
package/dist/cli/bin.js
CHANGED
|
@@ -361,7 +361,7 @@ function securityFields(counts) {
|
|
|
361
361
|
}
|
|
362
362
|
function domainFields(result) {
|
|
363
363
|
const fields = { "Domain checked at": result.checkedAt };
|
|
364
|
-
|
|
364
|
+
fields["Cert days remaining"] = result.certDaysRemaining;
|
|
365
365
|
return fields;
|
|
366
366
|
}
|
|
367
367
|
function browserFields(r) {
|
|
@@ -2430,266 +2430,30 @@ function readDbConfig() {
|
|
|
2430
2430
|
const authToken = process.env.TURSO_AUTH_TOKEN;
|
|
2431
2431
|
return authToken ? { url, authToken } : { url };
|
|
2432
2432
|
}
|
|
2433
|
+
function ensureMigrated(url, client) {
|
|
2434
|
+
if (url === ":memory:") return runMigrations(client).then(() => void 0);
|
|
2435
|
+
const cached = migrationsByUrl.get(url);
|
|
2436
|
+
if (cached) return cached;
|
|
2437
|
+
const p = runMigrations(client).then(() => void 0).catch((err) => {
|
|
2438
|
+
migrationsByUrl.delete(url);
|
|
2439
|
+
throw err;
|
|
2440
|
+
});
|
|
2441
|
+
migrationsByUrl.set(url, p);
|
|
2442
|
+
return p;
|
|
2443
|
+
}
|
|
2433
2444
|
async function openDb(cfg) {
|
|
2434
2445
|
const clientConfig = cfg.authToken ? { url: cfg.url, authToken: cfg.authToken } : { url: cfg.url };
|
|
2435
2446
|
const client = createClient(clientConfig);
|
|
2436
|
-
await
|
|
2447
|
+
await ensureMigrated(cfg.url, client);
|
|
2437
2448
|
return new Kysely({ dialect: new LibsqlDialect({ client }) });
|
|
2438
2449
|
}
|
|
2450
|
+
var migrationsByUrl;
|
|
2439
2451
|
var init_client2 = __esm({
|
|
2440
2452
|
"src/db/client.ts"() {
|
|
2441
2453
|
"use strict";
|
|
2442
2454
|
init_credentials();
|
|
2443
2455
|
init_migrate();
|
|
2444
|
-
|
|
2445
|
-
});
|
|
2446
|
-
|
|
2447
|
-
// src/forms/types.ts
|
|
2448
|
-
var SUBMISSION_FORM_TYPES;
|
|
2449
|
-
var init_types = __esm({
|
|
2450
|
-
"src/forms/types.ts"() {
|
|
2451
|
-
"use strict";
|
|
2452
|
-
SUBMISSION_FORM_TYPES = [
|
|
2453
|
-
"contact",
|
|
2454
|
-
"inquiry",
|
|
2455
|
-
"newsletter",
|
|
2456
|
-
"rsvp",
|
|
2457
|
-
"reserve"
|
|
2458
|
-
];
|
|
2459
|
-
}
|
|
2460
|
-
});
|
|
2461
|
-
|
|
2462
|
-
// src/reports/submission-row.ts
|
|
2463
|
-
function toFormType(raw) {
|
|
2464
|
-
if (raw && SUBMISSION_FORM_TYPES.includes(raw)) return raw;
|
|
2465
|
-
if (raw)
|
|
2466
|
-
console.warn(`[submissions] unknown Form type ${JSON.stringify(raw)} \u2014 treating as contact`);
|
|
2467
|
-
return "contact";
|
|
2468
|
-
}
|
|
2469
|
-
function toStatus(raw) {
|
|
2470
|
-
if (raw && SUBMISSION_STATUSES.includes(raw))
|
|
2471
|
-
return raw;
|
|
2472
|
-
return "new";
|
|
2473
|
-
}
|
|
2474
|
-
function toNotifyStatus(raw) {
|
|
2475
|
-
if (raw && NOTIFY_STATUSES.includes(raw)) return raw;
|
|
2476
|
-
return "skipped";
|
|
2477
|
-
}
|
|
2478
|
-
var SUBMISSION_STATUSES, NOTIFY_STATUSES;
|
|
2479
|
-
var init_submission_row = __esm({
|
|
2480
|
-
"src/reports/submission-row.ts"() {
|
|
2481
|
-
"use strict";
|
|
2482
|
-
init_types();
|
|
2483
|
-
SUBMISSION_STATUSES = ["new", "read", "archived", "spam"];
|
|
2484
|
-
NOTIFY_STATUSES = ["sent", "failed", "skipped"];
|
|
2485
|
-
}
|
|
2486
|
-
});
|
|
2487
|
-
|
|
2488
|
-
// src/reports/airtable/submissions.ts
|
|
2489
|
-
function mapRow3(rec) {
|
|
2490
|
-
const f = rec.fields;
|
|
2491
|
-
const linkSites = f["Site"] ?? [];
|
|
2492
|
-
return {
|
|
2493
|
-
id: rec.id,
|
|
2494
|
-
submissionId: typeof f["Submission ID"] === "number" ? f["Submission ID"] : null,
|
|
2495
|
-
siteId: linkSites[0] ?? "",
|
|
2496
|
-
formType: toFormType(f["Form type"]),
|
|
2497
|
-
name: String(f["Name"] ?? ""),
|
|
2498
|
-
email: String(f["Email"] ?? ""),
|
|
2499
|
-
phone: f["Phone"] ?? null,
|
|
2500
|
-
message: f["Message"] ?? null,
|
|
2501
|
-
extraFields: f["Extra fields"] ?? null,
|
|
2502
|
-
sourceUrl: f["Source URL"] ?? null,
|
|
2503
|
-
utm: f["UTM"] ?? null,
|
|
2504
|
-
submittedAt: f["Submitted at"] ?? null,
|
|
2505
|
-
status: toStatus(f["Status"]),
|
|
2506
|
-
notifyStatus: toNotifyStatus(f["Notify status"]),
|
|
2507
|
-
resendMessageId: f["Resend message ID"] ?? null
|
|
2508
|
-
};
|
|
2509
|
-
}
|
|
2510
|
-
var SUBMISSIONS_TABLE;
|
|
2511
|
-
var init_submissions = __esm({
|
|
2512
|
-
"src/reports/airtable/submissions.ts"() {
|
|
2513
|
-
"use strict";
|
|
2514
|
-
init_reports();
|
|
2515
|
-
init_submission_row();
|
|
2516
|
-
SUBMISSIONS_TABLE = "Submissions";
|
|
2517
|
-
}
|
|
2518
|
-
});
|
|
2519
|
-
|
|
2520
|
-
// src/reports/airtable/screenouts.ts
|
|
2521
|
-
function num(v) {
|
|
2522
|
-
return typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
2523
|
-
}
|
|
2524
|
-
function siteIdOf(fields) {
|
|
2525
|
-
const link = fields["Site"];
|
|
2526
|
-
return link?.[0] ?? "";
|
|
2527
|
-
}
|
|
2528
|
-
async function listScreenOutsSince(base, sinceDate) {
|
|
2529
|
-
const out = /* @__PURE__ */ new Map();
|
|
2530
|
-
await base(SCREENOUTS_TABLE).select({ filterByFormula: `{Date} >= ${JSON.stringify(sinceDate)}`, pageSize: 100 }).eachPage((records, fetchNextPage) => {
|
|
2531
|
-
for (const rec of records) {
|
|
2532
|
-
const f = rec.fields;
|
|
2533
|
-
const date = typeof f["Date"] === "string" ? f["Date"] : "";
|
|
2534
|
-
if (date < sinceDate) continue;
|
|
2535
|
-
const siteId = siteIdOf(f);
|
|
2536
|
-
if (!siteId) continue;
|
|
2537
|
-
const cur = out.get(siteId) ?? { honeypot: 0, tooFast: 0, markedSpam: 0 };
|
|
2538
|
-
cur.honeypot += num(f["Honeypot"]);
|
|
2539
|
-
cur.tooFast += num(f["Too-fast"]);
|
|
2540
|
-
cur.markedSpam += num(f["Marked spam"]);
|
|
2541
|
-
out.set(siteId, cur);
|
|
2542
|
-
}
|
|
2543
|
-
fetchNextPage();
|
|
2544
|
-
});
|
|
2545
|
-
return out;
|
|
2546
|
-
}
|
|
2547
|
-
var SCREENOUTS_TABLE;
|
|
2548
|
-
var init_screenouts = __esm({
|
|
2549
|
-
"src/reports/airtable/screenouts.ts"() {
|
|
2550
|
-
"use strict";
|
|
2551
|
-
SCREENOUTS_TABLE = "Spam Screenouts";
|
|
2552
|
-
}
|
|
2553
|
-
});
|
|
2554
|
-
|
|
2555
|
-
// src/db/submissions.ts
|
|
2556
|
-
import { sql } from "kysely";
|
|
2557
|
-
async function backfillSubmission(db, row) {
|
|
2558
|
-
await db.insertInto("submissions").values({
|
|
2559
|
-
id: row.id,
|
|
2560
|
-
submission_id: row.submissionId,
|
|
2561
|
-
site_id: row.siteId,
|
|
2562
|
-
form_type: row.formType,
|
|
2563
|
-
name: row.name,
|
|
2564
|
-
email: row.email,
|
|
2565
|
-
phone: row.phone,
|
|
2566
|
-
message: row.message,
|
|
2567
|
-
extra_fields: row.extraFields,
|
|
2568
|
-
source_url: row.sourceUrl,
|
|
2569
|
-
utm: row.utm,
|
|
2570
|
-
submitted_at: row.submittedAt,
|
|
2571
|
-
status: row.status,
|
|
2572
|
-
notify_status: row.notifyStatus,
|
|
2573
|
-
resend_message_id: row.resendMessageId
|
|
2574
|
-
}).onConflict((oc) => oc.column("id").doNothing()).execute();
|
|
2575
|
-
}
|
|
2576
|
-
var init_submissions2 = __esm({
|
|
2577
|
-
"src/db/submissions.ts"() {
|
|
2578
|
-
"use strict";
|
|
2579
|
-
init_submission_row();
|
|
2580
|
-
}
|
|
2581
|
-
});
|
|
2582
|
-
|
|
2583
|
-
// src/db/screenouts.ts
|
|
2584
|
-
import { sql as sql2 } from "kysely";
|
|
2585
|
-
async function listScreenOutsSince2(db, sinceDate) {
|
|
2586
|
-
const rows = await db.selectFrom("spam_screenouts").select((eb) => [
|
|
2587
|
-
"site_id",
|
|
2588
|
-
eb.fn.sum("honeypot").as("honeypot"),
|
|
2589
|
-
eb.fn.sum("too_fast").as("too_fast"),
|
|
2590
|
-
eb.fn.sum("marked_spam").as("marked_spam")
|
|
2591
|
-
]).where("date", ">=", sinceDate).groupBy("site_id").execute();
|
|
2592
|
-
const out = /* @__PURE__ */ new Map();
|
|
2593
|
-
for (const r of rows) {
|
|
2594
|
-
out.set(r.site_id, {
|
|
2595
|
-
honeypot: Number(r.honeypot) || 0,
|
|
2596
|
-
tooFast: Number(r.too_fast) || 0,
|
|
2597
|
-
markedSpam: Number(r.marked_spam) || 0
|
|
2598
|
-
});
|
|
2599
|
-
}
|
|
2600
|
-
return out;
|
|
2601
|
-
}
|
|
2602
|
-
async function backfillScreenoutBucket(db, b) {
|
|
2603
|
-
await sql2`
|
|
2604
|
-
INSERT INTO spam_screenouts (site_id, date, honeypot, too_fast, marked_spam)
|
|
2605
|
-
VALUES (${b.siteId}, ${b.date}, ${b.honeypot}, ${b.tooFast}, ${b.markedSpam})
|
|
2606
|
-
ON CONFLICT (site_id, date) DO UPDATE SET
|
|
2607
|
-
honeypot = excluded.honeypot,
|
|
2608
|
-
too_fast = excluded.too_fast,
|
|
2609
|
-
marked_spam = excluded.marked_spam
|
|
2610
|
-
`.execute(db);
|
|
2611
|
-
}
|
|
2612
|
-
var init_screenouts2 = __esm({
|
|
2613
|
-
"src/db/screenouts.ts"() {
|
|
2614
|
-
"use strict";
|
|
2615
|
-
}
|
|
2616
|
-
});
|
|
2617
|
-
|
|
2618
|
-
// src/db/backfill.ts
|
|
2619
|
-
var backfill_exports = {};
|
|
2620
|
-
__export(backfill_exports, {
|
|
2621
|
-
backfillScreenouts: () => backfillScreenouts,
|
|
2622
|
-
backfillSubmissions: () => backfillSubmissions,
|
|
2623
|
-
reconcile: () => reconcile
|
|
2624
|
-
});
|
|
2625
|
-
import { sql as sql3 } from "kysely";
|
|
2626
|
-
async function backfillSubmissions(base, db) {
|
|
2627
|
-
const rows = [];
|
|
2628
|
-
await base(SUBMISSIONS_TABLE).select({ pageSize: 100 }).eachPage((records, fetchNextPage) => {
|
|
2629
|
-
for (const rec of records) rows.push(mapRow3({ id: rec.id, fields: rec.fields }));
|
|
2630
|
-
fetchNextPage();
|
|
2631
|
-
});
|
|
2632
|
-
for (const row of rows) await backfillSubmission(db, row);
|
|
2633
|
-
return rows.length;
|
|
2634
|
-
}
|
|
2635
|
-
function num2(v) {
|
|
2636
|
-
return typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
2637
|
-
}
|
|
2638
|
-
async function backfillScreenouts(base, db) {
|
|
2639
|
-
const agg = /* @__PURE__ */ new Map();
|
|
2640
|
-
await base(SCREENOUTS_TABLE).select({ pageSize: 100 }).eachPage((records, fetchNextPage) => {
|
|
2641
|
-
for (const rec of records) {
|
|
2642
|
-
const f = rec.fields;
|
|
2643
|
-
const siteId = f["Site"]?.[0] ?? "";
|
|
2644
|
-
const date = typeof f["Date"] === "string" ? f["Date"] : "";
|
|
2645
|
-
if (!siteId || !date) continue;
|
|
2646
|
-
const key = `${siteId} ${date}`;
|
|
2647
|
-
const cur = agg.get(key) ?? { siteId, date, honeypot: 0, tooFast: 0, markedSpam: 0 };
|
|
2648
|
-
cur.honeypot += num2(f["Honeypot"]);
|
|
2649
|
-
cur.tooFast += num2(f["Too-fast"]);
|
|
2650
|
-
cur.markedSpam += num2(f["Marked spam"]);
|
|
2651
|
-
agg.set(key, cur);
|
|
2652
|
-
}
|
|
2653
|
-
fetchNextPage();
|
|
2654
|
-
});
|
|
2655
|
-
for (const bucket of agg.values()) await backfillScreenoutBucket(db, bucket);
|
|
2656
|
-
return agg.size;
|
|
2657
|
-
}
|
|
2658
|
-
async function reconcile(base, db) {
|
|
2659
|
-
let airtableSubs = 0;
|
|
2660
|
-
await base(SUBMISSIONS_TABLE).select({ pageSize: 100, fields: [] }).eachPage((records, fetchNextPage) => {
|
|
2661
|
-
airtableSubs += records.length;
|
|
2662
|
-
fetchNextPage();
|
|
2663
|
-
});
|
|
2664
|
-
const libCountRow = await sql3`SELECT COUNT(*) AS n FROM submissions`.execute(db);
|
|
2665
|
-
const libsqlSubs = Number(libCountRow.rows[0]?.n ?? 0);
|
|
2666
|
-
const aMap = await listScreenOutsSince(base, "0001-01-01");
|
|
2667
|
-
const lMap = await listScreenOutsSince2(db, "0001-01-01");
|
|
2668
|
-
const sumOf = (m) => {
|
|
2669
|
-
const t = { honeypot: 0, tooFast: 0, markedSpam: 0 };
|
|
2670
|
-
for (const v of m.values()) {
|
|
2671
|
-
t.honeypot += v.honeypot;
|
|
2672
|
-
t.tooFast += v.tooFast;
|
|
2673
|
-
t.markedSpam += v.markedSpam;
|
|
2674
|
-
}
|
|
2675
|
-
return t;
|
|
2676
|
-
};
|
|
2677
|
-
const aScreen = sumOf(aMap);
|
|
2678
|
-
const lScreen = sumOf(lMap);
|
|
2679
|
-
const ok = airtableSubs === libsqlSubs && aScreen.honeypot === lScreen.honeypot && aScreen.tooFast === lScreen.tooFast && aScreen.markedSpam === lScreen.markedSpam;
|
|
2680
|
-
return {
|
|
2681
|
-
ok,
|
|
2682
|
-
submissions: { airtable: airtableSubs, libsql: libsqlSubs },
|
|
2683
|
-
screenouts: { airtable: aScreen, libsql: lScreen }
|
|
2684
|
-
};
|
|
2685
|
-
}
|
|
2686
|
-
var init_backfill = __esm({
|
|
2687
|
-
"src/db/backfill.ts"() {
|
|
2688
|
-
"use strict";
|
|
2689
|
-
init_submissions();
|
|
2690
|
-
init_screenouts();
|
|
2691
|
-
init_submissions2();
|
|
2692
|
-
init_screenouts2();
|
|
2456
|
+
migrationsByUrl = /* @__PURE__ */ new Map();
|
|
2693
2457
|
}
|
|
2694
2458
|
});
|
|
2695
2459
|
|
|
@@ -5350,7 +5114,12 @@ function makeGitHub(deps) {
|
|
|
5350
5114
|
return r.stdout.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
|
|
5351
5115
|
},
|
|
5352
5116
|
async secretExists(repo, name) {
|
|
5353
|
-
const out = await gh([
|
|
5117
|
+
const out = await gh([
|
|
5118
|
+
"api",
|
|
5119
|
+
`repos/${repo}/actions/secrets?per_page=100`,
|
|
5120
|
+
"--jq",
|
|
5121
|
+
".secrets[].name"
|
|
5122
|
+
]);
|
|
5354
5123
|
return out.split("\n").map((l) => l.trim()).includes(name);
|
|
5355
5124
|
},
|
|
5356
5125
|
async autoMergeEnabled(repo) {
|
|
@@ -5360,7 +5129,7 @@ function makeGitHub(deps) {
|
|
|
5360
5129
|
async findOpenSelfUpdatingPR(repo) {
|
|
5361
5130
|
const out = await gh([
|
|
5362
5131
|
"api",
|
|
5363
|
-
`repos/${repo}/pulls?state=open`,
|
|
5132
|
+
`repos/${repo}/pulls?state=open&per_page=100`,
|
|
5364
5133
|
"--jq",
|
|
5365
5134
|
'.[] | select(.head.ref | startswith("maint/self-updating-")) | .html_url'
|
|
5366
5135
|
]);
|
|
@@ -7229,7 +6998,7 @@ async function draftDueReports(base, today) {
|
|
|
7229
6998
|
continue;
|
|
7230
6999
|
}
|
|
7231
7000
|
const pendingEarlier = reports.find(
|
|
7232
|
-
(r) => r.siteId === item.site.id && r.reportType === item.reportType && r.sentAt === null && r.period !== null && r.period < period
|
|
7001
|
+
(r) => r.siteId === item.site.id && r.reportType === item.reportType && r.draftReady && r.sentAt === null && r.period !== null && r.period < period
|
|
7233
7002
|
);
|
|
7234
7003
|
if (pendingEarlier) {
|
|
7235
7004
|
skipped++;
|
|
@@ -7870,34 +7639,8 @@ async function runDbCommand(action, opts) {
|
|
|
7870
7639
|
code: 0
|
|
7871
7640
|
};
|
|
7872
7641
|
}
|
|
7873
|
-
if (action === "backfill") {
|
|
7874
|
-
const { openDb: openDb2, readDbConfig: readDbConfig2 } = await Promise.resolve().then(() => (init_client2(), client_exports2));
|
|
7875
|
-
const cfg = opts.url ? { url: opts.url } : readDbConfig2();
|
|
7876
|
-
const { openBase: openBase2, readAirtableConfig: readAirtableConfig2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
7877
|
-
const { backfillSubmissions: backfillSubmissions2, backfillScreenouts: backfillScreenouts2 } = await Promise.resolve().then(() => (init_backfill(), backfill_exports));
|
|
7878
|
-
const base = openBase2(readAirtableConfig2());
|
|
7879
|
-
const db = await openDb2(cfg);
|
|
7880
|
-
const subs = await backfillSubmissions2(base, db);
|
|
7881
|
-
const buckets = await backfillScreenouts2(base, db);
|
|
7882
|
-
return { output: `Backfilled ${subs} submissions, ${buckets} screen-out buckets.`, code: 0 };
|
|
7883
|
-
}
|
|
7884
|
-
if (action === "reconcile") {
|
|
7885
|
-
const { openDb: openDb2, readDbConfig: readDbConfig2 } = await Promise.resolve().then(() => (init_client2(), client_exports2));
|
|
7886
|
-
const cfg = opts.url ? { url: opts.url } : readDbConfig2();
|
|
7887
|
-
const { openBase: openBase2, readAirtableConfig: readAirtableConfig2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
7888
|
-
const { reconcile: reconcile2 } = await Promise.resolve().then(() => (init_backfill(), backfill_exports));
|
|
7889
|
-
const base = openBase2(readAirtableConfig2());
|
|
7890
|
-
const db = await openDb2(cfg);
|
|
7891
|
-
const r = await reconcile2(base, db);
|
|
7892
|
-
const lines = [
|
|
7893
|
-
`submissions: airtable=${r.submissions.airtable} libsql=${r.submissions.libsql}`,
|
|
7894
|
-
`screenouts: airtable=${JSON.stringify(r.screenouts.airtable)} libsql=${JSON.stringify(r.screenouts.libsql)}`,
|
|
7895
|
-
r.ok ? "OK \u2014 parity confirmed." : "MISMATCH \u2014 do not cut over."
|
|
7896
|
-
];
|
|
7897
|
-
return { output: lines.join("\n"), code: r.ok ? 0 : 1 };
|
|
7898
|
-
}
|
|
7899
7642
|
return {
|
|
7900
|
-
output: `unknown db action '${action}'. Use: migrate
|
|
7643
|
+
output: `unknown db action '${action}'. Use: migrate.`,
|
|
7901
7644
|
code: 1
|
|
7902
7645
|
};
|
|
7903
7646
|
}
|