cursedops 0.19.3 → 0.19.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/launchd.ts +33 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cursedops",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.5",
|
|
4
4
|
"description": "The build-and-ops answers this generation's apps wrote independently and identically: finding a generation's roots — and printing a command that runs when pasted — without knowing a path, the generation's whole-tree laws run over one repo from a checkout or a worktree, macOS launchd agent install/replace/remove and the live port a job serves, the scaffolding and verdicts of a deployed smoke (origin probe, the smoke's own environment, a network that lies about DNS, a settled version), the static-serving helpers eight apps copied — the path-traversal guard among them — the API floor that keeps an unmatched /api/... from ever being answered with the app shell, the commit and dirty flag a checkout-served process reports, the Cloudflare Worker deploy toolkit four apps copied (the deploy sequence, exact-set secrets over a pipe, origin-first rollback, the curl edge fetch, the row-for-row D1 import proof, the billed-CPU tail check around a deploy's walk and smoke, and each app's worker:secrets and worker:smoke main as one function of its data), the relay a Worker fronts a Mac-bound app with (the Durable Object, the frames, the Mac's dialer and key rotation — lifted from station for roms — and the signed-in stage walk's skeleton and the relay app's whole worker:deploy), the Worker import-graph and await-port checks every Worker app's suite runs over its own source, and the public-surface ratchet three published libraries each carried a forked copy of. Mechanism only — no app knows its name from here. Bun, zero runtime dependencies (typescript is an optional peer, for public-surface only), ships source.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
package/src/launchd.ts
CHANGED
|
@@ -171,6 +171,16 @@ export interface PlistSpec {
|
|
|
171
171
|
throttleInterval?: number;
|
|
172
172
|
processType?: string;
|
|
173
173
|
startCalendarInterval?: CalendarInterval;
|
|
174
|
+
/**
|
|
175
|
+
* Run every this many seconds — "every five minutes", which a calendar key cannot say.
|
|
176
|
+
*
|
|
177
|
+
* 🔴 Exclusive with {@link startCalendarInterval}: launchd accepts a plist carrying
|
|
178
|
+
* both without complaint and honors ONE, which is how a "daily" backup in this fleet
|
|
179
|
+
* became a job that never fired. {@link renderPlist} throws rather than render that.
|
|
180
|
+
*/
|
|
181
|
+
startInterval?: number;
|
|
182
|
+
/** Run when any of these paths changes (a drop folder, a file another job writes). */
|
|
183
|
+
watchPaths?: readonly string[];
|
|
174
184
|
/**
|
|
175
185
|
* The provenance comment written at the top of the file.
|
|
176
186
|
*
|
|
@@ -206,6 +216,12 @@ function integers(record: Record<string, number>, indent: string): string {
|
|
|
206
216
|
* those two being wrong is an app that either never starts or never stops.
|
|
207
217
|
*/
|
|
208
218
|
export function renderPlist(spec: PlistSpec): string {
|
|
219
|
+
if (spec.startInterval !== undefined && spec.startCalendarInterval) {
|
|
220
|
+
throw new Error(`${spec.label}: startInterval and startCalendarInterval are exclusive — launchd silently honors one of the two; pick one`);
|
|
221
|
+
}
|
|
222
|
+
if (spec.startInterval !== undefined && !(Number.isInteger(spec.startInterval) && spec.startInterval > 0)) {
|
|
223
|
+
throw new Error(`${spec.label}: startInterval must be a positive whole number of seconds (got ${spec.startInterval})`);
|
|
224
|
+
}
|
|
209
225
|
const lines: string[] = [];
|
|
210
226
|
lines.push(`\t<key>Label</key>`, `\t<string>${escapeXml(spec.label)}</string>`);
|
|
211
227
|
lines.push(`\t<key>ProgramArguments</key>`, `\t<array>`);
|
|
@@ -219,6 +235,12 @@ export function renderPlist(spec: PlistSpec): string {
|
|
|
219
235
|
const defined = Object.fromEntries(Object.entries(spec.startCalendarInterval).filter(([, value]) => typeof value === "number")) as Record<string, number>;
|
|
220
236
|
lines.push(`\t<key>StartCalendarInterval</key>`, `\t<dict>`, integers(defined, "\t\t"), `\t</dict>`);
|
|
221
237
|
}
|
|
238
|
+
if (spec.startInterval !== undefined) lines.push(`\t<key>StartInterval</key>`, `\t<integer>${spec.startInterval}</integer>`);
|
|
239
|
+
if (spec.watchPaths && spec.watchPaths.length > 0) {
|
|
240
|
+
lines.push(`\t<key>WatchPaths</key>`, `\t<array>`);
|
|
241
|
+
for (const path of spec.watchPaths) lines.push(`\t\t<string>${escapeXml(path)}</string>`);
|
|
242
|
+
lines.push(`\t</array>`);
|
|
243
|
+
}
|
|
222
244
|
if (spec.runAtLoad !== undefined) lines.push(`\t<key>RunAtLoad</key>`, `\t<${spec.runAtLoad}/>`);
|
|
223
245
|
if (spec.keepAlive !== undefined) lines.push(`\t<key>KeepAlive</key>`, `\t<${spec.keepAlive}/>`);
|
|
224
246
|
if (spec.throttleInterval !== undefined) lines.push(`\t<key>ThrottleInterval</key>`, `\t<integer>${spec.throttleInterval}</integer>`);
|
|
@@ -404,6 +426,17 @@ export async function answering(
|
|
|
404
426
|
}
|
|
405
427
|
}
|
|
406
428
|
|
|
429
|
+
/**
|
|
430
|
+
* Does the app on this Mac's `port` answer its health route? {@link answering}, tightened.
|
|
431
|
+
*
|
|
432
|
+
* `/healthz` and `response.ok` rather than `/` and `< 500`: an app that serves its SPA shell
|
|
433
|
+
* to anyone answers `/` with only something static mounted, and a health route is the one that
|
|
434
|
+
* needs the server. Six apps' `service.ts` wrote this same probe by hand (forge task 3683).
|
|
435
|
+
*/
|
|
436
|
+
export async function healthzAnswering(port: number, { timeoutMs = 45_000, stepMs = 500, path = "/healthz" } = {}): Promise<boolean> {
|
|
437
|
+
return await answering(`http://127.0.0.1:${port}${path}`, { timeoutMs, stepMs, accept: (response) => response.ok });
|
|
438
|
+
}
|
|
439
|
+
|
|
407
440
|
/**
|
|
408
441
|
* The `PORT` a `launchctl print` dump says the job is handed, or `null`.
|
|
409
442
|
*
|