@homeflare/alchemy 0.27.4 → 0.29.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 (217) hide show
  1. package/README.md +2 -0
  2. package/dist/caddy/admin-address.d.ts +27 -0
  3. package/dist/caddy/admin-address.d.ts.map +1 -0
  4. package/dist/caddy/admin-calls.d.ts +25 -28
  5. package/dist/caddy/admin-calls.d.ts.map +1 -1
  6. package/dist/caddy/admin.d.ts +28 -38
  7. package/dist/caddy/admin.d.ts.map +1 -1
  8. package/dist/caddy/caddy-http-client.d.ts +25 -0
  9. package/dist/caddy/caddy-http-client.d.ts.map +1 -0
  10. package/dist/caddy/config-errors.d.ts +24 -0
  11. package/dist/caddy/config-errors.d.ts.map +1 -0
  12. package/dist/caddy/config-lifecycle.d.ts +20 -49
  13. package/dist/caddy/config-lifecycle.d.ts.map +1 -1
  14. package/dist/caddy/config-reconcile.d.ts +45 -0
  15. package/dist/caddy/config-reconcile.d.ts.map +1 -0
  16. package/dist/caddy/config.d.ts +4 -3
  17. package/dist/caddy/config.d.ts.map +1 -1
  18. package/dist/caddy/fake-caddy.d.ts +10 -2
  19. package/dist/caddy/fake-caddy.d.ts.map +1 -1
  20. package/dist/caddy/index.d.ts +3 -3
  21. package/dist/caddy/index.d.ts.map +1 -1
  22. package/dist/caddy/index.js +487 -444
  23. package/dist/caddy/index.js.map +16 -12
  24. package/dist/caddy/local-admin.d.ts +20 -34
  25. package/dist/caddy/local-admin.d.ts.map +1 -1
  26. package/dist/caddy/providers.d.ts +19 -3
  27. package/dist/caddy/providers.d.ts.map +1 -1
  28. package/dist/cloudflare/mesh-node-token.d.ts +1 -1
  29. package/dist/cloudflare/mesh-node-token.d.ts.map +1 -1
  30. package/dist/forgejo/branch-protection.d.ts +4 -4
  31. package/dist/forgejo/org-actions-secrets.d.ts +4 -4
  32. package/dist/forgejo/org-label.d.ts +4 -4
  33. package/dist/forgejo/org-team.d.ts +4 -4
  34. package/dist/forgejo/repo-webhook.d.ts +4 -4
  35. package/dist/forgejo/repository.d.ts +4 -4
  36. package/dist/forgejo/team-member.d.ts +4 -4
  37. package/dist/github/declare-repo-baseline.d.ts +1 -1
  38. package/dist/github/declare-repo-baseline.d.ts.map +1 -1
  39. package/dist/github/index.d.ts +9 -5
  40. package/dist/github/index.d.ts.map +1 -1
  41. package/dist/github/index.js +161 -25
  42. package/dist/github/index.js.map +15 -13
  43. package/dist/github/repo-baseline-data.d.ts +10 -1
  44. package/dist/github/repo-baseline-data.d.ts.map +1 -1
  45. package/dist/github/repo-policy.d.ts +1 -1
  46. package/dist/github/repo-policy.d.ts.map +1 -1
  47. package/dist/github/repository-ruleset-errors.d.ts +37 -3
  48. package/dist/github/repository-ruleset-errors.d.ts.map +1 -1
  49. package/dist/github/repository-ruleset-form.d.ts.map +1 -1
  50. package/dist/github/repository-ruleset-guards.d.ts +15 -2
  51. package/dist/github/repository-ruleset-guards.d.ts.map +1 -1
  52. package/dist/github/repository-ruleset-narrowing-guards.d.ts +39 -0
  53. package/dist/github/repository-ruleset-narrowing-guards.d.ts.map +1 -0
  54. package/dist/github/repository-ruleset-octokit.d.ts +49 -0
  55. package/dist/github/repository-ruleset-octokit.d.ts.map +1 -1
  56. package/dist/github/repository-ruleset-probe.d.ts +2 -1
  57. package/dist/github/repository-ruleset-probe.d.ts.map +1 -1
  58. package/dist/github/repository-ruleset-props.d.ts +91 -0
  59. package/dist/github/repository-ruleset-props.d.ts.map +1 -0
  60. package/dist/github/repository-ruleset-providers.d.ts +42 -0
  61. package/dist/github/repository-ruleset-providers.d.ts.map +1 -0
  62. package/dist/github/repository-ruleset-reconcile.d.ts.map +1 -1
  63. package/dist/github/repository-ruleset.d.ts +28 -34
  64. package/dist/github/repository-ruleset.d.ts.map +1 -1
  65. package/dist/litellm/fake-litellm.d.ts +27 -16
  66. package/dist/litellm/fake-litellm.d.ts.map +1 -1
  67. package/dist/litellm/fake-stack.d.ts +9 -4
  68. package/dist/litellm/fake-stack.d.ts.map +1 -1
  69. package/dist/litellm/index.d.ts +7 -5
  70. package/dist/litellm/index.d.ts.map +1 -1
  71. package/dist/litellm/index.js +53 -123
  72. package/dist/litellm/index.js.map +8 -8
  73. package/dist/litellm/operations.d.ts +60 -0
  74. package/dist/litellm/operations.d.ts.map +1 -0
  75. package/dist/litellm/pass-through-endpoint.d.ts +10 -28
  76. package/dist/litellm/pass-through-endpoint.d.ts.map +1 -1
  77. package/dist/litellm/pass-through-errors.d.ts +37 -0
  78. package/dist/litellm/pass-through-errors.d.ts.map +1 -0
  79. package/dist/litellm/pass-through-form.d.ts +11 -4
  80. package/dist/litellm/pass-through-form.d.ts.map +1 -1
  81. package/dist/litellm/providers.d.ts +7 -5
  82. package/dist/litellm/providers.d.ts.map +1 -1
  83. package/dist/netbox/prefix.d.ts +4 -4
  84. package/dist/paperless/custom-field.d.ts +21 -21
  85. package/dist/paperless/custom-field.d.ts.map +1 -1
  86. package/dist/paperless/document-type.d.ts +34 -4
  87. package/dist/paperless/document-type.d.ts.map +1 -1
  88. package/dist/paperless/fake-paperless.d.ts +36 -19
  89. package/dist/paperless/fake-paperless.d.ts.map +1 -1
  90. package/dist/paperless/index.d.ts +7 -2
  91. package/dist/paperless/index.d.ts.map +1 -1
  92. package/dist/paperless/index.js +164 -228
  93. package/dist/paperless/index.js.map +10 -13
  94. package/dist/paperless/matching-locate.d.ts +42 -20
  95. package/dist/paperless/matching-locate.d.ts.map +1 -1
  96. package/dist/paperless/matching-types.d.ts +47 -18
  97. package/dist/paperless/matching-types.d.ts.map +1 -1
  98. package/dist/paperless/matching.d.ts +30 -27
  99. package/dist/paperless/matching.d.ts.map +1 -1
  100. package/dist/paperless/providers.d.ts +13 -7
  101. package/dist/paperless/providers.d.ts.map +1 -1
  102. package/dist/paperless/storage-path.d.ts +34 -4
  103. package/dist/paperless/storage-path.d.ts.map +1 -1
  104. package/dist/paperless/tag.d.ts +43 -8
  105. package/dist/paperless/tag.d.ts.map +1 -1
  106. package/dist/proxmox/acl.d.ts +30 -23
  107. package/dist/proxmox/acl.d.ts.map +1 -1
  108. package/dist/proxmox/distilled-pve.d.ts +89 -0
  109. package/dist/proxmox/distilled-pve.d.ts.map +1 -0
  110. package/dist/proxmox/index.js +663 -609
  111. package/dist/proxmox/index.js.map +12 -11
  112. package/dist/proxmox/members.d.ts +29 -10
  113. package/dist/proxmox/members.d.ts.map +1 -1
  114. package/docs/caddy-admin.md +1 -1
  115. package/docs/caddy.md +3 -2
  116. package/docs/credentials.md +16 -0
  117. package/docs/litellm.md +21 -13
  118. package/docs/paperless.md +12 -0
  119. package/docs/peers.md +5 -0
  120. package/docs/repo-policy.md +16 -1
  121. package/docs/repository-ruleset.md +53 -13
  122. package/docs/upstream-conformance.md +126 -10
  123. package/package.json +9 -2
  124. package/src/caddy/admin-address.ts +71 -0
  125. package/src/caddy/admin-calls.ts +48 -100
  126. package/src/caddy/admin.ts +37 -41
  127. package/src/caddy/adopt-scope.test.ts +1 -1
  128. package/src/caddy/caddy-http-client.ts +229 -0
  129. package/src/caddy/config-adopt-provider.test.ts +139 -0
  130. package/src/caddy/config-adopt.test.ts +60 -126
  131. package/src/caddy/config-errors.ts +47 -0
  132. package/src/caddy/config-lifecycle.test.ts +55 -42
  133. package/src/caddy/config-lifecycle.ts +72 -162
  134. package/src/caddy/config-reconcile.ts +138 -0
  135. package/src/caddy/config.ts +36 -42
  136. package/src/caddy/fake-caddy.ts +20 -2
  137. package/src/caddy/index.ts +3 -8
  138. package/src/caddy/local-admin.test.ts +35 -21
  139. package/src/caddy/local-admin.ts +60 -167
  140. package/src/caddy/providers.test.ts +14 -6
  141. package/src/caddy/providers.ts +20 -4
  142. package/src/github/declare-repo-baseline.ts +6 -6
  143. package/src/github/index.ts +10 -4
  144. package/src/github/repo-baseline-data.test.ts +13 -0
  145. package/src/github/repo-baseline-data.ts +11 -2
  146. package/src/github/repo-policy-stack.test.ts +59 -0
  147. package/src/github/repo-policy.ts +3 -2
  148. package/src/github/repository-ruleset-errors.ts +51 -4
  149. package/src/github/repository-ruleset-exact-adopt-k1.test.ts +109 -0
  150. package/src/github/repository-ruleset-exact-adopt.test.ts +184 -0
  151. package/src/github/repository-ruleset-form.ts +7 -3
  152. package/src/github/repository-ruleset-guards.ts +11 -4
  153. package/src/github/repository-ruleset-narrowing-guards.ts +141 -0
  154. package/src/github/repository-ruleset-narrowing.test.ts +210 -0
  155. package/src/github/repository-ruleset-octokit.test.ts +141 -0
  156. package/src/github/repository-ruleset-octokit.ts +121 -30
  157. package/src/github/repository-ruleset-probe.ts +2 -1
  158. package/src/github/repository-ruleset-props.ts +95 -0
  159. package/src/github/repository-ruleset-providers.ts +46 -0
  160. package/src/github/repository-ruleset-reconcile.ts +35 -0
  161. package/src/github/repository-ruleset-wire.test.ts +38 -27
  162. package/src/github/repository-ruleset-write-refusals.test.ts +6 -26
  163. package/src/github/repository-ruleset.ts +53 -43
  164. package/src/litellm/fake-litellm.ts +83 -78
  165. package/src/litellm/fake-stack.ts +13 -5
  166. package/src/litellm/index.ts +7 -21
  167. package/src/litellm/operations.ts +149 -0
  168. package/src/litellm/pass-through-endpoint-delete-swallow.test.ts +17 -20
  169. package/src/litellm/pass-through-endpoint-replace.test.ts +3 -4
  170. package/src/litellm/pass-through-endpoint.test.ts +8 -8
  171. package/src/litellm/pass-through-endpoint.ts +21 -31
  172. package/src/litellm/pass-through-errors.ts +37 -0
  173. package/src/litellm/pass-through-form.ts +20 -10
  174. package/src/litellm/providers.ts +8 -7
  175. package/src/paperless/custom-field.test.ts +49 -40
  176. package/src/paperless/custom-field.ts +57 -33
  177. package/src/paperless/document-type.test.ts +48 -0
  178. package/src/paperless/document-type.ts +51 -22
  179. package/src/paperless/fake-paperless.ts +63 -72
  180. package/src/paperless/index.ts +7 -13
  181. package/src/paperless/matching-locate.test.ts +102 -0
  182. package/src/paperless/matching-locate.ts +80 -86
  183. package/src/paperless/matching-types.ts +49 -16
  184. package/src/paperless/matching.ts +77 -90
  185. package/src/paperless/providers.ts +14 -10
  186. package/src/paperless/storage-path.test.ts +56 -0
  187. package/src/paperless/storage-path.ts +53 -24
  188. package/src/paperless/tag-identity.test.ts +127 -0
  189. package/src/paperless/tag.test.ts +100 -0
  190. package/src/paperless/tag.ts +62 -32
  191. package/src/proxmox/acl.test.ts +145 -0
  192. package/src/proxmox/acl.ts +128 -150
  193. package/src/proxmox/distilled-pve.test.ts +231 -0
  194. package/src/proxmox/distilled-pve.ts +147 -0
  195. package/src/proxmox/members-timeout.test.ts +161 -0
  196. package/src/proxmox/members.test.ts +9 -0
  197. package/src/proxmox/members.ts +36 -2
  198. package/dist/litellm/client.d.ts +0 -76
  199. package/dist/litellm/client.d.ts.map +0 -1
  200. package/dist/litellm/credentials.d.ts +0 -48
  201. package/dist/litellm/credentials.d.ts.map +0 -1
  202. package/dist/litellm/generated/pass-through.d.ts +0 -71
  203. package/dist/litellm/generated/pass-through.d.ts.map +0 -1
  204. package/dist/paperless/client.d.ts +0 -37
  205. package/dist/paperless/client.d.ts.map +0 -1
  206. package/dist/paperless/credentials.d.ts +0 -40
  207. package/dist/paperless/credentials.d.ts.map +0 -1
  208. package/dist/paperless/errors.d.ts +0 -49
  209. package/dist/paperless/errors.d.ts.map +0 -1
  210. package/src/litellm/client.ts +0 -195
  211. package/src/litellm/credentials.ts +0 -73
  212. package/src/litellm/generated/pass-through.ts +0 -73
  213. package/src/paperless/client.ts +0 -88
  214. package/src/paperless/credentials.ts +0 -69
  215. package/src/paperless/errors.ts +0 -62
  216. package/src/paperless/matching-identity.test.ts +0 -194
  217. package/src/paperless/matching.test.ts +0 -128
@@ -43,304 +43,134 @@ var accessForwardAuth = (props) => {
43
43
  import * as Context from "effect/Context";
44
44
  import * as Layer from "effect/Layer";
45
45
 
46
- class CaddyUnreachableError extends Error {
47
- name = "CaddyUnreachableError";
48
- }
49
-
50
46
  class CaddyAdminService extends Context.Service()("homeflare/caddy/CaddyAdmin") {
51
47
  }
52
- var caddyAdminLayer = (admin) => Layer.succeed(CaddyAdminService, admin);
53
- // src/launchd/job-form.ts
54
- import { createHash } from "node:crypto";
55
-
56
- // src/launchd/plist.ts
57
- class PlistError extends Error {
58
- path;
59
- constructor(path, detail) {
60
- super(`plist ${path === "" ? "(root)" : path}: ${detail}`);
61
- this.path = path;
62
- this.name = "PlistError";
63
- }
64
- }
65
- var HEADER = `<?xml version="1.0" encoding="UTF-8"?>
66
- ` + '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" ' + `"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
67
- ` + `<plist version="1.0">
68
- `;
69
- var MAX_DEPTH = 32;
70
- var FORBIDDEN = /[\x00-\x08\x0B\x0C\x0E-\x1F\uD800-\uDFFF\uFFFE\uFFFF]/u;
71
- var escapeText = (text, path) => {
72
- const bad = FORBIDDEN.exec(text);
73
- if (bad !== null) {
74
- const code = bad[0].codePointAt(0) ?? 0;
75
- throw new PlistError(path, `U+${code.toString(16).toUpperCase().padStart(4, "0")} cannot appear in XML 1.0`);
76
- }
77
- return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\r", "&#13;");
78
- };
79
- var isPlainObject = (value) => {
80
- const proto = Object.getPrototypeOf(value);
81
- return proto === Object.prototype || proto === null;
82
- };
83
- var renderValue = (value, path, depth, out) => {
84
- const pad = "\t".repeat(depth);
85
- if (depth > MAX_DEPTH)
86
- throw new PlistError(path, `nested deeper than ${String(MAX_DEPTH)}`);
87
- if (typeof value === "string") {
88
- out.push(`${pad}<string>${escapeText(value, path)}</string>`);
89
- return;
90
- }
91
- if (typeof value === "boolean") {
92
- out.push(`${pad}<${value ? "true" : "false"}/>`);
93
- return;
94
- }
95
- if (typeof value === "number") {
96
- if (!Number.isSafeInteger(value)) {
97
- throw new PlistError(path, `${String(value)} is not a safe integer (no <real> support)`);
98
- }
99
- out.push(`${pad}<integer>${String(value === 0 ? 0 : value)}</integer>`);
100
- return;
101
- }
102
- if (Array.isArray(value)) {
103
- if (value.length === 0) {
104
- out.push(`${pad}<array/>`);
105
- return;
106
- }
107
- out.push(`${pad}<array>`);
108
- for (const [index, item] of value.entries()) {
109
- renderValue(item, `${path}[${String(index)}]`, depth + 1, out);
110
- }
111
- out.push(`${pad}</array>`);
112
- return;
113
- }
114
- if (typeof value === "object" && value !== null && isPlainObject(value)) {
115
- renderDict(value, path, depth, out);
116
- return;
117
- }
118
- const kind = value === null ? "null" : typeof value;
119
- throw new PlistError(path, `${kind} has no plist form — omit the key instead`);
120
- };
121
- var renderDict = (dict, path, depth, out) => {
122
- const pad = "\t".repeat(depth);
123
- const keys = Object.keys(dict).sort();
124
- if (keys.length === 0) {
125
- out.push(`${pad}<dict/>`);
126
- return;
127
- }
128
- out.push(`${pad}<dict>`);
129
- for (const key of keys) {
130
- const child = path === "" ? key : `${path}.${key}`;
131
- out.push(`${pad} <key>${escapeText(key, child)}</key>`);
132
- renderValue(dict[key], child, depth + 1, out);
133
- }
134
- out.push(`${pad}</dict>`);
135
- };
136
- var renderPlist = (dict) => {
137
- if (typeof dict !== "object" || dict === null || Array.isArray(dict) || !isPlainObject(dict)) {
138
- throw new PlistError("", "the top level of a launchd plist must be a dict");
139
- }
140
- const out = [];
141
- renderDict(dict, "", 0, out);
142
- return `${HEADER}${out.join(`
143
- `)}
144
- </plist>
145
- `;
146
- };
147
-
148
- // src/launchd/job-form.ts
149
- var parseDomain = (domain) => {
150
- if (domain === "system")
151
- return { kind: "system" };
152
- const match = /^gui\/(\d+)$/.exec(domain);
153
- return match?.[1] === undefined ? undefined : { kind: "gui", uid: Number(match[1]) };
154
- };
155
- var serviceTarget = (label, domain) => `${domain}/${label}`;
156
- var plistPathFor = (label, domain, home) => {
157
- if (domain.kind === "system")
158
- return `/Library/LaunchDaemons/${label}.plist`;
159
- if (home === undefined || !home.startsWith("/")) {
160
- throw new Error(`gui/${String(domain.uid)}: no home directory for that uid on this host`);
161
- }
162
- return `${home}/Library/LaunchAgents/${label}.plist`;
163
- };
164
- var OWNED_KEYS = [
165
- "EnvironmentVariables",
166
- "GroupName",
167
- "KeepAlive",
168
- "Label",
169
- "Program",
170
- "ProgramArguments",
171
- "RunAtLoad",
172
- "StandardErrorPath",
173
- "StandardOutPath",
174
- "StartCalendarInterval",
175
- "StartInterval",
176
- "ThrottleInterval",
177
- "UserName",
178
- "WorkingDirectory"
179
- ];
180
- var calendarDict = (entry) => {
181
- const dict = {};
182
- if (entry.minute !== undefined)
183
- dict["Minute"] = entry.minute;
184
- if (entry.hour !== undefined)
185
- dict["Hour"] = entry.hour;
186
- if (entry.day !== undefined)
187
- dict["Day"] = entry.day;
188
- if (entry.weekday !== undefined)
189
- dict["Weekday"] = entry.weekday;
190
- if (entry.month !== undefined)
191
- dict["Month"] = entry.month;
192
- return dict;
193
- };
194
- var keepAliveValue = (keepAlive) => {
195
- if (typeof keepAlive === "boolean")
196
- return keepAlive;
197
- const dict = {};
198
- if (keepAlive.successfulExit !== undefined)
199
- dict["SuccessfulExit"] = keepAlive.successfulExit;
200
- if (keepAlive.crashed !== undefined)
201
- dict["Crashed"] = keepAlive.crashed;
202
- return dict;
203
- };
204
- var jobPlistDict = (props) => {
205
- const dict = { ...props.extraKeys };
206
- dict["Label"] = props.label;
207
- dict["ProgramArguments"] = [...props.programArguments];
208
- const env = props.environment ?? {};
209
- if (Object.keys(env).length > 0)
210
- dict["EnvironmentVariables"] = { ...env };
211
- if (props.runAtLoad !== undefined)
212
- dict["RunAtLoad"] = props.runAtLoad;
213
- if (props.keepAlive !== undefined)
214
- dict["KeepAlive"] = keepAliveValue(props.keepAlive);
215
- if (props.startInterval !== undefined)
216
- dict["StartInterval"] = props.startInterval;
217
- const calendar = props.startCalendarInterval;
218
- if (calendar !== undefined) {
219
- dict["StartCalendarInterval"] = Array.isArray(calendar) ? calendar.map(calendarDict) : calendarDict(calendar);
220
- }
221
- if (props.throttleInterval !== undefined)
222
- dict["ThrottleInterval"] = props.throttleInterval;
223
- if (props.standardOutPath !== undefined)
224
- dict["StandardOutPath"] = props.standardOutPath;
225
- if (props.standardErrorPath !== undefined)
226
- dict["StandardErrorPath"] = props.standardErrorPath;
227
- if (props.workingDirectory !== undefined)
228
- dict["WorkingDirectory"] = props.workingDirectory;
229
- if (props.userName !== undefined)
230
- dict["UserName"] = props.userName;
231
- if (props.groupName !== undefined)
232
- dict["GroupName"] = props.groupName;
233
- return dict;
234
- };
235
- var sha256Hex = (bytes) => createHash("sha256").update(bytes).digest("hex");
236
- var renderJob = (props) => {
237
- const text = renderPlist(jobPlistDict(props));
238
- const bytes = new TextEncoder().encode(text);
239
- return { bytes, sha256: sha256Hex(bytes), text };
240
- };
241
-
242
- // src/caddy/digest.ts
243
- var sorted = (value) => {
244
- if (Array.isArray(value))
245
- return value.map(sorted);
246
- if (typeof value === "object" && value !== null) {
247
- const object = value;
248
- const out = {};
249
- for (const key of Object.keys(object).sort())
250
- out[key] = sorted(object[key] ?? null);
251
- return out;
252
- }
253
- return value;
254
- };
255
- var parseConfig = (text) => JSON.parse(text);
256
- var canonicalConfig = (config) => JSON.stringify(sorted(config ?? null));
257
- var configDigest = (config) => sha256Hex(canonicalConfig(config));
258
-
259
- // src/caddy/admin-calls.ts
260
- class CaddyAdminError extends Error {
261
- status;
262
- method;
263
- path;
264
- constructor(status, method, path, reason) {
265
- super(`Caddy ${method} ${path} → ${String(status)}: ${reason}`);
266
- this.status = status;
267
- this.method = method;
268
- this.path = path;
269
- this.name = "CaddyAdminError";
270
- }
271
- }
272
- var CADDYFILE = "text/caddyfile";
273
- var ERROR_OPEN = '{"error":';
274
- var describe = (warning) => `${warning.file ?? "Caddyfile"}:${String(warning.line ?? 0)}: ${warning.message ?? "(no message)"}`;
275
- var errorIn = (body) => {
276
- const at = body.lastIndexOf(ERROR_OPEN);
277
- if (at === -1)
278
- return;
279
- try {
280
- const parsed = JSON.parse(body.slice(at));
281
- return typeof parsed.error === "string" ? parsed.error : undefined;
282
- } catch {
283
- return;
284
- }
285
- };
286
- var warningsIn = (text) => {
287
- if (text.trim() === "")
288
- return [];
289
- try {
290
- const parsed = JSON.parse(text);
291
- return Array.isArray(parsed) ? parsed.map(describe) : [];
292
- } catch {
293
- return [];
48
+ var caddyAdminLayer = (transport) => Layer.mergeAll(Layer.succeed(CaddyAdminService, {
49
+ endpoint: transport.endpoint,
50
+ listener: transport.listener
51
+ }), transport.layer);
52
+ // src/caddy/caddy-http-client.ts
53
+ import * as http from "node:http";
54
+ import { Readable } from "node:stream";
55
+ import * as Duration from "effect/Duration";
56
+ import * as Effect from "effect/Effect";
57
+ import * as Layer2 from "effect/Layer";
58
+ import * as Result from "effect/Result";
59
+ import * as HttpClient from "effect/unstable/http/HttpClient";
60
+ import * as HttpClientError from "effect/unstable/http/HttpClientError";
61
+ import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse";
62
+ var NEVER_CONNECTED = new Set(["ECONNREFUSED", "ENOENT"]);
63
+ var codeOf = (cause) => typeof cause === "object" && cause !== null && ("code" in cause) ? String(cause.code) : undefined;
64
+ var isUnreachable = (error) => error instanceof HttpClientError.HttpClientError && error.reason._tag === "TransportError" && NEVER_CONNECTED.has(codeOf(error.reason.cause) ?? "");
65
+ var transportFailure = (request2, cause) => new HttpClientError.HttpClientError({
66
+ reason: new HttpClientError.TransportError({ request: request2, cause })
67
+ });
68
+ var flattenHeaders = (headers) => {
69
+ const flat = {};
70
+ for (const [key, value] of Object.entries(headers)) {
71
+ if (value !== undefined)
72
+ flat[key] = Array.isArray(value) ? value.join(", ") : value;
294
73
  }
74
+ return flat;
295
75
  };
296
- var fail = (response, method, path) => {
297
- const reason = errorIn(response.body) ?? (response.body.trim().slice(0, 500) || "(empty body)");
298
- const shown = response.status === 200 ? `${reason} (error in a 200 body)` : reason;
299
- throw new CaddyAdminError(response.status, method, path, shown);
300
- };
301
- var adaptCaddyfile = async (admin, caddyfile) => {
302
- const response = await admin.request({
303
- body: caddyfile,
304
- headers: { "Content-Type": CADDYFILE },
305
- method: "POST",
306
- path: "/adapt"
76
+ var waitForResponse = (nodeRequest, request2) => Effect.callback((resume) => {
77
+ const onError2 = (cause) => {
78
+ resume(Effect.fail(transportFailure(request2, cause)));
79
+ };
80
+ nodeRequest.on("error", onError2);
81
+ const onResponse = (response) => {
82
+ nodeRequest.off("error", onError2);
83
+ resume(Effect.succeed(response));
84
+ };
85
+ nodeRequest.on("response", onResponse);
86
+ return Effect.sync(() => {
87
+ nodeRequest.off("error", onError2);
88
+ nodeRequest.off("response", onResponse);
307
89
  });
308
- if (response.status !== 200)
309
- fail(response, "POST", "/adapt");
310
- const parsed = JSON.parse(response.body);
311
- return { config: parsed.result ?? null, warnings: (parsed.warnings ?? []).map(describe) };
312
- };
313
- var readRunningConfig = async (admin) => {
314
- const response = await admin.request({ method: "GET", path: "/config/" });
315
- if (response.status !== 200)
316
- fail(response, "GET", "/config/");
317
- return parseConfig(response.body);
318
- };
319
- var loadCaddyfile = async (admin, caddyfile, sourceFile) => {
320
- const response = await admin.request({
321
- body: caddyfile,
322
- headers: {
323
- "Content-Type": CADDYFILE,
324
- ...sourceFile === undefined ? {} : { "Caddy-Config-Source-Adapter": "caddyfile", "Caddy-Config-Source-File": sourceFile }
325
- },
326
- method: "POST",
327
- path: "/load"
90
+ });
91
+ var waitForFinish = (nodeRequest, request2) => Effect.callback((resume) => {
92
+ const onError2 = (cause) => {
93
+ resume(Effect.fail(transportFailure(request2, cause)));
94
+ };
95
+ nodeRequest.once("error", onError2);
96
+ const onFinish = () => {
97
+ nodeRequest.off("error", onError2);
98
+ resume(Effect.void);
99
+ };
100
+ nodeRequest.once("finish", onFinish);
101
+ return Effect.sync(() => {
102
+ nodeRequest.off("error", onError2);
103
+ nodeRequest.off("finish", onFinish);
328
104
  });
329
- const refused = errorIn(response.body);
330
- if (response.status !== 200 || refused !== undefined)
331
- fail(response, "POST", "/load");
332
- return warningsIn(response.body);
105
+ });
106
+ var sendBody = (nodeRequest, request2) => Effect.suspend(() => {
107
+ const body = request2.body;
108
+ if (body._tag === "Empty") {
109
+ nodeRequest.end();
110
+ return waitForFinish(nodeRequest, request2);
111
+ }
112
+ if (body._tag === "Uint8Array") {
113
+ nodeRequest.setHeader("content-length", body.contentLength);
114
+ nodeRequest.end(body.body);
115
+ return waitForFinish(nodeRequest, request2);
116
+ }
117
+ return Effect.fail(new HttpClientError.HttpClientError({
118
+ reason: new HttpClientError.EncodeError({
119
+ request: request2,
120
+ description: `Caddy admin transport: unsupported request body "${body._tag}" — every Caddy operation sends Empty or a Uint8Array body`
121
+ })
122
+ }));
123
+ });
124
+ var toWebResponse = (source) => new Response(Readable.toWeb(source), {
125
+ headers: flattenHeaders(source.headers),
126
+ status: source.statusCode ?? 0
127
+ });
128
+ var exchangeOnce = (target, request2, url, signal, timeoutMs) => Effect.suspend(() => {
129
+ const nodeRequest = http.request({
130
+ headers: request2.headers,
131
+ method: request2.method,
132
+ path: `${url.pathname}${url.search}`,
133
+ signal,
134
+ timeout: timeoutMs,
135
+ ...target.kind === "unix" ? { socketPath: target.socketPath } : { host: target.host, port: target.port }
136
+ });
137
+ nodeRequest.on("timeout", () => {
138
+ nodeRequest.destroy(new Error(`no answer in ${String(timeoutMs)} ms`));
139
+ });
140
+ return Effect.raceFirst(waitForResponse(nodeRequest, request2), sendBody(nodeRequest, request2).pipe(Effect.andThen(Effect.never))).pipe(Effect.onError(() => Effect.sync(() => nodeRequest.destroy())), Effect.map((source) => HttpClientResponse.fromWeb(request2, toWebResponse(source))));
141
+ });
142
+ var withUnreachableRetry = (attempt, retries, retryDelayMs) => Effect.gen(function* () {
143
+ let attemptsLeft = retries;
144
+ for (;; ) {
145
+ const outcome = yield* Effect.result(attempt);
146
+ if (Result.isSuccess(outcome))
147
+ return outcome.success;
148
+ if (attemptsLeft <= 0 || !isUnreachable(outcome.failure)) {
149
+ return yield* Effect.fail(outcome.failure);
150
+ }
151
+ attemptsLeft -= 1;
152
+ yield* Effect.sleep(Duration.millis(retryDelayMs));
153
+ }
154
+ });
155
+ var makeLocalHttpClient = (target, options = {}) => {
156
+ const timeoutMs = options.timeoutMs ?? 60000;
157
+ const retries = options.retries ?? 2;
158
+ const retryDelayMs = options.retryDelayMs ?? 500;
159
+ return Layer2.succeed(HttpClient.HttpClient, HttpClient.make((request2, url, signal) => {
160
+ const attempt = exchangeOnce(target, request2, url, signal, timeoutMs);
161
+ return retries <= 0 ? attempt : withUnreachableRetry(attempt, retries, retryDelayMs);
162
+ }));
333
163
  };
334
164
  // src/caddy/config.ts
335
165
  import { Resource } from "alchemy";
336
166
  import { Unowned as Unowned2 } from "alchemy/AdoptPolicy";
337
167
  import { isResolved } from "alchemy/Diff";
338
168
  import * as Provider from "alchemy/Provider";
339
- import * as Effect6 from "effect/Effect";
169
+ import * as Effect11 from "effect/Effect";
340
170
 
341
171
  // src/launchd/host-effect.ts
342
- import * as Effect from "effect/Effect";
343
- var lift = (step) => Effect.tryPromise({
172
+ import * as Effect2 from "effect/Effect";
173
+ var lift = (step) => Effect2.tryPromise({
344
174
  catch: (cause) => cause instanceof Error ? cause : new Error(String(cause)),
345
175
  try: step
346
176
  });
@@ -355,32 +185,32 @@ var resolvedString = (news, key) => {
355
185
  import { AdoptPolicy } from "alchemy/AdoptPolicy";
356
186
  import { AlchemyContext } from "alchemy/AlchemyContext";
357
187
  import { Stack as Stack3 } from "alchemy/Stack";
358
- import * as Effect5 from "effect/Effect";
188
+ import * as Effect6 from "effect/Effect";
359
189
  import * as Option4 from "effect/Option";
360
190
 
361
191
  // src/ownership/resume.ts
362
192
  import { Unowned } from "alchemy/AdoptPolicy";
363
193
  import { Artifacts } from "alchemy/Artifacts";
364
- import * as Effect2 from "effect/Effect";
194
+ import * as Effect3 from "effect/Effect";
365
195
  import * as Option from "effect/Option";
366
196
  var PROVEN = "homeflare/ownership/resumes";
367
197
  var UNFINISHED = "homeflare/ownership/unfinished";
368
- var note = (key, instanceId) => Effect2.gen(function* () {
369
- const bag = yield* Effect2.serviceOption(Artifacts);
198
+ var note = (key, instanceId) => Effect3.gen(function* () {
199
+ const bag = yield* Effect3.serviceOption(Artifacts);
370
200
  if (Option.isSome(bag))
371
201
  yield* bag.value.set(key, instanceId);
372
202
  });
373
- var noted = (key, instanceId) => Effect2.gen(function* () {
374
- const bag = yield* Effect2.serviceOption(Artifacts);
203
+ var noted = (key, instanceId) => Effect3.gen(function* () {
204
+ const bag = yield* Effect3.serviceOption(Artifacts);
375
205
  if (Option.isNone(bag))
376
206
  return false;
377
207
  return (yield* bag.value.get(key)) === instanceId;
378
208
  });
379
- var noteUnfinished = (instanceId) => Effect2.as(note(UNFINISHED, instanceId), undefined);
380
- var noteResume = (instanceId, recovered) => Effect2.gen(function* () {
209
+ var noteUnfinished = (instanceId) => Effect3.as(note(UNFINISHED, instanceId), undefined);
210
+ var noteResume = (instanceId, recovered) => Effect3.gen(function* () {
381
211
  yield* noteUnfinished(instanceId);
382
- const unproven = () => Effect2.succeed(undefined);
383
- const found = yield* recovered.pipe(Effect2.catch(unproven), Effect2.catchDefect(unproven));
212
+ const unproven = () => Effect3.succeed(undefined);
213
+ const found = yield* recovered.pipe(Effect3.catch(unproven), Effect3.catchDefect(unproven));
384
214
  if (found === undefined || found === null || Unowned.is(found))
385
215
  return;
386
216
  yield* note(PROVEN, instanceId);
@@ -391,7 +221,7 @@ var provingResumes = (service) => {
391
221
  const { diff, read } = service;
392
222
  if (diff === undefined || read === undefined)
393
223
  return service;
394
- const proving = (input) => Effect2.gen(function* () {
224
+ const proving = (input) => Effect3.gen(function* () {
395
225
  if (input.output === undefined) {
396
226
  yield* noteResume(input.instanceId, read({ ...input, output: undefined }));
397
227
  }
@@ -403,15 +233,15 @@ var provingResumes = (service) => {
403
233
  // src/ownership/rows.ts
404
234
  import { Stack as Stack2 } from "alchemy/Stack";
405
235
  import { State, isActionState } from "alchemy/State/State";
406
- import * as Effect4 from "effect/Effect";
236
+ import * as Effect5 from "effect/Effect";
407
237
  import * as Option3 from "effect/Option";
408
238
 
409
239
  // src/ownership/whole.ts
410
240
  import { Stack } from "alchemy/Stack";
411
- import * as Effect3 from "effect/Effect";
241
+ import * as Effect4 from "effect/Effect";
412
242
  import * as Option2 from "effect/Option";
413
243
  var absent = (value) => value === undefined || value === null;
414
- var isPlainObject2 = (value) => {
244
+ var isPlainObject = (value) => {
415
245
  if (typeof value !== "object" || value === null || Array.isArray(value))
416
246
  return false;
417
247
  const proto = Object.getPrototypeOf(value);
@@ -427,29 +257,29 @@ var carries = (declared, recorded) => {
427
257
  return false;
428
258
  return declared.every((each, index) => carries(each, recorded[index]));
429
259
  }
430
- if (isPlainObject2(declared)) {
431
- if (!isPlainObject2(recorded))
260
+ if (isPlainObject(declared)) {
261
+ if (!isPlainObject(recorded))
432
262
  return false;
433
263
  return Object.entries(declared).every(([key, each]) => carries(each, recorded[key]));
434
264
  }
435
265
  return true;
436
266
  };
437
- var wholeDeclaration = (fqn, props) => Effect3.map(Effect3.serviceOption(Stack), (stack) => {
267
+ var wholeDeclaration = (fqn, props) => Effect4.map(Effect4.serviceOption(Stack), (stack) => {
438
268
  const declared = Option2.isSome(stack) ? stack.value.resources[fqn] : undefined;
439
269
  return declared !== undefined && carries(declared.Props, props);
440
270
  });
441
271
 
442
272
  // src/ownership/rows.ts
443
- var storeOf = Effect4.gen(function* () {
444
- const stack = yield* Effect4.serviceOption(Stack2);
445
- const state = yield* Effect4.serviceOption(State);
273
+ var storeOf = Effect5.gen(function* () {
274
+ const stack = yield* Effect5.serviceOption(Stack2);
275
+ const state = yield* Effect5.serviceOption(State);
446
276
  if (Option3.isNone(stack) || Option3.isNone(state))
447
277
  return;
448
278
  const store = yield* state.value;
449
279
  const where = { stack: stack.value.name, stage: stack.value.stage };
450
280
  return { resources: stack.value.resources, store, where };
451
281
  });
452
- var rowAt = (store, where, fqn) => store.get({ ...where, fqn }).pipe(Effect4.orElseSucceed(() => {
282
+ var rowAt = (store, where, fqn) => store.get({ ...where, fqn }).pipe(Effect5.orElseSucceed(() => {
453
283
  return;
454
284
  }));
455
285
  var generationOf = (row, instanceId) => {
@@ -462,7 +292,7 @@ var generationOf = (row, instanceId) => {
462
292
  }
463
293
  return;
464
294
  };
465
- var recordedGeneration = (fqn, instanceId) => Effect4.gen(function* () {
295
+ var recordedGeneration = (fqn, instanceId) => Effect5.gen(function* () {
466
296
  const found = yield* storeOf;
467
297
  if (found === undefined)
468
298
  return;
@@ -473,14 +303,14 @@ var recordedGeneration = (fqn, instanceId) => Effect4.gen(function* () {
473
303
  }
474
304
  return;
475
305
  });
476
- var recordedInstance = (fqn, instanceId) => Effect4.gen(function* () {
306
+ var recordedInstance = (fqn, instanceId) => Effect5.gen(function* () {
477
307
  const generation = yield* recordedGeneration(fqn, instanceId);
478
308
  if (generation === undefined)
479
309
  return "absent";
480
310
  return (yield* wholeDeclaration(fqn, generation.props)) ? "whole" : "partial";
481
311
  });
482
- var isCreate = (fqn, instanceId) => Effect4.map(recordedGeneration(fqn, instanceId), (generation) => generation?.status === "creating");
483
- var forgetRefusedCreate = (fqn, instanceId) => Effect4.gen(function* () {
312
+ var isCreate = (fqn, instanceId) => Effect5.map(recordedGeneration(fqn, instanceId), (generation) => generation?.status === "creating");
313
+ var forgetRefusedCreate = (fqn, instanceId) => Effect5.gen(function* () {
484
314
  const found = yield* storeOf;
485
315
  if (found === undefined)
486
316
  return;
@@ -489,32 +319,32 @@ var forgetRefusedCreate = (fqn, instanceId) => Effect4.gen(function* () {
489
319
  if (mine?.status !== "creating" || mine.instanceId !== instanceId || mine.attr !== undefined) {
490
320
  return;
491
321
  }
492
- yield* found.store.delete({ ...found.where, fqn }).pipe(Effect4.ignore);
322
+ yield* found.store.delete({ ...found.where, fqn }).pipe(Effect5.ignore);
493
323
  });
494
324
 
495
325
  // src/ownership/adopt.ts
496
- var adoptEnabled = (fqn) => Effect5.gen(function* () {
497
- const stack = yield* Effect5.serviceOption(Stack3);
326
+ var adoptEnabled = (fqn) => Effect6.gen(function* () {
327
+ const stack = yield* Effect6.serviceOption(Stack3);
498
328
  const scoped = Option4.isSome(stack) ? stack.value.resources[fqn]?.Adopt : undefined;
499
329
  if (scoped !== undefined)
500
330
  return scoped;
501
- const policy = yield* Effect5.serviceOption(AdoptPolicy);
331
+ const policy = yield* Effect6.serviceOption(AdoptPolicy);
502
332
  if (Option4.isSome(policy))
503
333
  return policy.value;
504
- const context = yield* Effect5.serviceOption(AlchemyContext);
334
+ const context = yield* Effect6.serviceOption(AlchemyContext);
505
335
  return Option4.isSome(context) ? context.value.adopt : false;
506
336
  });
507
- var adoptable = (owner) => Effect5.gen(function* () {
337
+ var adoptable = (owner) => Effect6.gen(function* () {
508
338
  if (yield* isCreate(owner.fqn, owner.instanceId))
509
339
  return true;
510
340
  return yield* unfinished(owner.instanceId);
511
341
  });
512
- var adoptsAtApply = (owner) => Effect5.gen(function* () {
342
+ var adoptsAtApply = (owner) => Effect6.gen(function* () {
513
343
  if (!(yield* adoptEnabled(owner.fqn)))
514
344
  return false;
515
345
  return yield* adoptable(owner);
516
346
  });
517
- var refuseTakeover = (owner, what) => Effect5.gen(function* () {
347
+ var refuseTakeover = (owner, what) => Effect6.gen(function* () {
518
348
  if (owner.output !== undefined)
519
349
  return;
520
350
  if (yield* resumes(owner.instanceId))
@@ -524,10 +354,30 @@ var refuseTakeover = (owner, what) => Effect5.gen(function* () {
524
354
  return;
525
355
  yield* forgetRefusedCreate(owner.fqn, owner.instanceId);
526
356
  const remedy = may ? "Deploy with --adopt (or wrap the resource in adopt(true)) to take it over, or remove it first." : "It is the new identity of a replace, which --adopt does not cover: remove it first.";
527
- return yield* Effect5.die(new Error(`${what}: already exists, and this stack holds no state for it — the plan could not ask ` + "first (a prop was still an Output, a replace moved onto it, or it appeared after the " + `plan). Nothing was written. ${remedy}`));
357
+ return yield* Effect6.die(new Error(`${what}: already exists, and this stack holds no state for it — the plan could not ask ` + "first (a prop was still an Output, a replace moved onto it, or it appeared after the " + `plan). Nothing was written. ${remedy}`));
528
358
  });
529
359
  var claimFor = (owner) => (what) => refuseTakeover(owner, what);
530
360
 
361
+ // src/caddy/config-lifecycle.ts
362
+ import * as Effect9 from "effect/Effect";
363
+ import * as Result2 from "effect/Result";
364
+
365
+ // src/caddy/admin-calls.ts
366
+ import * as Effect7 from "effect/Effect";
367
+ import * as Caddy from "@distilled.cloud/caddy";
368
+ var CADDYFILE = "text/caddyfile";
369
+ var describe = (warning) => `${warning.file ?? "Caddyfile"}:${String(warning.line ?? 0)}: ${warning.message ?? "(no message)"}`;
370
+ var adaptCaddyfile = (caddyfile) => Effect7.map(Caddy.Services.admin.adaptConfig({ config: caddyfile, contentType: CADDYFILE }), (response) => ({
371
+ config: response.result ?? null,
372
+ warnings: (response.warnings ?? []).map(describe)
373
+ }));
374
+ var readRunningConfig = () => Effect7.map(Caddy.Services.admin.getConfig({ path: "" }), (response) => response.value ?? null);
375
+ var loadCaddyfile = (caddyfile, sourceFile) => Effect7.map(Caddy.Services.admin.loadConfig({
376
+ config: caddyfile,
377
+ contentType: CADDYFILE,
378
+ ...sourceFile === undefined ? {} : { sourceAdapter: "caddyfile", sourceFile }
379
+ }), (response) => (response.warnings ?? []).map(describe));
380
+
531
381
  // src/caddy/admin-guard.ts
532
382
  var DEFAULT_ADMIN_PORT = 2019;
533
383
  var LOOPBACK_HOST = /^(?:localhost|127(?:\.\d{1,3}){3}|\[?::1\]?)$/;
@@ -606,6 +456,14 @@ var adminProblems = (adapted, transport) => {
606
456
  return found;
607
457
  };
608
458
 
459
+ // src/caddy/config-errors.ts
460
+ import * as Effect8 from "effect/Effect";
461
+ var refuse = (endpoint, message) => new Error(`Caddy.Config at ${endpoint}: ${message}`);
462
+ var short = (digest) => digest.slice(0, 12);
463
+ var messageOf = (error) => ("message" in error) && typeof error.message === "string" ? error.message : String(error);
464
+ var operationFailure = (endpoint, op, error) => error._tag === "LoadRefused" ? refuse(endpoint, `Caddy refused the Caddyfile (${error._tag}, a 200 whose body carried the error, not a ` + `failure status) — ${error.message}`) : refuse(endpoint, `${op} failed (${error._tag}) — ${messageOf(error)}`);
465
+ var wrapOperation = (endpoint, op) => (effect) => effect.pipe(Effect8.mapError((error) => isUnreachable(error) ? error : operationFailure(endpoint, op, error)));
466
+
609
467
  // src/launchd/secret-tripwire.ts
610
468
  var ALTERNATIVE = "props are stored unencrypted in Alchemy state. Have a secret renderer (e.g. openbao-agent) " + "write it to a root-owned file and declare the file path instead (FOO_FILE=/path, --foo-file /path).";
611
469
  var SECRET_NAME = /(?:^|_)(?:TOKEN|SECRET|PASSWORD|PASSWD|PASSPHRASE|API_?KEY|ACCESS_KEY|PRIVATE_KEY|SECRET_KEY|CREDENTIALS?)$/i;
@@ -743,139 +601,356 @@ var configProblems = (props) => {
743
601
  return found;
744
602
  };
745
603
 
604
+ // src/launchd/job-form.ts
605
+ import { createHash } from "node:crypto";
606
+
607
+ // src/launchd/plist.ts
608
+ class PlistError extends Error {
609
+ path;
610
+ constructor(path, detail) {
611
+ super(`plist ${path === "" ? "(root)" : path}: ${detail}`);
612
+ this.path = path;
613
+ this.name = "PlistError";
614
+ }
615
+ }
616
+ var HEADER = `<?xml version="1.0" encoding="UTF-8"?>
617
+ ` + '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" ' + `"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
618
+ ` + `<plist version="1.0">
619
+ `;
620
+ var MAX_DEPTH = 32;
621
+ var FORBIDDEN = /[\x00-\x08\x0B\x0C\x0E-\x1F\uD800-\uDFFF\uFFFE\uFFFF]/u;
622
+ var escapeText = (text, path) => {
623
+ const bad = FORBIDDEN.exec(text);
624
+ if (bad !== null) {
625
+ const code = bad[0].codePointAt(0) ?? 0;
626
+ throw new PlistError(path, `U+${code.toString(16).toUpperCase().padStart(4, "0")} cannot appear in XML 1.0`);
627
+ }
628
+ return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\r", "&#13;");
629
+ };
630
+ var isPlainObject2 = (value) => {
631
+ const proto = Object.getPrototypeOf(value);
632
+ return proto === Object.prototype || proto === null;
633
+ };
634
+ var renderValue = (value, path, depth, out) => {
635
+ const pad = "\t".repeat(depth);
636
+ if (depth > MAX_DEPTH)
637
+ throw new PlistError(path, `nested deeper than ${String(MAX_DEPTH)}`);
638
+ if (typeof value === "string") {
639
+ out.push(`${pad}<string>${escapeText(value, path)}</string>`);
640
+ return;
641
+ }
642
+ if (typeof value === "boolean") {
643
+ out.push(`${pad}<${value ? "true" : "false"}/>`);
644
+ return;
645
+ }
646
+ if (typeof value === "number") {
647
+ if (!Number.isSafeInteger(value)) {
648
+ throw new PlistError(path, `${String(value)} is not a safe integer (no <real> support)`);
649
+ }
650
+ out.push(`${pad}<integer>${String(value === 0 ? 0 : value)}</integer>`);
651
+ return;
652
+ }
653
+ if (Array.isArray(value)) {
654
+ if (value.length === 0) {
655
+ out.push(`${pad}<array/>`);
656
+ return;
657
+ }
658
+ out.push(`${pad}<array>`);
659
+ for (const [index, item] of value.entries()) {
660
+ renderValue(item, `${path}[${String(index)}]`, depth + 1, out);
661
+ }
662
+ out.push(`${pad}</array>`);
663
+ return;
664
+ }
665
+ if (typeof value === "object" && value !== null && isPlainObject2(value)) {
666
+ renderDict(value, path, depth, out);
667
+ return;
668
+ }
669
+ const kind = value === null ? "null" : typeof value;
670
+ throw new PlistError(path, `${kind} has no plist form — omit the key instead`);
671
+ };
672
+ var renderDict = (dict, path, depth, out) => {
673
+ const pad = "\t".repeat(depth);
674
+ const keys = Object.keys(dict).sort();
675
+ if (keys.length === 0) {
676
+ out.push(`${pad}<dict/>`);
677
+ return;
678
+ }
679
+ out.push(`${pad}<dict>`);
680
+ for (const key of keys) {
681
+ const child = path === "" ? key : `${path}.${key}`;
682
+ out.push(`${pad} <key>${escapeText(key, child)}</key>`);
683
+ renderValue(dict[key], child, depth + 1, out);
684
+ }
685
+ out.push(`${pad}</dict>`);
686
+ };
687
+ var renderPlist = (dict) => {
688
+ if (typeof dict !== "object" || dict === null || Array.isArray(dict) || !isPlainObject2(dict)) {
689
+ throw new PlistError("", "the top level of a launchd plist must be a dict");
690
+ }
691
+ const out = [];
692
+ renderDict(dict, "", 0, out);
693
+ return `${HEADER}${out.join(`
694
+ `)}
695
+ </plist>
696
+ `;
697
+ };
698
+
699
+ // src/launchd/job-form.ts
700
+ var parseDomain = (domain) => {
701
+ if (domain === "system")
702
+ return { kind: "system" };
703
+ const match = /^gui\/(\d+)$/.exec(domain);
704
+ return match?.[1] === undefined ? undefined : { kind: "gui", uid: Number(match[1]) };
705
+ };
706
+ var serviceTarget = (label, domain) => `${domain}/${label}`;
707
+ var plistPathFor = (label, domain, home) => {
708
+ if (domain.kind === "system")
709
+ return `/Library/LaunchDaemons/${label}.plist`;
710
+ if (home === undefined || !home.startsWith("/")) {
711
+ throw new Error(`gui/${String(domain.uid)}: no home directory for that uid on this host`);
712
+ }
713
+ return `${home}/Library/LaunchAgents/${label}.plist`;
714
+ };
715
+ var OWNED_KEYS = [
716
+ "EnvironmentVariables",
717
+ "GroupName",
718
+ "KeepAlive",
719
+ "Label",
720
+ "Program",
721
+ "ProgramArguments",
722
+ "RunAtLoad",
723
+ "StandardErrorPath",
724
+ "StandardOutPath",
725
+ "StartCalendarInterval",
726
+ "StartInterval",
727
+ "ThrottleInterval",
728
+ "UserName",
729
+ "WorkingDirectory"
730
+ ];
731
+ var calendarDict = (entry) => {
732
+ const dict = {};
733
+ if (entry.minute !== undefined)
734
+ dict["Minute"] = entry.minute;
735
+ if (entry.hour !== undefined)
736
+ dict["Hour"] = entry.hour;
737
+ if (entry.day !== undefined)
738
+ dict["Day"] = entry.day;
739
+ if (entry.weekday !== undefined)
740
+ dict["Weekday"] = entry.weekday;
741
+ if (entry.month !== undefined)
742
+ dict["Month"] = entry.month;
743
+ return dict;
744
+ };
745
+ var keepAliveValue = (keepAlive) => {
746
+ if (typeof keepAlive === "boolean")
747
+ return keepAlive;
748
+ const dict = {};
749
+ if (keepAlive.successfulExit !== undefined)
750
+ dict["SuccessfulExit"] = keepAlive.successfulExit;
751
+ if (keepAlive.crashed !== undefined)
752
+ dict["Crashed"] = keepAlive.crashed;
753
+ return dict;
754
+ };
755
+ var jobPlistDict = (props) => {
756
+ const dict = { ...props.extraKeys };
757
+ dict["Label"] = props.label;
758
+ dict["ProgramArguments"] = [...props.programArguments];
759
+ const env = props.environment ?? {};
760
+ if (Object.keys(env).length > 0)
761
+ dict["EnvironmentVariables"] = { ...env };
762
+ if (props.runAtLoad !== undefined)
763
+ dict["RunAtLoad"] = props.runAtLoad;
764
+ if (props.keepAlive !== undefined)
765
+ dict["KeepAlive"] = keepAliveValue(props.keepAlive);
766
+ if (props.startInterval !== undefined)
767
+ dict["StartInterval"] = props.startInterval;
768
+ const calendar = props.startCalendarInterval;
769
+ if (calendar !== undefined) {
770
+ dict["StartCalendarInterval"] = Array.isArray(calendar) ? calendar.map(calendarDict) : calendarDict(calendar);
771
+ }
772
+ if (props.throttleInterval !== undefined)
773
+ dict["ThrottleInterval"] = props.throttleInterval;
774
+ if (props.standardOutPath !== undefined)
775
+ dict["StandardOutPath"] = props.standardOutPath;
776
+ if (props.standardErrorPath !== undefined)
777
+ dict["StandardErrorPath"] = props.standardErrorPath;
778
+ if (props.workingDirectory !== undefined)
779
+ dict["WorkingDirectory"] = props.workingDirectory;
780
+ if (props.userName !== undefined)
781
+ dict["UserName"] = props.userName;
782
+ if (props.groupName !== undefined)
783
+ dict["GroupName"] = props.groupName;
784
+ return dict;
785
+ };
786
+ var sha256Hex = (bytes) => createHash("sha256").update(bytes).digest("hex");
787
+ var renderJob = (props) => {
788
+ const text = renderPlist(jobPlistDict(props));
789
+ const bytes = new TextEncoder().encode(text);
790
+ return { bytes, sha256: sha256Hex(bytes), text };
791
+ };
792
+
793
+ // src/caddy/digest.ts
794
+ var sorted = (value) => {
795
+ if (Array.isArray(value))
796
+ return value.map(sorted);
797
+ if (typeof value === "object" && value !== null) {
798
+ const object = value;
799
+ const out = {};
800
+ for (const key of Object.keys(object).sort())
801
+ out[key] = sorted(object[key] ?? null);
802
+ return out;
803
+ }
804
+ return value;
805
+ };
806
+ var canonicalConfig = (config) => JSON.stringify(sorted(config ?? null));
807
+ var configDigest = (config) => sha256Hex(canonicalConfig(config));
808
+
746
809
  // src/caddy/config-lifecycle.ts
747
- var refuse = (admin, message) => new Error(`Caddy.Config at ${admin.endpoint}: ${message}`);
748
- var short = (digest) => digest.slice(0, 12);
749
810
  var servesNothing = (config) => {
750
811
  const apps = typeof config === "object" && config !== null ? config.apps : undefined;
751
812
  return typeof apps !== "object" || apps === null || Object.keys(apps).length === 0;
752
813
  };
753
- var desiredConfig = async (admin, props) => {
814
+ var desiredConfig = (props) => Effect9.gen(function* () {
815
+ const { endpoint, listener } = yield* CaddyAdminService;
754
816
  const found = configProblems(props);
755
817
  if (found.length > 0)
756
- throw refuse(admin, found.join("; "));
757
- const adapted = await adaptCaddyfile(admin, props.caddyfile);
818
+ return yield* Effect9.fail(refuse(endpoint, found.join("; ")));
819
+ const adapted = yield* adaptCaddyfile(props.caddyfile).pipe(wrapOperation(endpoint, "POST /adapt"));
758
820
  if (servesNothing(adapted.config)) {
759
- throw refuse(admin, "the Caddyfile adapts to no apps — loading it would stop every site");
821
+ return yield* Effect9.fail(refuse(endpoint, "the Caddyfile adapts to no apps — loading it would stop every site"));
760
822
  }
761
- const guard = adminProblems(adapted.config, admin.listener);
823
+ const guard = adminProblems(adapted.config, listener);
762
824
  if (guard.length > 0)
763
- throw refuse(admin, guard.join("; "));
825
+ return yield* Effect9.fail(refuse(endpoint, guard.join("; ")));
764
826
  return { digest: configDigest(adapted.config), warnings: adapted.warnings };
765
- };
766
- var attributesOf = (admin, running, sourceFile) => ({
827
+ });
828
+ var attributesOf = (endpoint, running, sourceFile) => ({
767
829
  configSha256: configDigest(running),
768
- endpoint: admin.endpoint,
830
+ endpoint,
769
831
  ...sourceFile === undefined ? {} : { sourceFile }
770
832
  });
771
- var readLive = async (admin, sourceFile) => attributesOf(admin, await readRunningConfig(admin), sourceFile);
833
+ var readLive = (sourceFile) => Effect9.gen(function* () {
834
+ const { endpoint } = yield* CaddyAdminService;
835
+ const running = yield* readRunningConfig().pipe(wrapOperation(endpoint, "GET /config/"));
836
+ return attributesOf(endpoint, running, sourceFile);
837
+ });
772
838
  var claimable = (running, declaredDigest) => servesNothing(running) || configDigest(running) === declaredDigest;
773
- var probeLive = async (admin, caddyfile, sourceFile) => {
774
- const running = await readRunningConfig(admin);
839
+ var probeLive = (caddyfile, sourceFile) => Effect9.gen(function* () {
840
+ const { endpoint } = yield* CaddyAdminService;
841
+ const running = yield* readRunningConfig();
775
842
  if (servesNothing(running))
776
843
  return;
777
- const attributes = attributesOf(admin, running, sourceFile);
844
+ const attributes = attributesOf(endpoint, running, sourceFile);
778
845
  if (caddyfile === undefined) {
779
846
  return { attributes, ours: false, unchecked: "the stored Caddyfile is not plain text" };
780
847
  }
781
- try {
782
- const want = await desiredConfig(admin, { caddyfile });
783
- return { attributes, ours: claimable(running, want.digest) };
784
- } catch (cause) {
785
- if (cause instanceof CaddyUnreachableError)
786
- throw cause;
787
- return {
788
- attributes,
789
- ours: false,
790
- unchecked: cause instanceof Error ? cause.message : String(cause)
791
- };
848
+ const desired = yield* Effect9.result(desiredConfig({ caddyfile }));
849
+ if (Result2.isSuccess(desired)) {
850
+ return { attributes, ours: claimable(running, desired.success.digest) };
792
851
  }
793
- };
794
- var diffConfig = async (admin, news, output) => {
795
- const want = await desiredConfig(admin, news);
796
- const live = await readLive(admin);
797
- const converged = want.digest === live.configSha256 && live.configSha256 === output.configSha256 && news.sourceFile === output.sourceFile && admin.endpoint === output.endpoint;
852
+ if (isUnreachable(desired.failure))
853
+ return yield* Effect9.fail(desired.failure);
854
+ const cause = desired.failure;
855
+ return {
856
+ attributes,
857
+ ours: false,
858
+ unchecked: cause instanceof Error ? cause.message : String(cause)
859
+ };
860
+ });
861
+
862
+ // src/caddy/config-reconcile.ts
863
+ import * as Effect10 from "effect/Effect";
864
+ import * as Result3 from "effect/Result";
865
+ var diffConfig = (news, output) => Effect10.gen(function* () {
866
+ const { endpoint } = yield* CaddyAdminService;
867
+ const want = yield* desiredConfig(news);
868
+ const live = yield* readLive();
869
+ const converged = want.digest === live.configSha256 && live.configSha256 === output.configSha256 && news.sourceFile === output.sourceFile && endpoint === output.endpoint;
798
870
  return converged ? { action: "noop" } : { action: "update" };
799
- };
800
- var reconcileConfig = async (admin, props, authority) => {
801
- const want = await desiredConfig(admin, props);
802
- const running = await readRunningConfig(admin);
871
+ });
872
+ var reconcileConfig = (props, authority) => Effect10.gen(function* () {
873
+ const { endpoint } = yield* CaddyAdminService;
874
+ const want = yield* desiredConfig(props);
875
+ const running = yield* readRunningConfig().pipe(wrapOperation(endpoint, "GET /config/"));
803
876
  const before = configDigest(running);
804
877
  const known = before === authority.stored || claimable(running, want.digest);
805
878
  if (!authority.takeOver && !known) {
806
- throw refuse(admin, `Caddy runs config ${short(before)}, which this stack did not load, and the Caddyfile adapts ` + `to ${short(want.digest)} — loading it would replace every site that config serves. ` + "Deploy with --adopt (or wrap the resource in adopt(true)) to take this Caddy over, or " + "point caddyProviders() at the Caddy you meant.");
879
+ return yield* Effect10.fail(refuse(endpoint, `Caddy runs config ${short(before)}, which this stack did not load, and the Caddyfile ` + `adapts to ${short(want.digest)} — loading it would replace every site that config ` + "serves. Deploy with --adopt (or wrap the resource in adopt(true)) to take this Caddy " + "over, or point caddyProviders() at the Caddy you meant."));
807
880
  }
808
881
  let warnings = want.warnings;
809
882
  const loaded = before !== want.digest;
810
883
  if (loaded) {
811
- try {
812
- warnings = await loadCaddyfile(admin, props.caddyfile, props.sourceFile);
813
- } catch (cause) {
814
- if (!(cause instanceof CaddyAdminError))
815
- throw cause;
816
- const after = await readLive(admin).catch(() => {
817
- return;
818
- });
884
+ const outcome = yield* Effect10.result(loadCaddyfile(props.caddyfile, props.sourceFile));
885
+ if (Result3.isFailure(outcome)) {
886
+ const error = outcome.failure;
887
+ if (isUnreachable(error))
888
+ return yield* Effect10.fail(error);
889
+ const afterRead = yield* Effect10.result(readLive());
890
+ const after = Result3.isSuccess(afterRead) ? afterRead.success : undefined;
819
891
  const state = after === undefined ? "and the running config could not be read back" : after.configSha256 === before ? "Caddy kept the previous config (still serving)" : `and the running config is now ${short(after.configSha256)}, not the previous ${short(before)}`;
820
- throw refuse(admin, `Caddy refused the Caddyfile — ${cause.message}; ${state}`);
892
+ const reason = error._tag === "LoadRefused" ? `${error.message} (LoadRefused: a 200 whose body carried the error, not a failure status)` : messageOf(error);
893
+ return yield* Effect10.fail(refuse(endpoint, `Caddy refused the Caddyfile — ${reason}; ${state}`));
821
894
  }
895
+ warnings = outcome.success;
822
896
  }
823
- const attributes = await readLive(admin, props.sourceFile);
897
+ const attributes = yield* readLive(props.sourceFile);
824
898
  if (attributes.configSha256 !== want.digest) {
825
- throw refuse(admin, `after the load Caddy runs config ${short(attributes.configSha256)}, not the ` + `${short(want.digest)} this Caddyfile adapts to — another writer changed it, or it was not applied`);
899
+ return yield* Effect10.fail(refuse(endpoint, `after the load Caddy runs config ${short(attributes.configSha256)}, not the ` + `${short(want.digest)} this Caddyfile adapts to — another writer changed it, or it was ` + "not applied"));
826
900
  }
827
901
  return { attributes, loaded, warnings };
828
- };
902
+ });
829
903
 
830
904
  // src/caddy/config.ts
831
905
  var CaddyConfig = Resource("Caddy.Config", {
832
906
  defaultRemovalPolicy: "retain"
833
907
  });
834
- var unreachable = (error) => error instanceof CaddyUnreachableError;
835
- var planWithoutCaddy = (admin, fallback) => (error) => Effect6.as(Effect6.logWarning(`Caddy.Config: no Caddy at ${admin.endpoint} (${error.message}); planning without it — ` + "the Caddyfile is validated when the deploy reaches Caddy"), fallback);
836
- var CaddyConfigProvider = () => Provider.effect(CaddyConfig, Effect6.gen(function* () {
908
+ var planWithoutCaddy = (admin, fallback) => (error) => Effect11.as(Effect11.logWarning(`Caddy.Config: no Caddy at ${admin.endpoint} ` + `(${error instanceof Error ? error.message : String(error)}); planning without it — ` + "the Caddyfile is validated when the deploy reaches Caddy"), fallback);
909
+ var CaddyConfigProvider = () => Provider.effect(CaddyConfig, Effect11.gen(function* () {
837
910
  const admin = yield* CaddyAdminService;
838
- const probe = (caddyfile, sourceFile) => Effect6.flatMap(lift(() => probeLive(admin, caddyfile, sourceFile)), (found) => {
911
+ const probe = (caddyfile, sourceFile) => Effect11.flatMap(probeLive(caddyfile, sourceFile), (found) => {
839
912
  if (found === undefined)
840
- return Effect6.succeed(undefined);
913
+ return Effect11.succeed(undefined);
841
914
  if (found.ours)
842
- return Effect6.succeed(found.attributes);
915
+ return Effect11.succeed(found.attributes);
843
916
  const unowned = Unowned2(found.attributes);
844
- return found.unchecked === undefined ? Effect6.succeed(unowned) : Effect6.as(Effect6.logWarning(`Caddy.Config at ${admin.endpoint}: the running config could not be compared ` + `with the declared Caddyfile (${found.unchecked}), so it is not treated as ours`), unowned);
917
+ return found.unchecked === undefined ? Effect11.succeed(unowned) : Effect11.as(Effect11.logWarning(`Caddy.Config at ${admin.endpoint}: the running config could not be compared ` + `with the declared Caddyfile (${found.unchecked}), so it is not treated as ours`), unowned);
845
918
  });
846
919
  return CaddyConfig.Provider.of({
847
- list: () => Effect6.succeed([]),
848
- read: ({ olds, output }) => (output === undefined ? probe(resolvedString(olds, "caddyfile"), resolvedString(olds, "sourceFile")) : lift(() => readLive(admin, resolvedString(olds, "sourceFile")))).pipe(Effect6.catchIf(unreachable, planWithoutCaddy(admin, undefined))),
920
+ list: () => Effect11.succeed([]),
921
+ read: ({ olds, output }) => Effect11.gen(function* () {
922
+ return output === undefined ? yield* probe(resolvedString(olds, "caddyfile"), resolvedString(olds, "sourceFile")) : yield* readLive(resolvedString(olds, "sourceFile"));
923
+ }).pipe(Effect11.catchIf(isUnreachable, planWithoutCaddy(admin, undefined))),
849
924
  diff: ({ news, output }) => {
850
925
  if (output === undefined)
851
- return Effect6.succeed(undefined);
926
+ return Effect11.succeed(undefined);
852
927
  const update = planWithoutCaddy(admin, { action: "update" });
853
928
  if (isResolved(news)) {
854
- return lift(() => diffConfig(admin, news, output)).pipe(Effect6.catchIf(unreachable, update));
929
+ return diffConfig(news, output).pipe(Effect11.catchIf(isUnreachable, update));
855
930
  }
856
931
  const caddyfile = resolvedString(news, "caddyfile");
857
932
  if (caddyfile === undefined)
858
- return Effect6.succeed(undefined);
859
- return lift(async () => {
860
- await diffConfig(admin, { caddyfile }, output);
861
- return { action: "update" };
862
- }).pipe(Effect6.catchIf(unreachable, update));
933
+ return Effect11.succeed(undefined);
934
+ return Effect11.as(diffConfig({ caddyfile }, output), { action: "update" }).pipe(Effect11.catchIf(isUnreachable, update));
863
935
  },
864
- reconcile: ({ fqn, news, output }) => Effect6.gen(function* () {
936
+ reconcile: ({ fqn, news, output }) => Effect11.gen(function* () {
865
937
  const sameCaddy = output !== undefined && output.endpoint === admin.endpoint;
866
938
  const takeOver = sameCaddy || (yield* adoptEnabled(fqn));
867
939
  const stored = output === undefined ? {} : { stored: output.configSha256 };
868
- const applied = yield* lift(() => reconcileConfig(admin, news, { takeOver, ...stored }));
940
+ const applied = yield* reconcileConfig(news, { takeOver, ...stored });
869
941
  for (const warning of applied.warnings) {
870
- yield* Effect6.logWarning(`Caddy.Config at ${admin.endpoint}: ${warning}`);
942
+ yield* Effect11.logWarning(`Caddy.Config at ${admin.endpoint}: ${warning}`);
871
943
  }
872
944
  return applied.attributes;
873
945
  }),
874
- delete: () => Effect6.void
946
+ delete: () => Effect11.void
875
947
  });
876
948
  }));
877
949
  // src/caddy/local-admin.ts
878
- import { request as httpRequest } from "node:http";
950
+ import * as Layer3 from "effect/Layer";
951
+ import * as Caddy2 from "@distilled.cloud/caddy";
952
+
953
+ // src/caddy/admin-address.ts
879
954
  var DEFAULT_ADMIN_ADDRESS = "http://127.0.0.1:2019";
880
955
  var UNIX = "unix://";
881
956
  var HOST_PORT = /^(?:[A-Za-z0-9.-]+|\[[0-9A-Fa-f:.]+\]):\d{1,5}$/;
@@ -916,60 +991,29 @@ var parseAdminAddress = (address, hostHeader) => {
916
991
  target: { host: url.hostname.replace(/^\[(.*)\]$/, "$1"), kind: "tcp", port }
917
992
  };
918
993
  };
919
- var code = (cause) => typeof cause === "object" && cause !== null && ("code" in cause) ? String(cause.code) : undefined;
920
- var neverConnected = (cause) => code(cause) === "ECONNREFUSED" || code(cause) === "ENOENT";
921
- var exchange = (target, method, path, headers, body, timeoutMs) => new Promise((resolve, reject) => {
922
- const bytes = body === undefined ? undefined : Buffer.from(body, "utf8");
923
- const request = httpRequest({
924
- ...target.kind === "tcp" ? { host: target.host, port: target.port } : { socketPath: target.socketPath },
925
- headers: bytes === undefined ? headers : { ...headers, "Content-Length": bytes.length },
926
- method,
927
- path,
928
- timeout: timeoutMs
929
- }, (response) => {
930
- const chunks = [];
931
- response.on("data", (chunk) => chunks.push(chunk));
932
- response.on("error", reject);
933
- response.on("end", () => resolve({
934
- body: Buffer.concat(chunks).toString("utf8"),
935
- status: response.statusCode ?? 0
936
- }));
937
- });
938
- request.on("timeout", () => request.destroy(new Error(`no answer in ${String(timeoutMs)} ms`)));
939
- request.on("error", reject);
940
- request.end(bytes);
941
- });
994
+
995
+ // src/caddy/local-admin.ts
942
996
  var localCaddyAdmin = (options = {}) => {
943
997
  const address = options.address ?? DEFAULT_ADMIN_ADDRESS;
944
998
  const { listener, target } = parseAdminAddress(address, options.hostHeader);
945
- const timeoutMs = options.timeoutMs ?? 60000;
946
- const retries = options.retries ?? 2;
947
- const delay = options.retryDelayMs ?? 500;
948
- const fixed = listener.kind === "tcp" ? { Host: listener.hostHeader, Origin: `http://${listener.hostHeader}` } : { Host: "127.0.0.1" };
999
+ const credentials = target.kind === "unix" ? Caddy2.fromApiBaseUrl({
1000
+ apiBaseUrl: "http://localhost",
1001
+ hostHeader: options.hostHeader ?? "127.0.0.1"
1002
+ }) : Caddy2.fromApiBaseUrl({
1003
+ apiBaseUrl: address,
1004
+ ...options.hostHeader === undefined ? {} : { hostHeader: options.hostHeader }
1005
+ });
949
1006
  return {
950
1007
  endpoint: address,
951
- listener,
952
- request: async ({ body, headers, method, path }) => {
953
- for (let attempt = 0;; attempt += 1) {
954
- try {
955
- return await exchange(target, method, path, { ...headers, ...fixed }, body, timeoutMs);
956
- } catch (cause) {
957
- if (!neverConnected(cause) || attempt >= retries) {
958
- const reason = cause instanceof Error ? cause.message : String(cause);
959
- const message = `Caddy admin ${method} ${path} at ${address}: ${reason}`;
960
- throw neverConnected(cause) ? new CaddyUnreachableError(message) : new Error(message);
961
- }
962
- await new Promise((resolve) => setTimeout(resolve, delay));
963
- }
964
- }
965
- }
1008
+ layer: Layer3.mergeAll(credentials, makeLocalHttpClient(target, options), Layer3.succeed(Caddy2.Retry.Retry, { while: () => false })),
1009
+ listener
966
1010
  };
967
1011
  };
968
1012
  // src/caddy/providers.ts
969
- import * as Layer2 from "effect/Layer";
970
- var caddyProviders = (admin = localCaddyAdmin()) => CaddyConfigProvider().pipe(Layer2.provide(caddyAdminLayer(admin)));
1013
+ import * as Layer4 from "effect/Layer";
1014
+ var caddyProviders = (transport = localCaddyAdmin()) => CaddyConfigProvider().pipe(Layer4.provideMerge(caddyAdminLayer(transport)));
971
1015
  // src/caddy/with-file.ts
972
- import * as Effect8 from "effect/Effect";
1016
+ import * as Effect13 from "effect/Effect";
973
1017
  import * as RemovalPolicy from "alchemy/RemovalPolicy";
974
1018
 
975
1019
  // src/launchd/host-file.ts
@@ -977,7 +1021,7 @@ import { Resource as Resource2 } from "alchemy";
977
1021
  import { Unowned as Unowned3 } from "alchemy/AdoptPolicy";
978
1022
  import { isResolved as isResolved2 } from "alchemy/Diff";
979
1023
  import * as Provider2 from "alchemy/Provider";
980
- import * as Effect7 from "effect/Effect";
1024
+ import * as Effect12 from "effect/Effect";
981
1025
 
982
1026
  // src/launchd/file-identity.ts
983
1027
  var sameEntry = (a, b) => {
@@ -1006,11 +1050,11 @@ var leaveOldPath = async (runner, oldPath, path, remove, refuse3) => {
1006
1050
 
1007
1051
  // src/launchd/runner.ts
1008
1052
  import * as Context2 from "effect/Context";
1009
- import * as Layer3 from "effect/Layer";
1053
+ import * as Layer5 from "effect/Layer";
1010
1054
 
1011
1055
  class HostRunnerService extends Context2.Service()("homeflare/launchd/HostRunner") {
1012
1056
  }
1013
- var hostRunnerLayer = (runner) => Layer3.succeed(HostRunnerService, runner);
1057
+ var hostRunnerLayer = (runner) => Layer5.succeed(HostRunnerService, runner);
1014
1058
  var canActAsRoot = (runner) => runner.privileged || runner.effectiveUid() === 0;
1015
1059
 
1016
1060
  // src/launchd/file-converge.ts
@@ -1172,10 +1216,10 @@ var deleteFile = (runner, output) => removeWholeFile(runner, output, refuse3);
1172
1216
 
1173
1217
  // src/launchd/host-file.ts
1174
1218
  var HostFile = Resource2("Host.File");
1175
- var HostFileProvider = () => Provider2.effect(HostFile, Effect7.gen(function* () {
1219
+ var HostFileProvider = () => Provider2.effect(HostFile, Effect12.gen(function* () {
1176
1220
  const runner = yield* HostRunnerService;
1177
1221
  return HostFile.Provider.of({
1178
- list: () => Effect7.succeed([]),
1222
+ list: () => Effect12.succeed([]),
1179
1223
  read: ({ olds, output }) => lift(async () => {
1180
1224
  const found = await readFileAttributes(runner, olds.path);
1181
1225
  if (found === undefined)
@@ -1189,19 +1233,19 @@ var HostFileProvider = () => Provider2.effect(HostFile, Effect7.gen(function* ()
1189
1233
  return lift(() => diffFile(runner, news, output));
1190
1234
  const path = resolvedString(news, "path");
1191
1235
  if (path === undefined || path === output.path)
1192
- return Effect7.succeed(undefined);
1236
+ return Effect12.succeed(undefined);
1193
1237
  return lift(async () => await oneFile(runner, path, output.path) === true ? undefined : { action: "replace" });
1194
1238
  },
1195
- reconcile: ({ fqn, instanceId, news, output }) => Effect7.flatMap(adoptsAtApply({ fqn, instanceId, output }), (adopt) => lift(() => reconcileFile(runner, news, output, adopt))),
1239
+ reconcile: ({ fqn, instanceId, news, output }) => Effect12.flatMap(adoptsAtApply({ fqn, instanceId, output }), (adopt) => lift(() => reconcileFile(runner, news, output, adopt))),
1196
1240
  delete: ({ output }) => lift(() => deleteFile(runner, output))
1197
1241
  });
1198
1242
  }));
1199
1243
 
1200
1244
  // src/caddy/with-file.ts
1201
- var caddyWithFile = (id, props) => Effect8.gen(function* () {
1245
+ var caddyWithFile = (id, props) => Effect13.gen(function* () {
1202
1246
  const found = configProblems({ caddyfile: props.caddyfile, sourceFile: props.path });
1203
1247
  if (found.length > 0) {
1204
- return yield* Effect8.fail(new Error(`caddyWithFile ${id}: ${found.join("; ")}`));
1248
+ return yield* Effect13.fail(new Error(`caddyWithFile ${id}: ${found.join("; ")}`));
1205
1249
  }
1206
1250
  const file = yield* HostFile(`${id}-file`, {
1207
1251
  content: props.caddyfile,
@@ -1214,19 +1258,18 @@ var caddyWithFile = (id, props) => Effect8.gen(function* () {
1214
1258
  return { config, file };
1215
1259
  });
1216
1260
  export {
1217
- CaddyAdminError,
1218
1261
  CaddyAdminService,
1219
1262
  CaddyConfig,
1220
1263
  CaddyConfigProvider,
1221
- CaddyUnreachableError,
1222
1264
  DEFAULT_ADMIN_ADDRESS,
1223
1265
  accessForwardAuth,
1224
1266
  caddyAdminLayer,
1225
1267
  caddyProviders,
1226
1268
  caddyWithFile,
1269
+ isUnreachable,
1227
1270
  localCaddyAdmin,
1228
1271
  verifierProblems
1229
1272
  };
1230
1273
 
1231
- //# debugId=A91D75C3B7D7145864756E2164756E21
1274
+ //# debugId=643BF5F5D21610F364756E2164756E21
1232
1275
  //# sourceMappingURL=index.js.map