@reddoorla/maintenance 0.76.0 → 0.78.0

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 (121) hide show
  1. package/README.md +34 -7
  2. package/dist/{blux-RIJBRVCF.js → blux-2P4FGCUL.js} +5 -5
  3. package/dist/{blux-RIJBRVCF.js.map → blux-2P4FGCUL.js.map} +1 -1
  4. package/dist/{bump-deps-OQR3IXLN.js → bump-deps-XB2LJTVN.js} +3 -3
  5. package/dist/{chunk-RHPDQW6D.js → chunk-2SLED3NB.js} +1 -1
  6. package/dist/{chunk-RHPDQW6D.js.map → chunk-2SLED3NB.js.map} +1 -1
  7. package/dist/{chunk-HIN3P4ZN.js → chunk-4SOCPK7A.js} +10 -1
  8. package/dist/chunk-4SOCPK7A.js.map +1 -0
  9. package/dist/{chunk-CASCWQQX.js → chunk-73UFRZKY.js} +34 -19
  10. package/dist/chunk-73UFRZKY.js.map +1 -0
  11. package/dist/{chunk-D3DQFC32.js → chunk-ATJ6ZA2V.js} +2 -2
  12. package/dist/{chunk-ZO7WNY57.js → chunk-B5ZUQZFL.js} +2 -2
  13. package/dist/{chunk-PH3AGF4Z.js → chunk-DC5H7KEQ.js} +2 -2
  14. package/dist/{chunk-4VAUSRSS.js → chunk-FVVGDIGX.js} +2 -2
  15. package/dist/{chunk-7MJBTV2R.js → chunk-FX7RAMGC.js} +2 -2
  16. package/dist/{chunk-DYKG53IR.js → chunk-GWAOX3CU.js} +9 -9
  17. package/dist/{chunk-PKVZMFEB.js → chunk-K5JZ5KLO.js} +2 -2
  18. package/dist/{chunk-CZAMYVOP.js → chunk-KPQXB5CR.js} +2 -2
  19. package/dist/{chunk-BTEBRTW4.js → chunk-LS6QPANO.js} +2 -2
  20. package/dist/{chunk-2X322ZES.js → chunk-MGJ72BA4.js} +2 -2
  21. package/dist/chunk-MNDIED4M.js +137 -0
  22. package/dist/chunk-MNDIED4M.js.map +1 -0
  23. package/dist/{chunk-ZYBOT5PM.js → chunk-NT4WIP7I.js} +2 -2
  24. package/dist/{chunk-JNXCDUYB.js → chunk-Q276MZ7E.js} +3 -4
  25. package/dist/chunk-Q276MZ7E.js.map +1 -0
  26. package/dist/{chunk-GYNRZCM6.js → chunk-QG6OSL4M.js} +2 -2
  27. package/dist/{chunk-IRRZG66U.js → chunk-QMXQUL5Z.js} +34 -18
  28. package/dist/chunk-QMXQUL5Z.js.map +1 -0
  29. package/dist/{chunk-YEGHUODU.js → chunk-VSHC4HOI.js} +96 -1
  30. package/dist/chunk-VSHC4HOI.js.map +1 -0
  31. package/dist/{chunk-JTRTG5CU.js → chunk-YIQ7IWE3.js} +2 -2
  32. package/dist/{chunk-3U3IGVAQ.js → chunk-YSPA5AUT.js} +15 -5
  33. package/dist/chunk-YSPA5AUT.js.map +1 -0
  34. package/dist/{chunk-XKOZUVK2.js → chunk-ZTPPCHDB.js} +1 -1
  35. package/dist/chunk-ZTPPCHDB.js.map +1 -0
  36. package/dist/cli/bin.js +32 -21
  37. package/dist/cli/bin.js.map +1 -1
  38. package/dist/cli/commands/audit.d.ts +1 -1
  39. package/dist/cli/commands/audit.js +5 -5
  40. package/dist/client-X5WUJWDE.js +11 -0
  41. package/dist/configs/eslint.js +10 -0
  42. package/dist/configs/eslint.js.map +1 -1
  43. package/dist/{convert-to-pnpm-OVMASATZ.js → convert-to-pnpm-FZHSLUQB.js} +3 -3
  44. package/dist/{db-3QW2XEXA.js → db-JSIZYT23.js} +3 -3
  45. package/dist/{digest-75FWSC74.js → digest-NQAQI34P.js} +11 -11
  46. package/dist/forms/index.js.map +1 -1
  47. package/dist/{github-signals-TF3VSNIN.js → github-signals-4LWQGD5V.js} +5 -5
  48. package/dist/{health-endpoint-JZGYCMTV.js → health-endpoint-LFVE26QS.js} +3 -3
  49. package/dist/index.d.ts +7 -2
  50. package/dist/index.js +28 -21
  51. package/dist/index.js.map +1 -1
  52. package/dist/{init-2KOISBCZ.js → init-KF4QHMWQ.js} +12 -12
  53. package/dist/{launch-K4EHKTXC.js → launch-CVCIP5NU.js} +6 -5
  54. package/dist/launch-CVCIP5NU.js.map +1 -0
  55. package/dist/migrate-LJUFGXQZ.js +7 -0
  56. package/dist/{onboard-PX36YDZA.js → onboard-RDGHVCNG.js} +3 -3
  57. package/dist/{orchestrate-CPR6KVS2.js → orchestrate-QOQXOJLI.js} +5 -5
  58. package/dist/{preflight-QRRMPI7K.js → preflight-2PWVVIRR.js} +6 -6
  59. package/dist/protection-audit-X3BHT2MB.js +140 -0
  60. package/dist/protection-audit-X3BHT2MB.js.map +1 -0
  61. package/dist/recipes/sync-configs.d.ts +1 -1
  62. package/dist/recipes/sync-configs.js +3 -3
  63. package/dist/{renovate-dispatch-6II2JVOX.js → renovate-dispatch-2VYCEH42.js} +2 -2
  64. package/dist/renovate-dispatch-2VYCEH42.js.map +1 -0
  65. package/dist/{report-SLGBDJ6O.js → report-IU4CLJS5.js} +3 -3
  66. package/dist/{self-updating-2YSVNMPP.js → self-updating-ALMUWJNX.js} +5 -4
  67. package/dist/{self-updating-2YSVNMPP.js.map → self-updating-ALMUWJNX.js.map} +1 -1
  68. package/dist/{selftest-RVASRO3Z.js → selftest-PJFABZDM.js} +5 -5
  69. package/dist/{smoke-suite-CSP23NZM.js → smoke-suite-SUEJFBYP.js} +3 -3
  70. package/dist/{submissions-MDKWJYMM.js → submissions-3I3GAQAX.js} +13 -5
  71. package/dist/submissions-3I3GAQAX.js.map +1 -0
  72. package/dist/{submissions-BNRGY3JQ.js → submissions-SURN7BV5.js} +3 -3
  73. package/dist/{svelte-codemods-V54KOBWU.js → svelte-codemods-IWO7X3F3.js} +3 -3
  74. package/dist/{sync-configs-UBCJRASZ.js → sync-configs-7XGHFC6P.js} +4 -4
  75. package/dist/{types-0jreMCF5.d.ts → types-DnEL3NRn.d.ts} +1 -1
  76. package/dist/{upgrade-WR6ICPAG.js → upgrade-AM4S7JF3.js} +3 -3
  77. package/package.json +1 -1
  78. package/dist/chunk-3U3IGVAQ.js.map +0 -1
  79. package/dist/chunk-CASCWQQX.js.map +0 -1
  80. package/dist/chunk-HIN3P4ZN.js.map +0 -1
  81. package/dist/chunk-IRRZG66U.js.map +0 -1
  82. package/dist/chunk-JNXCDUYB.js.map +0 -1
  83. package/dist/chunk-XKOZUVK2.js.map +0 -1
  84. package/dist/chunk-YEGHUODU.js.map +0 -1
  85. package/dist/client-NVVQK3HH.js +0 -11
  86. package/dist/launch-K4EHKTXC.js.map +0 -1
  87. package/dist/migrate-35DNNF3H.js +0 -7
  88. package/dist/renovate-dispatch-6II2JVOX.js.map +0 -1
  89. package/dist/submissions-MDKWJYMM.js.map +0 -1
  90. /package/dist/{bump-deps-OQR3IXLN.js.map → bump-deps-XB2LJTVN.js.map} +0 -0
  91. /package/dist/{chunk-D3DQFC32.js.map → chunk-ATJ6ZA2V.js.map} +0 -0
  92. /package/dist/{chunk-ZO7WNY57.js.map → chunk-B5ZUQZFL.js.map} +0 -0
  93. /package/dist/{chunk-PH3AGF4Z.js.map → chunk-DC5H7KEQ.js.map} +0 -0
  94. /package/dist/{chunk-4VAUSRSS.js.map → chunk-FVVGDIGX.js.map} +0 -0
  95. /package/dist/{chunk-7MJBTV2R.js.map → chunk-FX7RAMGC.js.map} +0 -0
  96. /package/dist/{chunk-DYKG53IR.js.map → chunk-GWAOX3CU.js.map} +0 -0
  97. /package/dist/{chunk-PKVZMFEB.js.map → chunk-K5JZ5KLO.js.map} +0 -0
  98. /package/dist/{chunk-CZAMYVOP.js.map → chunk-KPQXB5CR.js.map} +0 -0
  99. /package/dist/{chunk-BTEBRTW4.js.map → chunk-LS6QPANO.js.map} +0 -0
  100. /package/dist/{chunk-2X322ZES.js.map → chunk-MGJ72BA4.js.map} +0 -0
  101. /package/dist/{chunk-ZYBOT5PM.js.map → chunk-NT4WIP7I.js.map} +0 -0
  102. /package/dist/{chunk-GYNRZCM6.js.map → chunk-QG6OSL4M.js.map} +0 -0
  103. /package/dist/{chunk-JTRTG5CU.js.map → chunk-YIQ7IWE3.js.map} +0 -0
  104. /package/dist/{client-NVVQK3HH.js.map → client-X5WUJWDE.js.map} +0 -0
  105. /package/dist/{convert-to-pnpm-OVMASATZ.js.map → convert-to-pnpm-FZHSLUQB.js.map} +0 -0
  106. /package/dist/{db-3QW2XEXA.js.map → db-JSIZYT23.js.map} +0 -0
  107. /package/dist/{digest-75FWSC74.js.map → digest-NQAQI34P.js.map} +0 -0
  108. /package/dist/{github-signals-TF3VSNIN.js.map → github-signals-4LWQGD5V.js.map} +0 -0
  109. /package/dist/{health-endpoint-JZGYCMTV.js.map → health-endpoint-LFVE26QS.js.map} +0 -0
  110. /package/dist/{init-2KOISBCZ.js.map → init-KF4QHMWQ.js.map} +0 -0
  111. /package/dist/{migrate-35DNNF3H.js.map → migrate-LJUFGXQZ.js.map} +0 -0
  112. /package/dist/{onboard-PX36YDZA.js.map → onboard-RDGHVCNG.js.map} +0 -0
  113. /package/dist/{orchestrate-CPR6KVS2.js.map → orchestrate-QOQXOJLI.js.map} +0 -0
  114. /package/dist/{preflight-QRRMPI7K.js.map → preflight-2PWVVIRR.js.map} +0 -0
  115. /package/dist/{report-SLGBDJ6O.js.map → report-IU4CLJS5.js.map} +0 -0
  116. /package/dist/{selftest-RVASRO3Z.js.map → selftest-PJFABZDM.js.map} +0 -0
  117. /package/dist/{smoke-suite-CSP23NZM.js.map → smoke-suite-SUEJFBYP.js.map} +0 -0
  118. /package/dist/{submissions-BNRGY3JQ.js.map → submissions-SURN7BV5.js.map} +0 -0
  119. /package/dist/{svelte-codemods-V54KOBWU.js.map → svelte-codemods-IWO7X3F3.js.map} +0 -0
  120. /package/dist/{sync-configs-UBCJRASZ.js.map → sync-configs-7XGHFC6P.js.map} +0 -0
  121. /package/dist/{upgrade-WR6ICPAG.js.map → upgrade-AM4S7JF3.js.map} +0 -0
@@ -11,8 +11,8 @@ import {
11
11
  } from "./chunk-4BHNP6Y2.js";
12
12
  import {
13
13
  bumpDeps
14
- } from "./chunk-D3DQFC32.js";
15
- import "./chunk-XKOZUVK2.js";
14
+ } from "./chunk-ATJ6ZA2V.js";
15
+ import "./chunk-ZTPPCHDB.js";
16
16
  import "./chunk-FDIFTIXC.js";
17
17
  import "./chunk-HVOOCK6L.js";
18
18
  import "./chunk-XXTZBPUY.js";
@@ -57,4 +57,4 @@ async function runBumpDepsCommand(site, opts) {
57
57
  export {
58
58
  runBumpDepsCommand
59
59
  };
60
- //# sourceMappingURL=bump-deps-OQR3IXLN.js.map
60
+ //# sourceMappingURL=bump-deps-XB2LJTVN.js.map
@@ -26,4 +26,4 @@ export {
26
26
  toStatus,
27
27
  toNotifyStatus
28
28
  };
29
- //# sourceMappingURL=chunk-RHPDQW6D.js.map
29
+ //# sourceMappingURL=chunk-2SLED3NB.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/reports/submission-row.ts"],"sourcesContent":["import { SUBMISSION_FORM_TYPES, type FormType } from \"../forms/types.js\";\n\nexport { SUBMISSION_FORM_TYPES };\nexport type { FormType };\n\nexport const SUBMISSION_STATUSES = [\"new\", \"read\", \"archived\", \"spam\", \"spam_auto\"] as const;\nexport type SubmissionStatus = (typeof SUBMISSION_STATUSES)[number];\n\n// \"bounced\" (2026-07-16): Resend ACCEPTED the notification (\"sent\") but later\n// reported a bounce/complaint via webhook — the lead never reached the client\n// (the Espada failure mode: 4 of 8 notifications bounced with nothing alarming).\n// The resend-webhook maps the event back onto the submission by resend_message_id.\nexport const NOTIFY_STATUSES = [\"sent\", \"failed\", \"skipped\", \"bounced\"] as const;\nexport type NotifyStatus = (typeof NOTIFY_STATUSES)[number];\n\nexport function toFormType(raw: string | undefined): FormType {\n if (raw && (SUBMISSION_FORM_TYPES as readonly string[]).includes(raw)) return raw as FormType;\n if (raw)\n console.warn(`[submissions] unknown Form type ${JSON.stringify(raw)} — treating as contact`);\n return \"contact\";\n}\n\nexport function toStatus(raw: string | undefined): SubmissionStatus {\n if (raw && (SUBMISSION_STATUSES as readonly string[]).includes(raw))\n return raw as SubmissionStatus;\n return \"new\";\n}\n\nexport function toNotifyStatus(raw: string | undefined): NotifyStatus {\n if (raw && (NOTIFY_STATUSES as readonly string[]).includes(raw)) return raw as NotifyStatus;\n return \"skipped\";\n}\n\nexport type SubmissionRow = {\n id: string;\n submissionId: number | null;\n siteId: string;\n formType: FormType;\n name: string;\n email: string;\n phone: string | null;\n message: string | null;\n /** Raw JSON string of any site-specific fields the typed columns didn't claim. */\n extraFields: string | null;\n sourceUrl: string | null;\n utm: string | null;\n submittedAt: string | null;\n status: SubmissionStatus;\n notifyStatus: NotifyStatus;\n resendMessageId: string | null;\n /** Heuristic spam score at ingest time; null for pre-classifier / un-scored rows. */\n spamScore?: number | null;\n /** Comma-joined classifier reason codes (e.g. \"links:3,disposable-email\"); null when unscored. */\n spamReason?: string | null;\n};\n\nexport type SubmissionInput = {\n siteId: string;\n formType: FormType;\n name: string;\n email: string;\n phone?: string;\n message?: string;\n extraFields?: Record<string, unknown>;\n sourceUrl?: string;\n utm?: string;\n submittedAt: Date;\n status?: SubmissionStatus;\n spamScore?: number | null;\n spamReason?: string | null;\n};\n"],"mappings":";;;;;AAKO,IAAM,sBAAsB,CAAC,OAAO,QAAQ,YAAY,QAAQ,WAAW;AAO3E,IAAM,kBAAkB,CAAC,QAAQ,UAAU,WAAW,SAAS;AAG/D,SAAS,WAAW,KAAmC;AAC5D,MAAI,OAAQ,sBAA4C,SAAS,GAAG,EAAG,QAAO;AAC9E,MAAI;AACF,YAAQ,KAAK,mCAAmC,KAAK,UAAU,GAAG,CAAC,6BAAwB;AAC7F,SAAO;AACT;AAEO,SAAS,SAAS,KAA2C;AAClE,MAAI,OAAQ,oBAA0C,SAAS,GAAG;AAChE,WAAO;AACT,SAAO;AACT;AAEO,SAAS,eAAe,KAAuC;AACpE,MAAI,OAAQ,gBAAsC,SAAS,GAAG,EAAG,QAAO;AACxE,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../src/reports/submission-row.ts"],"sourcesContent":["import { SUBMISSION_FORM_TYPES, type FormType } from \"../forms/types.js\";\n\nexport { SUBMISSION_FORM_TYPES };\nexport type { FormType };\n\nexport const SUBMISSION_STATUSES = [\"new\", \"read\", \"archived\", \"spam\", \"spam_auto\"] as const;\nexport type SubmissionStatus = (typeof SUBMISSION_STATUSES)[number];\n\n// \"bounced\" (2026-07-16): Resend ACCEPTED the notification (\"sent\") but later\n// reported a bounce/complaint via webhook — the lead never reached the client\n// (the Espada failure mode: 4 of 8 notifications bounced with nothing alarming).\n// The resend-webhook maps the event back onto the submission by resend_message_id.\nexport const NOTIFY_STATUSES = [\"sent\", \"failed\", \"skipped\", \"bounced\"] as const;\nexport type NotifyStatus = (typeof NOTIFY_STATUSES)[number];\n\nexport function toFormType(raw: string | undefined): FormType {\n if (raw && (SUBMISSION_FORM_TYPES as readonly string[]).includes(raw)) return raw as FormType;\n if (raw)\n console.warn(`[submissions] unknown Form type ${JSON.stringify(raw)} — treating as contact`);\n return \"contact\";\n}\n\nexport function toStatus(raw: string | undefined): SubmissionStatus {\n if (raw && (SUBMISSION_STATUSES as readonly string[]).includes(raw))\n return raw as SubmissionStatus;\n return \"new\";\n}\n\nexport function toNotifyStatus(raw: string | undefined): NotifyStatus {\n if (raw && (NOTIFY_STATUSES as readonly string[]).includes(raw)) return raw as NotifyStatus;\n return \"skipped\";\n}\n\nexport type SubmissionRow = {\n id: string;\n submissionId: number | null;\n siteId: string;\n formType: FormType;\n name: string;\n email: string;\n phone: string | null;\n message: string | null;\n /** Raw JSON string of any site-specific fields the typed columns didn't claim. */\n extraFields: string | null;\n sourceUrl: string | null;\n utm: string | null;\n submittedAt: string | null;\n status: SubmissionStatus;\n notifyStatus: NotifyStatus;\n resendMessageId: string | null;\n /** Heuristic spam score at ingest time; null for pre-classifier / un-scored rows. */\n spamScore?: number | null;\n /** Comma-joined classifier reason codes (e.g. \"links:3,disposable-email\"); null when unscored. */\n spamReason?: string | null;\n /** Comma-joined newsletter fan-out outcome per destination — `webhook:ok`,\n * `mailchimp:401`, `mailchimp:threw`, `mailchimp-tags:failed`. Null means nothing\n * was attempted: a non-newsletter form, a spam row, a site with no destination\n * configured, or a row written before 0005. See ingest.ts for the token grammar. */\n fanoutStatus?: string | null;\n};\n\nexport type SubmissionInput = {\n siteId: string;\n formType: FormType;\n name: string;\n email: string;\n phone?: string;\n message?: string;\n extraFields?: Record<string, unknown>;\n sourceUrl?: string;\n utm?: string;\n submittedAt: Date;\n status?: SubmissionStatus;\n spamScore?: number | null;\n spamReason?: string | null;\n};\n"],"mappings":";;;;;AAKO,IAAM,sBAAsB,CAAC,OAAO,QAAQ,YAAY,QAAQ,WAAW;AAO3E,IAAM,kBAAkB,CAAC,QAAQ,UAAU,WAAW,SAAS;AAG/D,SAAS,WAAW,KAAmC;AAC5D,MAAI,OAAQ,sBAA4C,SAAS,GAAG,EAAG,QAAO;AAC9E,MAAI;AACF,YAAQ,KAAK,mCAAmC,KAAK,UAAU,GAAG,CAAC,6BAAwB;AAC7F,SAAO;AACT;AAEO,SAAS,SAAS,KAA2C;AAClE,MAAI,OAAQ,oBAA0C,SAAS,GAAG;AAChE,WAAO;AACT,SAAO;AACT;AAEO,SAAS,eAAe,KAAuC;AACpE,MAAI,OAAQ,gBAAsC,SAAS,GAAG,EAAG,QAAO;AACxE,SAAO;AACT;","names":[]}
@@ -62,6 +62,15 @@ var MIGRATIONS = [
62
62
  {
63
63
  id: "0004_add_spam_reason",
64
64
  sql: `ALTER TABLE submissions ADD COLUMN spam_reason TEXT;`
65
+ },
66
+ {
67
+ // Outcome of the newsletter fan-out (site webhook + Mailchimp). Before this the
68
+ // results were console.error-only, so a rotated Mailchimp key or an outage would
69
+ // silently stop signups reaching the audience with no trace anywhere the operator
70
+ // looks — the row still read `notify=sent`, healthy. Same non-idempotent
71
+ // ADD COLUMN caveat as 0003.
72
+ id: "0005_add_fanout_status",
73
+ sql: `ALTER TABLE submissions ADD COLUMN fanout_status TEXT;`
65
74
  }
66
75
  ];
67
76
 
@@ -96,4 +105,4 @@ async function runMigrations(client) {
96
105
  export {
97
106
  runMigrations
98
107
  };
99
- //# sourceMappingURL=chunk-HIN3P4ZN.js.map
108
+ //# sourceMappingURL=chunk-4SOCPK7A.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/db/migrations.ts","../src/db/migrate.ts"],"sourcesContent":["/** Ordered, append-only list of standard-SQL migration scripts. Each runs once,\n * tracked by `id` in the `_migrations` table (see migrate.ts). Statements use\n * IF NOT EXISTS so even a partial re-apply is safe. Never edit a shipped script —\n * add a new one. Every statement in a script MUST be independently idempotent:\n * `migrate.ts` runs each script via `executeMultiple`, which is NOT transactional,\n * so a mid-script failure can leave earlier statements applied and the id unrecorded\n * — a re-run then re-executes the whole script. Standard SQLite SQL only (no\n * Turso-specific syntax) so the host stays a connection-string swap. */\nexport type Migration = { id: string; sql: string };\n\nexport const MIGRATIONS: Migration[] = [\n {\n id: \"0001_init\",\n sql: `\n CREATE TABLE IF NOT EXISTS submissions (\n id TEXT PRIMARY KEY,\n submission_id INTEGER,\n site_id TEXT NOT NULL,\n form_type TEXT NOT NULL,\n name TEXT NOT NULL,\n email TEXT NOT NULL,\n phone TEXT,\n message TEXT,\n extra_fields TEXT,\n source_url TEXT,\n utm TEXT,\n submitted_at TEXT,\n status TEXT NOT NULL DEFAULT 'new',\n notify_status TEXT NOT NULL DEFAULT 'skipped',\n resend_message_id TEXT\n );\n CREATE INDEX IF NOT EXISTS idx_submissions_site_submitted\n ON submissions (site_id, submitted_at DESC);\n CREATE INDEX IF NOT EXISTS idx_submissions_status\n ON submissions (status);\n CREATE TABLE IF NOT EXISTS spam_screenouts (\n site_id TEXT NOT NULL,\n date TEXT NOT NULL,\n honeypot INTEGER NOT NULL DEFAULT 0,\n too_fast INTEGER NOT NULL DEFAULT 0,\n marked_spam INTEGER NOT NULL DEFAULT 0,\n PRIMARY KEY (site_id, date)\n );\n `,\n },\n {\n id: \"0002_fleet_events\",\n sql: `\n CREATE TABLE IF NOT EXISTS fleet_events (\n id TEXT PRIMARY KEY,\n ts TEXT NOT NULL,\n type TEXT NOT NULL,\n site_id TEXT,\n site_name TEXT,\n summary TEXT NOT NULL,\n data TEXT,\n created_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS idx_fleet_events_ts ON fleet_events (ts);\n `,\n },\n {\n id: \"0003_add_spam_score\",\n // SQLite `ADD COLUMN` has no `IF NOT EXISTS`, so it is NOT self-idempotent: if the\n // `_migrations` marker is lost after the column is added (crash between statement\n // and marker write), a re-run would throw `duplicate column name`. `migrate.ts`\n // recognizes that error as already-applied and records the marker, keeping the\n // runner idempotent (see `isAlreadyAppliedError`).\n sql: `ALTER TABLE submissions ADD COLUMN spam_score REAL;`,\n },\n {\n id: \"0004_add_spam_reason\",\n sql: `ALTER TABLE submissions ADD COLUMN spam_reason TEXT;`,\n },\n {\n // Outcome of the newsletter fan-out (site webhook + Mailchimp). Before this the\n // results were console.error-only, so a rotated Mailchimp key or an outage would\n // silently stop signups reaching the audience with no trace anywhere the operator\n // looks — the row still read `notify=sent`, healthy. Same non-idempotent\n // ADD COLUMN caveat as 0003.\n id: \"0005_add_fanout_status\",\n sql: `ALTER TABLE submissions ADD COLUMN fanout_status TEXT;`,\n },\n];\n","import type { Client } from \"@libsql/client\";\nimport { MIGRATIONS } from \"./migrations.js\";\n\n/** True when an error means \"this DDL was already applied\" and re-applying is a\n * no-op we can safely ignore. `CREATE ... IF NOT EXISTS` never throws, but SQLite\n * `ALTER TABLE ... ADD COLUMN` has no `IF NOT EXISTS`, so a re-run after a lost\n * `_migrations` marker throws `duplicate column name: …`. Treating that as\n * already-applied keeps `runMigrations` idempotent for `ADD COLUMN` migrations —\n * without it a lost marker would boot-loop `openDb()` on every call. */\nfunction isAlreadyAppliedError(err: unknown): boolean {\n const message = err instanceof Error ? err.message : String(err);\n return /duplicate column name/i.test(message);\n}\n\n/** Apply every not-yet-applied migration in order against a raw libSQL client,\n * tracking applied ids in `_migrations`. Idempotent: ids already present are\n * skipped, the DDL uses IF NOT EXISTS where it can, and an already-applied\n * `ADD COLUMN` (recognized by its error) is swallowed so the marker is still\n * recorded. Returns the ids applied this run. Runs on every openDb() — cheap\n * (one indexed SELECT) and the gate every fresh Turso database needs before its\n * first write. */\nexport async function runMigrations(client: Client): Promise<string[]> {\n await client.execute(\n \"CREATE TABLE IF NOT EXISTS _migrations (id TEXT PRIMARY KEY, applied_at TEXT NOT NULL)\",\n );\n const existing = await client.execute(\"SELECT id FROM _migrations\");\n const applied = new Set(existing.rows.map((r) => String(r.id)));\n const ran: string[] = [];\n for (const m of MIGRATIONS) {\n if (applied.has(m.id)) continue;\n try {\n await client.executeMultiple(m.sql);\n } catch (err) {\n // A re-run after a lost marker can re-execute a non-idempotent `ADD COLUMN`;\n // treat \"already applied\" as success so we still record the marker below and\n // never boot-loop. Any other error propagates unchanged.\n if (!isAlreadyAppliedError(err)) throw err;\n }\n await client.execute({\n sql: \"INSERT INTO _migrations (id, applied_at) VALUES (?, ?)\",\n args: [m.id, new Date().toISOString()],\n });\n ran.push(m.id);\n }\n return ran;\n}\n"],"mappings":";AAUO,IAAM,aAA0B;AAAA,EACrC;AAAA,IACE,IAAI;AAAA,IACJ,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA+BP;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaP;AAAA,EACA;AAAA,IACE,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMJ,KAAK;AAAA,EACP;AAAA,EACA;AAAA,IACE,IAAI;AAAA,IACJ,KAAK;AAAA,EACP;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAME,IAAI;AAAA,IACJ,KAAK;AAAA,EACP;AACF;;;AC1EA,SAAS,sBAAsB,KAAuB;AACpD,QAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,SAAO,yBAAyB,KAAK,OAAO;AAC9C;AASA,eAAsB,cAAc,QAAmC;AACrE,QAAM,OAAO;AAAA,IACX;AAAA,EACF;AACA,QAAM,WAAW,MAAM,OAAO,QAAQ,4BAA4B;AAClE,QAAM,UAAU,IAAI,IAAI,SAAS,KAAK,IAAI,CAAC,MAAM,OAAO,EAAE,EAAE,CAAC,CAAC;AAC9D,QAAM,MAAgB,CAAC;AACvB,aAAW,KAAK,YAAY;AAC1B,QAAI,QAAQ,IAAI,EAAE,EAAE,EAAG;AACvB,QAAI;AACF,YAAM,OAAO,gBAAgB,EAAE,GAAG;AAAA,IACpC,SAAS,KAAK;AAIZ,UAAI,CAAC,sBAAsB,GAAG,EAAG,OAAM;AAAA,IACzC;AACA,UAAM,OAAO,QAAQ;AAAA,MACnB,KAAK;AAAA,MACL,MAAM,CAAC,EAAE,KAAI,oBAAI,KAAK,GAAE,YAAY,CAAC;AAAA,IACvC,CAAC;AACD,QAAI,KAAK,EAAE,EAAE;AAAA,EACf;AACA,SAAO;AACT;","names":[]}
@@ -60,37 +60,53 @@ export default createSvelteConfig({
60
60
  });
61
61
  `
62
62
  };
63
- var ci = {
64
- config: "ci",
65
- path: ".github/workflows/ci.yml",
66
- contents: `name: ci
67
- on:
68
- pull_request:
69
- push:
70
- branches: [main]
71
- jobs:
72
- ci:
73
- uses: reddoorla/.github/.github/workflows/ci.yml@78c4da64b675f0f474961f12715f2a4c09d46eb5 # v1.0.0
74
- `
75
- };
76
63
  var renovateAction = {
77
64
  config: "renovate-action",
78
65
  path: ".github/workflows/renovate.yml",
79
66
  contents: `name: renovate
80
67
  on:
81
68
  schedule:
82
- - cron: "0 7 * * 1"
69
+ # Twice daily. GitHub's platform auto-merge is disabled fleet-wide, so Renovate
70
+ # performs its own merges from inside this run \u2014 which means this cron is the
71
+ # MERGE cadence, not just the PR-creation cadence.
72
+ - cron: 0 */12 * * *
83
73
  workflow_dispatch:
74
+ permissions:
75
+ contents: read
84
76
  jobs:
85
77
  renovate:
86
78
  runs-on: ubuntu-latest
87
79
  steps:
88
- - uses: actions/checkout@v4
89
- - uses: renovatebot/github-action@v46.1.14
80
+ # Renovate authenticates as the reddoor-renovate GitHub App (installed
81
+ # org-wide, "All repositories" \u2014 future repos are auto-covered), NOT as an
82
+ # operator PAT. Why: the App is a distinct identity, so the audit trail
83
+ # finally distinguishes bot from human (the 2026-07-26 unreviewed-major
84
+ # forensics took hours precisely because it couldn't); the credential is
85
+ # permission-scoped and centrally revocable instead of a fleet-write PAT;
86
+ # and the installation gets its own API-quota pool instead of sharing the
87
+ # operator's. The minted token is scoped to THIS repository only
88
+ # (create-github-app-token's default when owner/repositories are omitted).
89
+ # RENOVATE_APP_ID / RENOVATE_APP_PRIVATE_KEY are org-level (all-repos
90
+ # visibility) \u2014 nothing to configure per repo.
91
+ # Digest-pinned: this workflow mints a repo-write token, so a mutable tag
92
+ # ref here is a supply-chain hole \u2014 a retagged \`@v3\`/\`@v46\` would run
93
+ # attacker code with that token. Pin to the commit; Renovate bumps these.
94
+ - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
95
+ id: app-token
96
+ with:
97
+ app-id: \${{ vars.RENOVATE_APP_ID }}
98
+ private-key: \${{ secrets.RENOVATE_APP_PRIVATE_KEY }}
99
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
100
+ - uses: renovatebot/github-action@1a96852b0384df1837619d04c60b2d10d1f9ff08 # v46.1.21
90
101
  with:
91
- token: \${{ secrets.RENOVATE_TOKEN }}
102
+ token: \${{ steps.app-token.outputs.token }}
92
103
  env:
93
104
  RENOVATE_REPOSITORIES: \${{ github.repository }}
105
+ # Commit/PR identity = the App's bot user, so gitAuthor matches the
106
+ # token identity (mismatched authors read as "operator pushed this").
107
+ # 312185038 = the bot's user id (gh api 'users/reddoor-renovate[bot]' --jq .id).
108
+ RENOVATE_USERNAME: reddoor-renovate[bot]
109
+ RENOVATE_GIT_AUTHOR: reddoor-renovate[bot] <312185038+reddoor-renovate[bot]@users.noreply.github.com>
94
110
  `
95
111
  };
96
112
  var renovateConfig = {
@@ -145,7 +161,6 @@ var ALL_TEMPLATES = [
145
161
  lighthouse,
146
162
  playwrightA11y,
147
163
  svelte,
148
- ci,
149
164
  renovateAction,
150
165
  renovateConfig,
151
166
  netlify
@@ -158,4 +173,4 @@ export {
158
173
  ALL_TEMPLATES,
159
174
  templatesByName
160
175
  };
161
- //# sourceMappingURL=chunk-CASCWQQX.js.map
176
+ //# sourceMappingURL=chunk-73UFRZKY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/recipes/sync-configs/templates.ts"],"sourcesContent":["import type { ConfigName } from \"../../types.js\";\n\nexport type ConfigTemplate = {\n config: ConfigName;\n path: string;\n contents: string;\n};\n\nconst eslint: ConfigTemplate = {\n config: \"eslint\",\n path: \"eslint.config.js\",\n contents: `import { createEslintConfig } from \"@reddoorla/maintenance/configs/eslint\";\nimport svelteConfig from \"./svelte.config.js\";\n\nexport default createEslintConfig({ svelteConfig });\n`,\n};\n\nconst prettier: ConfigTemplate = {\n config: \"prettier\",\n path: \".prettierrc.json\",\n contents: `{\n \"trailingComma\": \"all\",\n \"singleQuote\": false,\n \"printWidth\": 100,\n \"plugins\": [\"prettier-plugin-svelte\"]\n}\n`,\n};\n\nconst prettierIgnore: ConfigTemplate = {\n config: \"prettier-ignore\",\n path: \".prettierignore\",\n contents: `pnpm-lock.yaml\n.svelte-kit/\nbuild/\n.netlify/\ndist/\n`,\n};\n\nconst lighthouse: ConfigTemplate = {\n config: \"lighthouse\",\n path: \"lighthouserc.json\",\n contents: `${JSON.stringify(\n {\n $note:\n \"Generated by @reddoorla/maintenance sync-configs; edit src/configs/lighthouse.ts in the package instead.\",\n extends: \"@reddoorla/maintenance/configs/lighthouse\",\n },\n null,\n 2,\n )}\n`,\n};\n\nconst playwrightA11y: ConfigTemplate = {\n config: \"playwright-a11y\",\n path: \"playwright.config.ts\",\n contents: `export { default } from \"@reddoorla/maintenance/configs/playwright-a11y\";\n`,\n};\n\nconst svelte: ConfigTemplate = {\n config: \"svelte\",\n path: \"svelte.config.js\",\n contents: `import { createSvelteConfig } from \"@reddoorla/maintenance/configs/svelte\";\nimport adapter from \"@sveltejs/adapter-netlify\";\n\n/** @type {import('@sveltejs/kit').Config} */\nexport default createSvelteConfig({\n kit: { adapter: adapter({ edge: false, split: false }) },\n});\n`,\n};\n\n// There is deliberately NO ci.yml template. Every live fleet ci.yml carries\n// per-site values a static template cannot own (`netlify-site:`, plus\n// `permissions:` and `node-version:` inputs) — an exact-match heal against a\n// canonical byte template would strip them fleet-wide in green auto-mergeable\n// PRs (the \"armed clobber\" the 2026-08-02 architecture review flagged; it was\n// this file's `ci` entry). Ownership is split instead: the STARTER clone\n// provides each site's ci.yml shape at bootstrap, and Renovate's\n// github-actions manager bumps the pinned reusable-workflow ref per repo when\n// reddoorla/.github tags a new version. The caller job name `ci` + the\n// reusable workflow's job `ci` produce the check context \"ci / ci\" —\n// REQUIRED_CHECK in src/recipes/self-updating/index.ts depends on that naming.\n\nconst renovateAction: ConfigTemplate = {\n config: \"renovate-action\",\n path: \".github/workflows/renovate.yml\",\n contents: `name: renovate\non:\n schedule:\n # Twice daily. GitHub's platform auto-merge is disabled fleet-wide, so Renovate\n # performs its own merges from inside this run — which means this cron is the\n # MERGE cadence, not just the PR-creation cadence.\n - cron: 0 */12 * * *\n workflow_dispatch:\npermissions:\n contents: read\njobs:\n renovate:\n runs-on: ubuntu-latest\n steps:\n # Renovate authenticates as the reddoor-renovate GitHub App (installed\n # org-wide, \"All repositories\" — future repos are auto-covered), NOT as an\n # operator PAT. Why: the App is a distinct identity, so the audit trail\n # finally distinguishes bot from human (the 2026-07-26 unreviewed-major\n # forensics took hours precisely because it couldn't); the credential is\n # permission-scoped and centrally revocable instead of a fleet-write PAT;\n # and the installation gets its own API-quota pool instead of sharing the\n # operator's. The minted token is scoped to THIS repository only\n # (create-github-app-token's default when owner/repositories are omitted).\n # RENOVATE_APP_ID / RENOVATE_APP_PRIVATE_KEY are org-level (all-repos\n # visibility) — nothing to configure per repo.\n # Digest-pinned: this workflow mints a repo-write token, so a mutable tag\n # ref here is a supply-chain hole — a retagged \\`@v3\\`/\\`@v46\\` would run\n # attacker code with that token. Pin to the commit; Renovate bumps these.\n - uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0\n id: app-token\n with:\n app-id: \\${{ vars.RENOVATE_APP_ID }}\n private-key: \\${{ secrets.RENOVATE_APP_PRIVATE_KEY }}\n - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7\n - uses: renovatebot/github-action@1a96852b0384df1837619d04c60b2d10d1f9ff08 # v46.1.21\n with:\n token: \\${{ steps.app-token.outputs.token }}\n env:\n RENOVATE_REPOSITORIES: \\${{ github.repository }}\n # Commit/PR identity = the App's bot user, so gitAuthor matches the\n # token identity (mismatched authors read as \"operator pushed this\").\n # 312185038 = the bot's user id (gh api 'users/reddoor-renovate[bot]' --jq .id).\n RENOVATE_USERNAME: reddoor-renovate[bot]\n RENOVATE_GIT_AUTHOR: reddoor-renovate[bot] <312185038+reddoor-renovate[bot]@users.noreply.github.com>\n`,\n};\n\nconst renovateConfig: ConfigTemplate = {\n config: \"renovate-config\",\n path: \"renovate.json\",\n contents: `{\n \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n \"extends\": [\"github>reddoorla/.github:renovate-config\"]\n}\n`,\n};\n\nconst netlify: ConfigTemplate = {\n config: \"netlify\",\n path: \"netlify.toml\",\n contents: `[build]\n command = \"pnpm build\"\n publish = \"build/\"\n functions = \"functions/\"\n\n[build.environment]\n NODE_VERSION = \"22\"\n COREPACK_INTEGRITY_KEYS = \"0\"\n\n# Baseline security headers for all responses. CSP is emitted per-response by\n# SvelteKit (see \\`kit.csp\\` in svelte.config.js) so it is intentionally omitted\n# here to avoid conflicting duplicates.\n[[headers]]\n for = \"/*\"\n [headers.values]\n Strict-Transport-Security = \"max-age=63072000; includeSubDomains; preload\"\n X-Content-Type-Options = \"nosniff\"\n X-Frame-Options = \"SAMEORIGIN\"\n Referrer-Policy = \"strict-origin-when-cross-origin\"\n Permissions-Policy = \"camera=(), microphone=(), geolocation=(), interest-cohort=()\"\n Cross-Origin-Opener-Policy = \"same-origin\"\n\n[[headers]]\n for = \"/favicon.png\"\n [headers.values]\n Cache-Control = \"public, max-age=31536000, immutable\"\n\n[[headers]]\n for = \"/_app/immutable/*\"\n [headers.values]\n Cache-Control = \"public, max-age=31536000, immutable\"\n`,\n};\n\nexport const ALL_TEMPLATES: ConfigTemplate[] = [\n eslint,\n prettier,\n prettierIgnore,\n lighthouse,\n playwrightA11y,\n svelte,\n renovateAction,\n renovateConfig,\n netlify,\n];\n\nexport function templatesByName(which: ConfigName[]): ConfigTemplate[] {\n return ALL_TEMPLATES.filter((t) => which.includes(t.config));\n}\n"],"mappings":";AAQA,IAAM,SAAyB;AAAA,EAC7B,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,UAAU;AAAA;AAAA;AAAA;AAAA;AAKZ;AAEA,IAAM,WAA2B;AAAA,EAC/B,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOZ;AAEA,IAAM,iBAAiC;AAAA,EACrC,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAMZ;AAEA,IAAM,aAA6B;AAAA,EACjC,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,UAAU,GAAG,KAAK;AAAA,IAChB;AAAA,MACE,OACE;AAAA,MACF,SAAS;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAAA;AAEH;AAEA,IAAM,iBAAiC;AAAA,EACrC,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,UAAU;AAAA;AAEZ;AAEA,IAAM,SAAyB;AAAA,EAC7B,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQZ;AAcA,IAAM,iBAAiC;AAAA,EACrC,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA6CZ;AAEA,IAAM,iBAAiC;AAAA,EACrC,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,UAAU;AAAA;AAAA;AAAA;AAAA;AAKZ;AAEA,IAAM,UAA0B;AAAA,EAC9B,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgCZ;AAEO,IAAM,gBAAkC;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,gBAAgB,OAAuC;AACrE,SAAO,cAAc,OAAO,CAAC,MAAM,MAAM,SAAS,EAAE,MAAM,CAAC;AAC7D;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  withRecipe
3
- } from "./chunk-XKOZUVK2.js";
3
+ } from "./chunk-ZTPPCHDB.js";
4
4
  import {
5
5
  defaultSpawn
6
6
  } from "./chunk-HVOOCK6L.js";
@@ -76,4 +76,4 @@ async function bumpDeps(site, opts = {}) {
76
76
  export {
77
77
  bumpDeps
78
78
  };
79
- //# sourceMappingURL=chunk-D3DQFC32.js.map
79
+ //# sourceMappingURL=chunk-ATJ6ZA2V.js.map
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-J7SZQUCW.js";
4
4
  import {
5
5
  withRecipe
6
- } from "./chunk-XKOZUVK2.js";
6
+ } from "./chunk-ZTPPCHDB.js";
7
7
  import {
8
8
  bumpDep,
9
9
  readPackageJson,
@@ -243,4 +243,4 @@ async function upgradeSvelte4to5(site, opts = {}) {
243
243
  export {
244
244
  upgradeSvelte4to5
245
245
  };
246
- //# sourceMappingURL=chunk-ZO7WNY57.js.map
246
+ //# sourceMappingURL=chunk-B5ZUQZFL.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  runMigrations
3
- } from "./chunk-HIN3P4ZN.js";
3
+ } from "./chunk-4SOCPK7A.js";
4
4
  import {
5
5
  defaultCredentialsPath
6
6
  } from "./chunk-6VAL7XJT.js";
@@ -48,4 +48,4 @@ export {
48
48
  readDbConfig,
49
49
  openDb
50
50
  };
51
- //# sourceMappingURL=chunk-PH3AGF4Z.js.map
51
+ //# sourceMappingURL=chunk-DC5H7KEQ.js.map
@@ -20,7 +20,7 @@ import {
20
20
  } from "./chunk-WEKL2HYJ.js";
21
21
  import {
22
22
  recordFleetEventsBestEffort
23
- } from "./chunk-GYNRZCM6.js";
23
+ } from "./chunk-QG6OSL4M.js";
24
24
  import {
25
25
  listWebsites,
26
26
  siteSlug,
@@ -333,4 +333,4 @@ export {
333
333
  parseAddresses,
334
334
  isProbablyEmail
335
335
  };
336
- //# sourceMappingURL=chunk-4VAUSRSS.js.map
336
+ //# sourceMappingURL=chunk-FVVGDIGX.js.map
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-KRDLHAKK.js";
5
5
  import {
6
6
  withRecipe
7
- } from "./chunk-XKOZUVK2.js";
7
+ } from "./chunk-ZTPPCHDB.js";
8
8
  import {
9
9
  defaultSpawn
10
10
  } from "./chunk-HVOOCK6L.js";
@@ -142,4 +142,4 @@ async function healthEndpoint(site, deps = { spawn: defaultSpawn }) {
142
142
  export {
143
143
  healthEndpoint
144
144
  };
145
- //# sourceMappingURL=chunk-7MJBTV2R.js.map
145
+ //# sourceMappingURL=chunk-FX7RAMGC.js.map
@@ -1,27 +1,27 @@
1
1
  import {
2
2
  convertToPnpm
3
- } from "./chunk-2X322ZES.js";
3
+ } from "./chunk-MGJ72BA4.js";
4
4
  import {
5
5
  svelteCodemods
6
- } from "./chunk-BTEBRTW4.js";
6
+ } from "./chunk-LS6QPANO.js";
7
7
  import {
8
8
  healthEndpoint
9
- } from "./chunk-7MJBTV2R.js";
9
+ } from "./chunk-FX7RAMGC.js";
10
10
  import {
11
11
  smokeSuite
12
- } from "./chunk-PKVZMFEB.js";
12
+ } from "./chunk-K5JZ5KLO.js";
13
13
  import {
14
14
  onboard
15
- } from "./chunk-ZYBOT5PM.js";
15
+ } from "./chunk-NT4WIP7I.js";
16
16
  import {
17
17
  syncConfigs
18
- } from "./chunk-JNXCDUYB.js";
18
+ } from "./chunk-Q276MZ7E.js";
19
19
  import {
20
20
  withRecipe
21
- } from "./chunk-XKOZUVK2.js";
21
+ } from "./chunk-ZTPPCHDB.js";
22
22
  import {
23
23
  runAudits
24
- } from "./chunk-3U3IGVAQ.js";
24
+ } from "./chunk-YSPA5AUT.js";
25
25
  import {
26
26
  siteLabel
27
27
  } from "./chunk-XXTZBPUY.js";
@@ -143,4 +143,4 @@ export {
143
143
  DEFAULT_INIT_STEPS,
144
144
  init
145
145
  };
146
- //# sourceMappingURL=chunk-DYKG53IR.js.map
146
+ //# sourceMappingURL=chunk-GWAOX3CU.js.map
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-KRDLHAKK.js";
5
5
  import {
6
6
  withRecipe
7
- } from "./chunk-XKOZUVK2.js";
7
+ } from "./chunk-ZTPPCHDB.js";
8
8
  import {
9
9
  defaultSpawn
10
10
  } from "./chunk-HVOOCK6L.js";
@@ -312,4 +312,4 @@ async function smokeSuite(site, deps = { spawn: defaultSpawn }) {
312
312
  export {
313
313
  smokeSuite
314
314
  };
315
- //# sourceMappingURL=chunk-PKVZMFEB.js.map
315
+ //# sourceMappingURL=chunk-K5JZ5KLO.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  approveBlockers
3
- } from "./chunk-JTRTG5CU.js";
3
+ } from "./chunk-YIQ7IWE3.js";
4
4
  import {
5
5
  siteSlug
6
6
  } from "./chunk-GXVX4HUH.js";
@@ -297,4 +297,4 @@ export {
297
297
  readDigestState,
298
298
  writeDigestState
299
299
  };
300
- //# sourceMappingURL=chunk-CZAMYVOP.js.map
300
+ //# sourceMappingURL=chunk-KPQXB5CR.js.map
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-J7SZQUCW.js";
4
4
  import {
5
5
  withRecipe
6
- } from "./chunk-XKOZUVK2.js";
6
+ } from "./chunk-ZTPPCHDB.js";
7
7
 
8
8
  // src/recipes/svelte-codemods.ts
9
9
  import { writeFile } from "fs/promises";
@@ -32,4 +32,4 @@ async function svelteCodemods(site) {
32
32
  export {
33
33
  svelteCodemods
34
34
  };
35
- //# sourceMappingURL=chunk-BTEBRTW4.js.map
35
+ //# sourceMappingURL=chunk-LS6QPANO.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  withRecipe
3
- } from "./chunk-XKOZUVK2.js";
3
+ } from "./chunk-ZTPPCHDB.js";
4
4
  import {
5
5
  readPackageJson,
6
6
  writePackageJson
@@ -96,4 +96,4 @@ async function convertToPnpm(site, opts = {}) {
96
96
  export {
97
97
  convertToPnpm
98
98
  };
99
- //# sourceMappingURL=chunk-2X322ZES.js.map
99
+ //# sourceMappingURL=chunk-MGJ72BA4.js.map
@@ -0,0 +1,137 @@
1
+ // src/github/rulesets.ts
2
+ var FLEET_RULESET_NAME = "main: reviewed changes only";
3
+ var DEFAULT_PULL_REQUEST_PARAMS = {
4
+ required_approving_review_count: 0,
5
+ dismiss_stale_reviews_on_push: false,
6
+ require_code_owner_review: false,
7
+ require_last_push_approval: false,
8
+ required_review_thread_resolution: false,
9
+ allowed_merge_methods: ["squash", "merge", "rebase"]
10
+ };
11
+ function coversDefaultBranch(include) {
12
+ return (include ?? []).some((r) => r === "~DEFAULT_BRANCH" || r === "~ALL");
13
+ }
14
+ function statusChecksOf(rule) {
15
+ const raw = rule?.parameters?.["required_status_checks"];
16
+ return Array.isArray(raw) ? raw : [];
17
+ }
18
+ function ruleOfType(rules, type) {
19
+ return (rules ?? []).find((r) => r.type === type);
20
+ }
21
+ function desiredRuleset(requiredCheck) {
22
+ return {
23
+ name: FLEET_RULESET_NAME,
24
+ target: "branch",
25
+ enforcement: "active",
26
+ bypass_actors: [],
27
+ conditions: { ref_name: { include: ["~DEFAULT_BRANCH"], exclude: [] } },
28
+ rules: [
29
+ { type: "deletion" },
30
+ { type: "non_fast_forward" },
31
+ { type: "pull_request", parameters: { ...DEFAULT_PULL_REQUEST_PARAMS } },
32
+ ...requiredCheck === null ? [] : [
33
+ {
34
+ type: "required_status_checks",
35
+ parameters: {
36
+ strict_required_status_checks_policy: true,
37
+ do_not_enforce_on_create: false,
38
+ required_status_checks: [{ context: requiredCheck }]
39
+ }
40
+ }
41
+ ]
42
+ ]
43
+ };
44
+ }
45
+ function rulesetGaps(existing, requiredCheck) {
46
+ const gaps = [];
47
+ if (existing.enforcement !== "active") {
48
+ gaps.push(`enforcement is ${JSON.stringify(existing.enforcement ?? "unset")}, not "active"`);
49
+ }
50
+ const bypass = existing.bypass_actors ?? [];
51
+ if (bypass.length > 0) {
52
+ gaps.push(`${bypass.length} bypass actor(s) present (fleet floor is an empty bypass list)`);
53
+ }
54
+ if (!coversDefaultBranch(existing.conditions?.ref_name?.include)) {
55
+ gaps.push("ref conditions do not cover the default branch");
56
+ }
57
+ const exclude = existing.conditions?.ref_name?.exclude ?? [];
58
+ if (exclude.length > 0) {
59
+ gaps.push(
60
+ `${exclude.length} ref exclude pattern(s) present (excludes can only weaken; fleet floor is none)`
61
+ );
62
+ }
63
+ for (const type of ["deletion", "non_fast_forward", "pull_request"]) {
64
+ if (!ruleOfType(existing.rules, type)) gaps.push(`missing ${type} rule`);
65
+ }
66
+ if (requiredCheck !== null) {
67
+ const rsc = ruleOfType(existing.rules, "required_status_checks");
68
+ if (!rsc) {
69
+ gaps.push(`missing required_status_checks rule ("${requiredCheck}" is observed on the repo)`);
70
+ } else {
71
+ if (!statusChecksOf(rsc).some((c) => c.context === requiredCheck)) {
72
+ gaps.push(`"${requiredCheck}" not among the required status checks`);
73
+ }
74
+ if (rsc.parameters?.["strict_required_status_checks_policy"] !== true) {
75
+ gaps.push("strict (branch-up-to-date) status-check policy is off");
76
+ }
77
+ }
78
+ }
79
+ return gaps;
80
+ }
81
+ function healRuleset(existing, requiredCheck) {
82
+ const rules = (existing.rules ?? []).map((r) => ({
83
+ type: r.type,
84
+ ...r.parameters === void 0 ? {} : { parameters: { ...r.parameters } }
85
+ }));
86
+ for (const type of ["deletion", "non_fast_forward"]) {
87
+ if (!ruleOfType(rules, type)) rules.push({ type });
88
+ }
89
+ if (!ruleOfType(rules, "pull_request")) {
90
+ rules.push({ type: "pull_request", parameters: { ...DEFAULT_PULL_REQUEST_PARAMS } });
91
+ }
92
+ if (requiredCheck !== null) {
93
+ const rsc = ruleOfType(rules, "required_status_checks");
94
+ if (!rsc) {
95
+ rules.push({
96
+ type: "required_status_checks",
97
+ parameters: {
98
+ strict_required_status_checks_policy: true,
99
+ do_not_enforce_on_create: false,
100
+ required_status_checks: [{ context: requiredCheck }]
101
+ }
102
+ });
103
+ } else {
104
+ const params = rsc.parameters ??= {};
105
+ const checks = statusChecksOf(rsc);
106
+ if (!checks.some((c) => c.context === requiredCheck)) {
107
+ params["required_status_checks"] = [...checks, { context: requiredCheck }];
108
+ }
109
+ params["strict_required_status_checks_policy"] = true;
110
+ }
111
+ }
112
+ const include = existing.conditions?.ref_name?.include ?? [];
113
+ return {
114
+ name: existing.name,
115
+ target: "branch",
116
+ enforcement: "active",
117
+ bypass_actors: [],
118
+ conditions: {
119
+ ref_name: {
120
+ include: coversDefaultBranch(include) ? [...include] : [...include, "~DEFAULT_BRANCH"],
121
+ // Excludes are cleared, never preserved: they can only remove
122
+ // protection, and the pure logic cannot tell a benign pattern from
123
+ // one that neutralizes the default branch (see rulesetGaps).
124
+ exclude: []
125
+ }
126
+ },
127
+ rules
128
+ };
129
+ }
130
+
131
+ export {
132
+ FLEET_RULESET_NAME,
133
+ desiredRuleset,
134
+ rulesetGaps,
135
+ healRuleset
136
+ };
137
+ //# sourceMappingURL=chunk-MNDIED4M.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/github/rulesets.ts"],"sourcesContent":["/**\n * The pure invariant core of ruleset self-healing (spec:\n * docs/superpowers/specs/2026-08-02-ruleset-self-healing-design.md).\n *\n * Everything here is pure data-in/data-out so the brick-risk logic — \"when is\n * it safe to require a status check\", \"what does healing preserve\" — is unit\n * tested directly. The API I/O lives in gh.ts as thin wrappers.\n *\n * Why rulesets at all, when `protectBranch` (classic protection) already\n * exists: classic protection's `enforce_admins: false` exempts admins, and on\n * 2026-07-26 that hole let a bulk `gh pr merge --auto` sweep land two\n * unreviewed MAJOR bumps (reddoor-starter#77, reddoor-website#111). A ruleset\n * with an EMPTY bypass list binds everyone, including admins. Classic\n * protection stays in place (union-enforced) — this is the stronger layer on\n * top, and the one the fleet actually relies on.\n */\n\n/** One rule inside a ruleset. Kept structurally loose (`parameters` is an open\n * record) so a GitHub-added rule type or parameter can never crash the parse —\n * unknown shapes flow through healing untouched. */\nexport type RulesetRule = {\n type: string;\n parameters?: Record<string, unknown>;\n};\n\n/** The subset of a GET /repos/{repo}/rulesets/{id} response the logic reads.\n * Extra fields (timestamps, node_id, _links, current_user_can_bypass) are\n * ignored on read and never echoed back on PUT. */\nexport type ExistingRuleset = {\n id: number;\n name: string;\n enforcement?: string;\n bypass_actors?: unknown[];\n conditions?: { ref_name?: { include?: string[]; exclude?: string[] } };\n rules?: RulesetRule[];\n};\n\n/** The payload shape POST/PUT accepts. */\nexport type RulesetPayload = {\n name: string;\n target: \"branch\";\n enforcement: \"active\";\n bypass_actors: unknown[];\n conditions: { ref_name: { include: string[]; exclude: string[] } };\n rules: RulesetRule[];\n};\n\n/** Canonical fleet ruleset name — matches the 22 hand-applied 2026-07-31/08-01. */\nexport const FLEET_RULESET_NAME = \"main: reviewed changes only\";\n\n/** `pull_request` defaults for a CREATED ruleset. 0 approvals is deliberate:\n * one-member org + GitHub forbids self-approval, so any required approval\n * deadlocks every PR (operator- and Renovate-authored alike) until the\n * Renovate App identity exists. On HEAL these are never imposed — existing\n * parameters are preserved so a hand-hardened repo (e.g.\n * `required_review_thread_resolution: true`) is not fought back down. */\nconst DEFAULT_PULL_REQUEST_PARAMS: Record<string, unknown> = {\n required_approving_review_count: 0,\n dismiss_stale_reviews_on_push: false,\n require_code_owner_review: false,\n require_last_push_approval: false,\n required_review_thread_resolution: false,\n allowed_merge_methods: [\"squash\", \"merge\", \"rebase\"],\n};\n\n/** The ref-condition tokens that cover the default branch. `~ALL` is a\n * superset of `~DEFAULT_BRANCH`, so either satisfies the floor. */\nfunction coversDefaultBranch(include: string[] | undefined): boolean {\n return (include ?? []).some((r) => r === \"~DEFAULT_BRANCH\" || r === \"~ALL\");\n}\n\nfunction statusChecksOf(rule: RulesetRule | undefined): Array<{ context?: string }> {\n const raw = rule?.parameters?.[\"required_status_checks\"];\n return Array.isArray(raw) ? (raw as Array<{ context?: string }>) : [];\n}\n\nfunction ruleOfType(rules: RulesetRule[] | undefined, type: string): RulesetRule | undefined {\n return (rules ?? []).find((r) => r.type === type);\n}\n\n/**\n * The desired payload for a repo with NO existing fleet ruleset.\n *\n * `requiredCheck` is the two-stage gate: pass the context string ONLY when\n * that check-run has been observed on the default branch (see\n * `checkContextObserved` in gh.ts); pass `null` otherwise and the ruleset is\n * created with the refs rules alone. Requiring a context that never fires\n * would make the repo permanently unmergeable by EVERYONE — bypass_actors is\n * empty by design, so there is no admin escape. (`.github` sits at stage 1\n * forever for exactly this reason: its ci.yml is `workflow_call`-only and\n * emits no check runs.)\n */\nexport function desiredRuleset(requiredCheck: string | null): RulesetPayload {\n return {\n name: FLEET_RULESET_NAME,\n target: \"branch\",\n enforcement: \"active\",\n bypass_actors: [],\n conditions: { ref_name: { include: [\"~DEFAULT_BRANCH\"], exclude: [] } },\n rules: [\n { type: \"deletion\" },\n { type: \"non_fast_forward\" },\n { type: \"pull_request\", parameters: { ...DEFAULT_PULL_REQUEST_PARAMS } },\n ...(requiredCheck === null\n ? []\n : [\n {\n type: \"required_status_checks\",\n parameters: {\n strict_required_status_checks_policy: true,\n do_not_enforce_on_create: false,\n required_status_checks: [{ context: requiredCheck }],\n },\n },\n ]),\n ],\n };\n}\n\n/**\n * Human-readable floor violations of an existing ruleset; empty array = the\n * ruleset satisfies the fleet floor and must NOT be touched (the noop path).\n *\n * The floor is deliberately a MINIMUM, not an exact shape: extra rules and\n * stronger `pull_request` parameters are not gaps. The one aggressive check is\n * `bypass_actors` — ANY entry is drift, because an empty bypass list is the\n * entire point of the ruleset layer (it is what binds admins). When the\n * Renovate App identity someday warrants a bypass actor, this is the single\n * place that decision gets encoded.\n *\n * `requiredCheck: null` scopes the floor to stage 1 (refs rules only); a\n * string additionally demands that context among required status checks with\n * strict (up-to-date) policy.\n */\nexport function rulesetGaps(existing: ExistingRuleset, requiredCheck: string | null): string[] {\n const gaps: string[] = [];\n if (existing.enforcement !== \"active\") {\n gaps.push(`enforcement is ${JSON.stringify(existing.enforcement ?? \"unset\")}, not \"active\"`);\n }\n const bypass = existing.bypass_actors ?? [];\n if (bypass.length > 0) {\n gaps.push(`${bypass.length} bypass actor(s) present (fleet floor is an empty bypass list)`);\n }\n if (!coversDefaultBranch(existing.conditions?.ref_name?.include)) {\n gaps.push(\"ref conditions do not cover the default branch\");\n }\n // Exclude patterns can ONLY remove protection (GitHub applies exclude over\n // include), and the pure floor cannot resolve which patterns match the\n // default branch — `exclude: [\"refs/heads/main\"]` neutralizes the entire\n // ruleset while every other check here still passes. It is also the\n // least-destructive-LOOKING admin edit (\"I'll just exclude main for a\n // minute\"), i.e. exactly the bypass drift this layer alarms on. The fleet\n // floor is therefore NO excludes at all.\n const exclude = existing.conditions?.ref_name?.exclude ?? [];\n if (exclude.length > 0) {\n gaps.push(\n `${exclude.length} ref exclude pattern(s) present (excludes can only weaken; fleet floor is none)`,\n );\n }\n for (const type of [\"deletion\", \"non_fast_forward\", \"pull_request\"]) {\n if (!ruleOfType(existing.rules, type)) gaps.push(`missing ${type} rule`);\n }\n if (requiredCheck !== null) {\n const rsc = ruleOfType(existing.rules, \"required_status_checks\");\n if (!rsc) {\n gaps.push(`missing required_status_checks rule (\"${requiredCheck}\" is observed on the repo)`);\n } else {\n if (!statusChecksOf(rsc).some((c) => c.context === requiredCheck)) {\n gaps.push(`\"${requiredCheck}\" not among the required status checks`);\n }\n if (rsc.parameters?.[\"strict_required_status_checks_policy\"] !== true) {\n gaps.push(\"strict (branch-up-to-date) status-check policy is off\");\n }\n }\n }\n return gaps;\n}\n\n/**\n * The healed PUT payload for a drifted ruleset: existing content merged TOWARD\n * the floor, never away from it.\n *\n * Preserved verbatim: extra rule types (a hand-added `required_linear_history`\n * survives), existing `pull_request` parameters (hand-hardening survives),\n * other required status-check contexts (union, never replace — the same\n * lesson the classic protectBranch PUT learned), and existing ref INCLUDES\n * beyond the default-branch guarantee (extra includes only strengthen).\n *\n * Healed: enforcement → active, bypass_actors → [], and ref excludes → []\n * (both deliberate exceptions to never-weaken, because both are pure\n * weakenings — a bypass actor or an exclude matching the default branch IS\n * the drift being alarmed on), missing rules added, and — only when\n * `requiredCheck` is non-null — the context unioned in with strict policy\n * forced on.\n */\nexport function healRuleset(\n existing: ExistingRuleset,\n requiredCheck: string | null,\n): RulesetPayload {\n const rules: RulesetRule[] = (existing.rules ?? []).map((r) => ({\n type: r.type,\n ...(r.parameters === undefined ? {} : { parameters: { ...r.parameters } }),\n }));\n for (const type of [\"deletion\", \"non_fast_forward\"]) {\n if (!ruleOfType(rules, type)) rules.push({ type });\n }\n if (!ruleOfType(rules, \"pull_request\")) {\n rules.push({ type: \"pull_request\", parameters: { ...DEFAULT_PULL_REQUEST_PARAMS } });\n }\n if (requiredCheck !== null) {\n const rsc = ruleOfType(rules, \"required_status_checks\");\n if (!rsc) {\n rules.push({\n type: \"required_status_checks\",\n parameters: {\n strict_required_status_checks_policy: true,\n do_not_enforce_on_create: false,\n required_status_checks: [{ context: requiredCheck }],\n },\n });\n } else {\n const params = (rsc.parameters ??= {});\n const checks = statusChecksOf(rsc);\n if (!checks.some((c) => c.context === requiredCheck)) {\n params[\"required_status_checks\"] = [...checks, { context: requiredCheck }];\n }\n params[\"strict_required_status_checks_policy\"] = true;\n }\n }\n\n const include = existing.conditions?.ref_name?.include ?? [];\n return {\n name: existing.name,\n target: \"branch\",\n enforcement: \"active\",\n bypass_actors: [],\n conditions: {\n ref_name: {\n include: coversDefaultBranch(include) ? [...include] : [...include, \"~DEFAULT_BRANCH\"],\n // Excludes are cleared, never preserved: they can only remove\n // protection, and the pure logic cannot tell a benign pattern from\n // one that neutralizes the default branch (see rulesetGaps).\n exclude: [],\n },\n },\n rules,\n };\n}\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  withRecipe
3
- } from "./chunk-XKOZUVK2.js";
3
+ } from "./chunk-ZTPPCHDB.js";
4
4
  import {
5
5
  bumpDep,
6
6
  readPackageJson,
@@ -150,4 +150,4 @@ export {
150
150
  selfCaretRange,
151
151
  onboard
152
152
  };
153
- //# sourceMappingURL=chunk-ZYBOT5PM.js.map
153
+ //# sourceMappingURL=chunk-NT4WIP7I.js.map
@@ -1,10 +1,10 @@
1
1
  import {
2
2
  ALL_TEMPLATES,
3
3
  templatesByName
4
- } from "./chunk-CASCWQQX.js";
4
+ } from "./chunk-73UFRZKY.js";
5
5
  import {
6
6
  withRecipe
7
- } from "./chunk-XKOZUVK2.js";
7
+ } from "./chunk-ZTPPCHDB.js";
8
8
  import {
9
9
  listTrackedFiles,
10
10
  removeFromIndex
@@ -125,7 +125,6 @@ var ALL_CONFIG_NAMES = [
125
125
  "playwright-a11y",
126
126
  "svelte",
127
127
  "gitignore",
128
- "ci",
129
128
  "renovate-action",
130
129
  "renovate-config",
131
130
  "netlify"
@@ -208,4 +207,4 @@ export {
208
207
  isConfigName,
209
208
  syncConfigs
210
209
  };
211
- //# sourceMappingURL=chunk-JNXCDUYB.js.map
210
+ //# sourceMappingURL=chunk-Q276MZ7E.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/recipes/sync-configs.ts","../src/recipes/sync-configs/gitignore.ts"],"sourcesContent":["import { readFile, writeFile, mkdir } from \"node:fs/promises\";\nimport { join, dirname } from \"node:path\";\nimport type { RecipeResult, Site, ConfigName } from \"../types.js\";\nimport { ALL_TEMPLATES, templatesByName, type ConfigTemplate } from \"./sync-configs/templates.js\";\nimport {\n CANONICAL_GITIGNORE_ENTRIES,\n mergeGitignore,\n findTrackedArtifacts,\n} from \"./sync-configs/gitignore.js\";\nimport { listTrackedFiles, removeFromIndex } from \"../util/git.js\";\nimport { withRecipe } from \"./_with-recipe.js\";\n\nexport type SyncConfigsOptions = {\n which?: ConfigName[];\n};\n\nconst GITIGNORE_CONFIG: ConfigName = \"gitignore\";\nconst SVELTE_CONFIG: ConfigName = \"svelte\";\nconst NETLIFY_CONFIG: ConfigName = \"netlify\";\n\n/** A site's `svelte.config.js` is \"compliant\" — and left untouched by sync —\n * once it builds on the canonical helpers (createSvelteConfig + adapter-netlify).\n *\n * Unlike the other exact-match templates, svelte.config legitimately carries\n * site-specific `kit.alias` and `compilerOptions`; an exact overwrite would\n * clobber those on every sync (it silently dropped MSOT's $utils alias,\n * 2026-06-04). So once a config is on the canonical pattern we preserve it as-is\n * and only rewrite a genuinely off-pattern (or missing) config. `createSvelteConfig`\n * now provides the canonical `$lib` aliases itself, and a site's own `kit.alias`\n * overrides per key (and may add more), so a site's additive customization is safe\n * to preserve. */\nfunction isSvelteConfigCompliant(contents: string): boolean {\n return contents.includes(\"createSvelteConfig\") && contents.includes(\"@sveltejs/adapter-netlify\");\n}\n\n/** Any of the baseline security headers — the marker that a netlify.toml is\n * deliberately hardened (vs. e.g. a cache-control-only `[[headers]]` block). */\nconst SECURITY_HEADER_RE =\n /Strict-Transport-Security|Content-Security-Policy|X-Frame-Options|X-Content-Type-Options|Referrer-Policy|Permissions-Policy|Cross-Origin-Opener-Policy/i;\n\n/** A site's `netlify.toml` is \"compliant\" — and left untouched by sync — once it\n * carries a `[[headers]]` block AND a security header (HSTS/CSP/X-Frame-Options/…).\n *\n * Like svelte.config, netlify.toml legitimately holds site-specific config\n * (custom CSP, redirects, per-route headers). The canonical template ships the\n * baseline security headers, but an exact overwrite would CLOBBER a site's own\n * hardening — that bug stripped gallerysonder's headers on a routine sync\n * (2026-06-10). So a genuinely-hardened file is left alone, while a missing,\n * header-less (previously-stripped), OR merely cache-header file is non-compliant\n * and gets the canonical template, which backfills the security baseline. */\nfunction isNetlifyConfigCompliant(contents: string): boolean {\n return contents.includes(\"[[headers]]\") && SECURITY_HEADER_RE.test(contents);\n}\n\n/** Runtime enumeration of every `ConfigName`. Mirror of the union in\n * `src/types.ts`. Used by CLI `--only` validation; a missing entry would\n * silently accept typos. The type-test in `tests/types.test.ts` guards\n * against drift between this array and the union. */\nexport const ALL_CONFIG_NAMES: ConfigName[] = [\n \"lighthouse\",\n \"eslint\",\n \"prettier\",\n \"prettier-ignore\",\n \"playwright-a11y\",\n \"svelte\",\n \"gitignore\",\n \"renovate-action\",\n \"renovate-config\",\n \"netlify\",\n];\n\nexport function isConfigName(value: string): value is ConfigName {\n return (ALL_CONFIG_NAMES as string[]).includes(value);\n}\n\nasync function readMaybe(path: string): Promise<string | null> {\n try {\n return await readFile(path, \"utf-8\");\n } catch {\n return null;\n }\n}\n\nasync function planTemplateDiffs(\n cwd: string,\n templates: ConfigTemplate[],\n): Promise<ConfigTemplate[]> {\n const diffs: ConfigTemplate[] = [];\n for (const t of templates) {\n const existing = await readMaybe(join(cwd, t.path));\n if (existing === t.contents) continue;\n // svelte.config is compliance-checked, not exact-matched: an existing config\n // already on the canonical pattern is left alone so its aliases/compilerOptions\n // survive. A missing (null) or off-pattern config still gets the canonical template.\n if (t.config === SVELTE_CONFIG && existing !== null && isSvelteConfigCompliant(existing)) {\n continue;\n }\n // netlify.toml is likewise compliance-checked: a file that already carries\n // `[[headers]]` is hardened and left alone (an exact overwrite would strip\n // its security headers). A header-less / missing file gets the template.\n if (t.config === NETLIFY_CONFIG && existing !== null && isNetlifyConfigCompliant(existing)) {\n continue;\n }\n diffs.push(t);\n }\n return diffs;\n}\n\ntype GitignorePlan =\n { kind: \"noop\" } | { kind: \"apply\"; content: string; toUntrack: string[]; added: string[] };\n\nasync function planGitignore(cwd: string): Promise<GitignorePlan> {\n const existing = await readMaybe(join(cwd, \".gitignore\"));\n const merge = mergeGitignore(existing, CANONICAL_GITIGNORE_ENTRIES);\n const tracked = await listTrackedFiles(cwd);\n const toUntrack = findTrackedArtifacts(tracked, CANONICAL_GITIGNORE_ENTRIES);\n if (merge.added.length === 0 && toUntrack.length === 0) return { kind: \"noop\" };\n return { kind: \"apply\", content: merge.content, toUntrack, added: merge.added };\n}\n\nasync function applyGitignore(\n cwd: string,\n plan: Extract<GitignorePlan, { kind: \"apply\" }>,\n): Promise<void> {\n await writeFile(join(cwd, \".gitignore\"), plan.content, \"utf-8\");\n if (plan.toUntrack.length > 0) {\n await removeFromIndex(cwd, plan.toUntrack);\n }\n}\n\nexport async function syncConfigs(\n site: Site,\n opts: SyncConfigsOptions = {},\n): Promise<RecipeResult> {\n const requested = opts.which ?? ALL_TEMPLATES.map((t) => t.config).concat(GITIGNORE_CONFIG);\n const templateNames = requested.filter((c): c is ConfigName => c !== GITIGNORE_CONFIG);\n const templates = templatesByName(templateNames);\n const includeGitignore = requested.includes(GITIGNORE_CONFIG);\n\n return withRecipe({\n name: \"sync-configs\",\n site,\n plan: async () => {\n const templateDiffs = await planTemplateDiffs(site.path, templates);\n const gitignorePlan: GitignorePlan = includeGitignore\n ? await planGitignore(site.path)\n : { kind: \"noop\" };\n if (templateDiffs.length === 0 && gitignorePlan.kind === \"noop\") {\n return { kind: \"noop\", notes: \"all targeted configs already match\" };\n }\n return { kind: \"apply\", plan: { templateDiffs, gitignorePlan } };\n },\n apply: async ({ templateDiffs, gitignorePlan }, { commit }) => {\n for (const t of templateDiffs) {\n const dest = join(site.path, t.path);\n await mkdir(dirname(dest), { recursive: true });\n await writeFile(dest, t.contents, \"utf-8\");\n await commit(`chore: sync ${t.config} config from @reddoorla/maintenance`);\n }\n if (gitignorePlan.kind === \"apply\") {\n await applyGitignore(site.path, gitignorePlan);\n await commit(`chore: sync gitignore from @reddoorla/maintenance`);\n }\n return { kind: \"ok\" };\n },\n });\n}\n","/**\n * Comment line written above the appended block so future runs (and humans)\n * can recognize the managed section. Presence of this line is incidental —\n * the merge logic is keyed on each entry's normalized form, not on the marker.\n */\nexport const MANAGED_MARKER = \"# canonical entries from @reddoorla/maintenance sync-configs\";\n\n/**\n * Build artifacts, test outputs, deploy caches, and secrets that should never\n * be tracked across the reddoor fleet. Sites may keep additional site-specific\n * entries — they are preserved on merge.\n */\nexport const CANONICAL_GITIGNORE_ENTRIES: readonly string[] = [\n \"node_modules/\",\n \"build/\",\n \"dist/\",\n \".svelte-kit/\",\n \"coverage/\",\n \".vitest-cache/\",\n \"playwright-report/\",\n \"test-results/\",\n \".lighthouseci/\",\n \".tsbuildinfo\",\n \".env\",\n \".env.*\",\n \"!.env.example\",\n \".DS_Store\",\n \"*.log\",\n \".vercel/\",\n \".netlify/\",\n \".reddoor-a11y/\",\n // The a11y audit's transient spec dir, written inside the checkout and\n // normally cleaned, but a timeout-SIGKILL of the parent orphans it. Ignored\n // fleet-wide so it never dirties a self-updating repo's tree (2026-06-10 M-D).\n \".reddoor-a11y-spec-*/\",\n];\n\nexport type MergeResult = { content: string; added: string[] };\n\nfunction stripLeadingSlash(s: string): string {\n return s.startsWith(\"/\") ? s.slice(1) : s;\n}\n\nfunction stripTrailingSlash(s: string): string {\n return s.endsWith(\"/\") ? s.slice(0, -1) : s;\n}\n\n/**\n * Normalize for presence comparison only: strip leading `/`, trailing `/`,\n * and surrounding whitespace. `build`, `/build`, `build/`, and `/build/` all\n * collapse to the same key.\n */\nfunction normalizePresence(line: string): string {\n return stripTrailingSlash(stripLeadingSlash(line.trim()));\n}\n\nfunction presentSet(existing: string): Set<string> {\n const set = new Set<string>();\n for (const raw of existing.split(/\\r?\\n/)) {\n const trimmed = raw.trim();\n if (!trimmed) continue;\n if (trimmed.startsWith(\"#\")) continue;\n set.add(normalizePresence(trimmed));\n }\n return set;\n}\n\n/**\n * Merge `canonical` entries into `existing` .gitignore content.\n *\n * - Missing entries are appended under a managed marker comment.\n * - Existing entries (in any normalized variant — `/build`, `build/`, etc.)\n * are preserved as-is; we never rewrite the site's own lines.\n * - When every canonical entry is already present, returns the original\n * content unchanged with `added: []` — the recipe can treat that as noop.\n */\nexport function mergeGitignore(existing: string | null, canonical: readonly string[]): MergeResult {\n if (existing === null) {\n const body = [MANAGED_MARKER, ...canonical].join(\"\\n\") + \"\\n\";\n return { content: body, added: [...canonical] };\n }\n const present = presentSet(existing);\n const added: string[] = [];\n for (const entry of canonical) {\n const norm = normalizePresence(entry);\n if (!present.has(norm)) {\n added.push(entry);\n present.add(norm);\n }\n }\n if (added.length === 0) {\n return { content: existing, added: [] };\n }\n let base = existing;\n if (!base.endsWith(\"\\n\")) base += \"\\n\";\n const block = [\"\", MANAGED_MARKER, ...added].join(\"\\n\") + \"\\n\";\n return { content: base + block, added };\n}\n\n/**\n * Of the tracked paths, return those that fall under a canonical *directory*\n * entry — i.e., paths that the freshly-synced .gitignore now wants ignored\n * but which git currently has in the index.\n *\n * File-pattern entries (`.env`, `*.log`, `.DS_Store`) are intentionally\n * skipped: they may contain user-meaningful data, and `git rm --cached`\n * cannot scrub secrets from history anyway. Surfaced for manual review\n * instead of auto-removing.\n */\nexport function findTrackedArtifacts(\n tracked: readonly string[],\n canonical: readonly string[],\n): string[] {\n const dirEntries: string[] = [];\n for (const raw of canonical) {\n const t = raw.trim();\n if (!t) continue;\n if (t.startsWith(\"!\")) continue;\n if (/[*?[]/.test(t)) continue;\n const noLead = stripLeadingSlash(t);\n if (!noLead.endsWith(\"/\")) continue;\n const name = stripTrailingSlash(noLead);\n if (!name) continue;\n dirEntries.push(name);\n }\n const matched: string[] = [];\n for (const path of tracked) {\n for (const dir of dirEntries) {\n if (path === dir || path.startsWith(dir + \"/\")) {\n matched.push(path);\n break;\n }\n }\n }\n return matched;\n}\n"],"mappings":";;;;;;;;;;;;;AAAA,SAAS,UAAU,WAAW,aAAa;AAC3C,SAAS,MAAM,eAAe;;;ACIvB,IAAM,iBAAiB;AAOvB,IAAM,8BAAiD;AAAA,EAC5D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA,EAIA;AACF;AAIA,SAAS,kBAAkB,GAAmB;AAC5C,SAAO,EAAE,WAAW,GAAG,IAAI,EAAE,MAAM,CAAC,IAAI;AAC1C;AAEA,SAAS,mBAAmB,GAAmB;AAC7C,SAAO,EAAE,SAAS,GAAG,IAAI,EAAE,MAAM,GAAG,EAAE,IAAI;AAC5C;AAOA,SAAS,kBAAkB,MAAsB;AAC/C,SAAO,mBAAmB,kBAAkB,KAAK,KAAK,CAAC,CAAC;AAC1D;AAEA,SAAS,WAAW,UAA+B;AACjD,QAAM,MAAM,oBAAI,IAAY;AAC5B,aAAW,OAAO,SAAS,MAAM,OAAO,GAAG;AACzC,UAAM,UAAU,IAAI,KAAK;AACzB,QAAI,CAAC,QAAS;AACd,QAAI,QAAQ,WAAW,GAAG,EAAG;AAC7B,QAAI,IAAI,kBAAkB,OAAO,CAAC;AAAA,EACpC;AACA,SAAO;AACT;AAWO,SAAS,eAAe,UAAyB,WAA2C;AACjG,MAAI,aAAa,MAAM;AACrB,UAAM,OAAO,CAAC,gBAAgB,GAAG,SAAS,EAAE,KAAK,IAAI,IAAI;AACzD,WAAO,EAAE,SAAS,MAAM,OAAO,CAAC,GAAG,SAAS,EAAE;AAAA,EAChD;AACA,QAAM,UAAU,WAAW,QAAQ;AACnC,QAAM,QAAkB,CAAC;AACzB,aAAW,SAAS,WAAW;AAC7B,UAAM,OAAO,kBAAkB,KAAK;AACpC,QAAI,CAAC,QAAQ,IAAI,IAAI,GAAG;AACtB,YAAM,KAAK,KAAK;AAChB,cAAQ,IAAI,IAAI;AAAA,IAClB;AAAA,EACF;AACA,MAAI,MAAM,WAAW,GAAG;AACtB,WAAO,EAAE,SAAS,UAAU,OAAO,CAAC,EAAE;AAAA,EACxC;AACA,MAAI,OAAO;AACX,MAAI,CAAC,KAAK,SAAS,IAAI,EAAG,SAAQ;AAClC,QAAM,QAAQ,CAAC,IAAI,gBAAgB,GAAG,KAAK,EAAE,KAAK,IAAI,IAAI;AAC1D,SAAO,EAAE,SAAS,OAAO,OAAO,MAAM;AACxC;AAYO,SAAS,qBACd,SACA,WACU;AACV,QAAM,aAAuB,CAAC;AAC9B,aAAW,OAAO,WAAW;AAC3B,UAAM,IAAI,IAAI,KAAK;AACnB,QAAI,CAAC,EAAG;AACR,QAAI,EAAE,WAAW,GAAG,EAAG;AACvB,QAAI,QAAQ,KAAK,CAAC,EAAG;AACrB,UAAM,SAAS,kBAAkB,CAAC;AAClC,QAAI,CAAC,OAAO,SAAS,GAAG,EAAG;AAC3B,UAAM,OAAO,mBAAmB,MAAM;AACtC,QAAI,CAAC,KAAM;AACX,eAAW,KAAK,IAAI;AAAA,EACtB;AACA,QAAM,UAAoB,CAAC;AAC3B,aAAW,QAAQ,SAAS;AAC1B,eAAW,OAAO,YAAY;AAC5B,UAAI,SAAS,OAAO,KAAK,WAAW,MAAM,GAAG,GAAG;AAC9C,gBAAQ,KAAK,IAAI;AACjB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;;;ADvHA,IAAM,mBAA+B;AACrC,IAAM,gBAA4B;AAClC,IAAM,iBAA6B;AAanC,SAAS,wBAAwB,UAA2B;AAC1D,SAAO,SAAS,SAAS,oBAAoB,KAAK,SAAS,SAAS,2BAA2B;AACjG;AAIA,IAAM,qBACJ;AAYF,SAAS,yBAAyB,UAA2B;AAC3D,SAAO,SAAS,SAAS,aAAa,KAAK,mBAAmB,KAAK,QAAQ;AAC7E;AAMO,IAAM,mBAAiC;AAAA,EAC5C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,aAAa,OAAoC;AAC/D,SAAQ,iBAA8B,SAAS,KAAK;AACtD;AAEA,eAAe,UAAU,MAAsC;AAC7D,MAAI;AACF,WAAO,MAAM,SAAS,MAAM,OAAO;AAAA,EACrC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAe,kBACb,KACA,WAC2B;AAC3B,QAAM,QAA0B,CAAC;AACjC,aAAW,KAAK,WAAW;AACzB,UAAM,WAAW,MAAM,UAAU,KAAK,KAAK,EAAE,IAAI,CAAC;AAClD,QAAI,aAAa,EAAE,SAAU;AAI7B,QAAI,EAAE,WAAW,iBAAiB,aAAa,QAAQ,wBAAwB,QAAQ,GAAG;AACxF;AAAA,IACF;AAIA,QAAI,EAAE,WAAW,kBAAkB,aAAa,QAAQ,yBAAyB,QAAQ,GAAG;AAC1F;AAAA,IACF;AACA,UAAM,KAAK,CAAC;AAAA,EACd;AACA,SAAO;AACT;AAKA,eAAe,cAAc,KAAqC;AAChE,QAAM,WAAW,MAAM,UAAU,KAAK,KAAK,YAAY,CAAC;AACxD,QAAM,QAAQ,eAAe,UAAU,2BAA2B;AAClE,QAAM,UAAU,MAAM,iBAAiB,GAAG;AAC1C,QAAM,YAAY,qBAAqB,SAAS,2BAA2B;AAC3E,MAAI,MAAM,MAAM,WAAW,KAAK,UAAU,WAAW,EAAG,QAAO,EAAE,MAAM,OAAO;AAC9E,SAAO,EAAE,MAAM,SAAS,SAAS,MAAM,SAAS,WAAW,OAAO,MAAM,MAAM;AAChF;AAEA,eAAe,eACb,KACA,MACe;AACf,QAAM,UAAU,KAAK,KAAK,YAAY,GAAG,KAAK,SAAS,OAAO;AAC9D,MAAI,KAAK,UAAU,SAAS,GAAG;AAC7B,UAAM,gBAAgB,KAAK,KAAK,SAAS;AAAA,EAC3C;AACF;AAEA,eAAsB,YACpB,MACA,OAA2B,CAAC,GACL;AACvB,QAAM,YAAY,KAAK,SAAS,cAAc,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,gBAAgB;AAC1F,QAAM,gBAAgB,UAAU,OAAO,CAAC,MAAuB,MAAM,gBAAgB;AACrF,QAAM,YAAY,gBAAgB,aAAa;AAC/C,QAAM,mBAAmB,UAAU,SAAS,gBAAgB;AAE5D,SAAO,WAAW;AAAA,IAChB,MAAM;AAAA,IACN;AAAA,IACA,MAAM,YAAY;AAChB,YAAM,gBAAgB,MAAM,kBAAkB,KAAK,MAAM,SAAS;AAClE,YAAM,gBAA+B,mBACjC,MAAM,cAAc,KAAK,IAAI,IAC7B,EAAE,MAAM,OAAO;AACnB,UAAI,cAAc,WAAW,KAAK,cAAc,SAAS,QAAQ;AAC/D,eAAO,EAAE,MAAM,QAAQ,OAAO,qCAAqC;AAAA,MACrE;AACA,aAAO,EAAE,MAAM,SAAS,MAAM,EAAE,eAAe,cAAc,EAAE;AAAA,IACjE;AAAA,IACA,OAAO,OAAO,EAAE,eAAe,cAAc,GAAG,EAAE,OAAO,MAAM;AAC7D,iBAAW,KAAK,eAAe;AAC7B,cAAM,OAAO,KAAK,KAAK,MAAM,EAAE,IAAI;AACnC,cAAM,MAAM,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9C,cAAM,UAAU,MAAM,EAAE,UAAU,OAAO;AACzC,cAAM,OAAO,eAAe,EAAE,MAAM,qCAAqC;AAAA,MAC3E;AACA,UAAI,cAAc,SAAS,SAAS;AAClC,cAAM,eAAe,KAAK,MAAM,aAAa;AAC7C,cAAM,OAAO,mDAAmD;AAAA,MAClE;AACA,aAAO,EAAE,MAAM,KAAK;AAAA,IACtB;AAAA,EACF,CAAC;AACH;","names":[]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  openDb,
3
3
  readDbConfig
4
- } from "./chunk-PH3AGF4Z.js";
4
+ } from "./chunk-DC5H7KEQ.js";
5
5
 
6
6
  // src/db/fleet-events.ts
7
7
  async function recordFleetEvent(db, e) {
@@ -47,4 +47,4 @@ async function recordFleetEventsBestEffort(events, now, open = () => openDb(read
47
47
  export {
48
48
  recordFleetEventsBestEffort
49
49
  };
50
- //# sourceMappingURL=chunk-GYNRZCM6.js.map
50
+ //# sourceMappingURL=chunk-QG6OSL4M.js.map