@teamscala/service-boot 0.23.516 → 0.23.518
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 +232 -1127
- package/src/agreement-gate/settings.ts +8 -8
- package/src/apply-logger-config.test.ts +46 -0
- package/src/apply-logger-config.ts +31 -0
- package/src/asset-proxy.test.ts +9 -5
- package/src/auth/sign-out.ts +122 -0
- package/src/build-client-assets/hydrate-bundle.ts +14 -4
- package/src/build-client-assets/manifest.ts +5 -18
- package/src/build-client-assets/styles-build.test.ts +11 -8
- package/src/build-client-assets/styles-build.ts +14 -63
- package/src/build-client-assets.test.ts +0 -161
- package/src/build-client-assets.ts +20 -67
- package/src/content-gate/token.ts +9 -2
- package/src/install-logging.ts +10 -26
- package/src/mount-orpc-router.ts +8 -2
- package/src/mutable-app-ref.ts +0 -7
- package/src/page-server-loader-graph.test.ts +31 -0
- package/src/page-server-loader-graph.ts +21 -0
- package/src/pg-boss-mount.ts +3 -16
- package/src/readiness-gate.ts +0 -4
- package/src/register-api-routes.ts +8 -17
- package/src/register-page-server-client.ts +6 -0
- package/src/register-registry-page-route/meta-title.test.ts +50 -0
- package/src/register-registry-page-route/meta-title.ts +1 -1
- package/src/register-registry-page-route/page-data-shared.ts +7 -11
- package/src/register-registry-page-route/resolve-request-graph.test.ts +1 -1
- package/src/register-registry-page-route/resolve-request-graph.ts +1 -2
- package/src/register-registry-page-route.ts +14 -16
- package/src/resolve-brand-favicon.test.ts +63 -0
- package/src/resolve-theme-mode.test.ts +51 -0
- package/src/resolvers/fetch-org-brand.test.ts +48 -0
- package/src/resolvers/website-by-host-map.test.ts +63 -0
- package/src/resolvers/website-by-host-map.ts +15 -3
- package/src/run-boot-hook-list.test.ts +47 -0
- package/src/run-boot-hooks.test.ts +56 -0
- package/src/scala-tools-currency-watchdog.test.ts +43 -0
- package/src/scala-tools-currency-watchdog.ts +0 -5
- package/src/transfers/presigned-upload.ts +165 -0
- package/src/two-phase-minimal-app.test.ts +24 -0
- package/src/two-phase-readiness.test.ts +102 -0
- package/src/types.test.ts +20 -0
- package/src/ui-service.test.ts +24 -0
- package/src/ws-boot-self-test.test.ts +43 -0
- package/src/build-client-assets/input-fingerprint.ts +0 -92
- package/src/build-client-assets/input-hashing.ts +0 -206
- package/src/handlers/bun-stable-watch.ts +0 -86
- package/src/handlers/messaging-send-reconcile-logic.test.ts +0 -79
- package/src/handlers/messaging-send-reconcile-logic.ts +0 -83
- package/src/handlers/messaging-send-recording-logic.test.ts +0 -86
- package/src/handlers/messaging-send-recording-logic.ts +0 -68
- package/src/handlers/messaging-send-recording-watch.test.ts +0 -91
- package/src/handlers/messaging-send-recording-watch.ts +0 -244
- package/src/handlers/messaging-send-sweep-selection.test.ts +0 -47
- package/src/handlers/messaging-send-sweep-selection.ts +0 -35
- package/src/handlers/platform-fault-escalation/config.ts +0 -95
- package/src/handlers/platform-fault-escalation/delivery.ts +0 -327
- package/src/handlers/platform-fault-escalation/event-log-queries.ts +0 -182
- package/src/handlers/platform-fault-escalation/orpc-client.ts +0 -114
- package/src/handlers/platform-fault-escalation/probe.test.ts +0 -63
- package/src/handlers/platform-fault-escalation/probe.ts +0 -19
- package/src/handlers/platform-fault-escalation-logic.ts +0 -382
- package/src/handlers/platform-fault-escalation-refresh.test.ts +0 -72
- package/src/handlers/platform-fault-escalation.test.ts +0 -497
- package/src/handlers/platform-fault-escalation.ts +0 -246
- package/src/handlers/projects-digest.ts +0 -198
- package/src/handlers/quiz-research.ts +0 -178
- package/src/handlers/registry-stale-inactive-sweep.ts +0 -82
- package/src/handlers/scala-tools-currency-watch.test.ts +0 -65
- package/src/handlers/scala-tools-currency-watch.ts +0 -162
- package/src/handlers/singleton-tools-currency-watch.test.ts +0 -51
- package/src/handlers/singleton-tools-currency-watch.ts +0 -380
- package/src/handlers/sweep-event-log.ts +0 -98
- package/src/handlers/upstream-issue-sync.ts +0 -220
- package/src/handlers/whatsapp-liveness-handler.test.ts +0 -56
- package/src/handlers/whatsapp-liveness-logic.ts +0 -53
- package/src/handlers/whatsapp-liveness.test.ts +0 -78
- package/src/handlers/whatsapp-liveness.ts +0 -138
- package/src/interpolate-meta.ts +0 -123
- package/src/load-service-prisma-client.ts +0 -53
- package/src/loader-errors.ts +0 -41
- package/src/read-viewport-from-request.ts +0 -36
- package/src/register-page-server-client.test.ts +0 -185
- package/src/resolve-app-base-url.ts +0 -35
- package/src/resolve-from-loader-config.test.ts +0 -429
- package/src/resolve-from-loader-config.ts +0 -316
- package/src/resolve-service-context.ts +0 -88
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @system server-boot
|
|
3
|
-
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* The `registry-stale-inactive-sweep` pg-boss handler. Surfaces registry
|
|
7
|
-
* rows that have been inactive beyond a threshold with no deliberate-pause
|
|
8
|
-
* metadata — the "deactivated and forgotten" class that caused the
|
|
9
|
-
* fleet_agent_slots miscount (operator directive 2026-07-20).
|
|
10
|
-
*
|
|
11
|
-
* This is a MONITOR (detection), not enforcement: it records an event_log
|
|
12
|
-
* entry so the operator sees stale rows accumulate, but never blocks or
|
|
13
|
-
* auto-reaps. The structural prevention is the `deactivated_at` stamp the
|
|
14
|
-
* registry_edit deactivate path writes; this sweep is the detection
|
|
15
|
-
* backstop that surfaces rows whose `updated_at` (the deactivation
|
|
16
|
-
* timestamp) has aged past the threshold.
|
|
17
|
-
*
|
|
18
|
-
* Excluded from the sweep (deliberate deactivation — NOT stale):
|
|
19
|
-
* - config.pause_owner / config.reactivate_when (progressive-activation paused guards)
|
|
20
|
-
* - config.operator_deactivated = true (operator emergency off)
|
|
21
|
-
*
|
|
22
|
-
* Registered under slug `@teamscala/service-boot:registry-stale-inactive-sweep`.
|
|
23
|
-
*/
|
|
24
|
-
import type { ScheduledJobConfig } from "@teamscala/db-validation/registry-schemas/scheduled-job";
|
|
25
|
-
import { getPrisma } from "@teamscala/db/prisma-registry";
|
|
26
|
-
import { recordEvent } from "@teamscala/event-log/record-event";
|
|
27
|
-
import { getAppLogger } from "@teamscala/logger/app-loggers";
|
|
28
|
-
import { pgBossHandlerRegistry } from "../pg-boss-handler-registry.ts";
|
|
29
|
-
|
|
30
|
-
const STALE_AFTER_DAYS = 30;
|
|
31
|
-
const SAMPLE_LIMIT = 20;
|
|
32
|
-
|
|
33
|
-
interface StaleRow {
|
|
34
|
-
type: string;
|
|
35
|
-
slug: string;
|
|
36
|
-
updated_at: Date;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async function registryStaleInactiveSweep(): Promise<void> {
|
|
40
|
-
const log = getAppLogger();
|
|
41
|
-
const prisma = getPrisma();
|
|
42
|
-
|
|
43
|
-
const rows = await prisma.$queryRawUnsafe<StaleRow[]>(`
|
|
44
|
-
SELECT type, slug, updated_at
|
|
45
|
-
FROM registry_entries
|
|
46
|
-
WHERE is_active = false
|
|
47
|
-
AND updated_at < NOW() - INTERVAL '${STALE_AFTER_DAYS} days'
|
|
48
|
-
AND NOT (config ? 'pause_owner')
|
|
49
|
-
AND NOT (config ? 'reactivate_when')
|
|
50
|
-
AND NOT (config->>'operator_deactivated' = 'true')
|
|
51
|
-
ORDER BY updated_at ASC
|
|
52
|
-
`);
|
|
53
|
-
|
|
54
|
-
const byType = new Map<string, number>();
|
|
55
|
-
for (const r of rows) {
|
|
56
|
-
byType.set(r.type, (byType.get(r.type) ?? 0) + 1);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const samples = rows
|
|
60
|
-
.slice(0, SAMPLE_LIMIT)
|
|
61
|
-
.map((r) => `${r.type}/${r.slug}(${r.updated_at.toISOString().slice(0, 10)})`);
|
|
62
|
-
|
|
63
|
-
recordEvent({
|
|
64
|
-
kind: "registry.stale-inactive.detected",
|
|
65
|
-
payload: {
|
|
66
|
-
totalStale: rows.length,
|
|
67
|
-
staleAfterDays: STALE_AFTER_DAYS,
|
|
68
|
-
byType: Object.fromEntries(byType),
|
|
69
|
-
samples,
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
log.info(
|
|
74
|
-
`[registry-stale-inactive-sweep] ${rows.length} stale inactive row(s) (>${STALE_AFTER_DAYS}d, no pause metadata) — recorded to event_log`,
|
|
75
|
-
{ byType: Object.fromEntries(byType), samples },
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
pgBossHandlerRegistry.register(
|
|
80
|
-
"@teamscala/service-boot:registry-stale-inactive-sweep",
|
|
81
|
-
registryStaleInactiveSweep as (ctx: { config: ScheduledJobConfig }) => Promise<void>,
|
|
82
|
-
);
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
// @system codegen
|
|
2
|
-
// @status generated
|
|
3
|
-
// @edit change the suite in the owned-suites band, then re-run codegen. Hand-edits are overwritten.
|
|
4
|
-
//
|
|
5
|
-
// This suite's assertions are OWNED by the codegen band: the band module
|
|
6
|
-
// carries them verbatim, this file is the emission, and hand edits here are
|
|
7
|
-
// overwritten on the next run. The rationale each assertion carries moved
|
|
8
|
-
// with it into the band.
|
|
9
|
-
|
|
10
|
-
import { describe, expect, test } from "bun:test";
|
|
11
|
-
import { join } from "node:path";
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Regression lock: this watch must never restart scala-sentinel again.
|
|
15
|
-
*
|
|
16
|
-
* It used to reinstall @teamscala/scala-tools and then `pm2 restart scala-sentinel`
|
|
17
|
-
* "so the fresh scala-tools loads". That premise is false for this package —
|
|
18
|
-
* sentinel SPAWNS `node_modules/.bin/scala-tools` per invocation rather than
|
|
19
|
-
* importing it, so the reinstall alone is what takes effect. The restart's only
|
|
20
|
-
* real consequence was taking enforcement offline: every guard-daemon shutdown
|
|
21
|
-
* logs "152 guard(s) failed to run (fail-open)", so each publish of scala-tools
|
|
22
|
-
* opened a window where tool-check, turn-end and pre-push silently did not run.
|
|
23
|
-
*
|
|
24
|
-
* The assertion is on the SOURCE rather than on a mocked run, and that is
|
|
25
|
-
* deliberate rather than a shortcut: the property being locked is the ABSENCE of
|
|
26
|
-
* a side effect. A behavioural test can only prove the restart did not fire for
|
|
27
|
-
* the paths it happens to drive, whereas the absence of any pm2-restart call in
|
|
28
|
-
* the module proves it cannot fire on any path. It also stays honest if someone
|
|
29
|
-
* reintroduces the restart behind a new condition.
|
|
30
|
-
*/
|
|
31
|
-
const SOURCE = await Bun.file(join(import.meta.dir, "scala-tools-currency-watch.ts")).text();
|
|
32
|
-
|
|
33
|
-
/** Comment prose legitimately discusses the removed restart; code must not do it. */
|
|
34
|
-
const CODE = SOURCE.split("\n")
|
|
35
|
-
.filter((l) => !l.trimStart().startsWith("//") && !l.trimStart().startsWith("*"))
|
|
36
|
-
.join("\n");
|
|
37
|
-
|
|
38
|
-
describe("scala-tools-currency-watch never restarts the guard daemon", () => {
|
|
39
|
-
test("issues no pm2 restart command", () => {
|
|
40
|
-
expect(CODE).not.toContain('"restart"');
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
test("never names scala-sentinel as a spawn argument", () => {
|
|
44
|
-
// The reinstall targets the sentinel DIRECTORY (a path constant); what must
|
|
45
|
-
// not exist is scala-sentinel as a process argument to a supervisor call.
|
|
46
|
-
expect(CODE).not.toContain('"scala-sentinel"');
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
test("uses no bare pm2 invocation", () => {
|
|
50
|
-
// forbid-bare-pm2-restart: a bare `pm2` call bypasses the currency-gated
|
|
51
|
-
// canonical pipeline, and the guard blocks agents from making this exact
|
|
52
|
-
// call — the handler was doing what agents are prevented from doing.
|
|
53
|
-
expect(CODE).not.toContain('"pm2"');
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
test("still performs the reinstall, which is the watch's actual job", () => {
|
|
57
|
-
// Removing the restart must not turn this into a no-op watch.
|
|
58
|
-
expect(CODE).toContain("deps");
|
|
59
|
-
expect(CODE).toContain("install");
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
test("still records the reinstall event for observability", () => {
|
|
63
|
-
expect(CODE).toContain("scala-tools.currency.reinstalled");
|
|
64
|
-
});
|
|
65
|
-
});
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @system server-boot
|
|
3
|
-
* @status handwritten
|
|
4
|
-
* @edit edit directly
|
|
5
|
-
*
|
|
6
|
-
* The `scala-tools-currency-watch` pg-boss handler — keeps scala-sentinel's
|
|
7
|
-
* INSTALLED @teamscala/scala-tools at registry `latest` (open migration
|
|
8
|
-
* `daemon-scala-tools-currency`). scala-sentinel resolves scala-tools via its
|
|
9
|
-
* own node_modules (the installed copy), which goes STALE on every
|
|
10
|
-
* @teamscala/scala-tools publish until a manual deps install + restart — so the
|
|
11
|
-
* daemon's publish-chain + codegen trio run OLD scala-tools while workspace
|
|
12
|
-
* source is current (incident #58). This watch polls the registry dist-tag and,
|
|
13
|
-
* when scala-sentinel's installed copy lags, REINSTALLS it. It does not restart
|
|
14
|
-
* anything: sentinel spawns `node_modules/.bin/scala-tools` per invocation
|
|
15
|
-
* rather than importing it, so the next spawn picks up the new binary. See the
|
|
16
|
-
* no-restart note at the call site for the evidence and for why the restart that
|
|
17
|
-
* used to live there was disabling enforcement on every publish.
|
|
18
|
-
*
|
|
19
|
-
* Boot-safety (protective-layers-fail-explicitly): every step is wrapped — a
|
|
20
|
-
* dist-tag fetch failure, a version-parse failure, OR an install failure NEVER
|
|
21
|
-
* crashes the job or loga; each records a failure event + returns. Repeated
|
|
22
|
-
* reinstalls are prevented by strict semver-lt (re-installs only when installed
|
|
23
|
-
* is genuinely behind latest) + the idempotent --force-resolve install.
|
|
24
|
-
* Subprocesses go through @teamscala/os spawn (the single spawn surface).
|
|
25
|
-
*
|
|
26
|
-
* Registered under slug `@teamscala/service-boot:scala-tools-currency-watch`.
|
|
27
|
-
*/
|
|
28
|
-
// NO RESTART. The reinstall alone is sufficient, and restarting here was actively harmful.
|
|
29
|
-
//
|
|
30
|
-
// The premise this watch was written on — "restart so the fresh scala-tools loads" — does not hold for THIS package. scala-sentinel never
|
|
31
|
-
// imports `@teamscala/scala-tools`; it SPAWNS `node_modules/.bin/scala-tools` per invocation, so a freshly installed binary is picked up by the next
|
|
32
|
-
// spawn with no process restart at all. Verified 2026-07-27: the package is declared in scala-sentinel/package.json, yet scala-guard/src contains zero
|
|
33
|
-
// import statements resolving to it — it is spawned, never linked. dep-sync already encodes the same conclusion — HARNESS_CRITICAL_FAST_PATH
|
|
34
|
-
// maps this exact pair to install-WITHOUT-restart, while its PM2_CACHED sibling map exists for packages a daemon really does load at boot
|
|
35
|
-
// (@teamscala/codegen → codegen-daemon). This handler was the odd one out.
|
|
36
|
-
//
|
|
37
|
-
// The cost was not theoretical. Restarting the guard daemon takes enforcement offline for its warmup, and every shutdown logs "152 guard(s)
|
|
38
|
-
// failed to run (fail-open)" — so each unnecessary restart opened a window in which tool-check, turn-end and pre-push silently did not run. Three
|
|
39
|
-
// scala-tools versions published on 2026-07-26 alone, and this watch polls, so it re-fired for each. That inverts
|
|
40
|
-
// `infrastructure-outranks-application-durability`: a currency convenience was repeatedly disabling the thing it exists to keep current.
|
|
41
|
-
//
|
|
42
|
-
// It also used a bare `pm2 restart`, which is forbidden (`forbid-bare-pm2-restart`) precisely because it bypasses the
|
|
43
|
-
// currency-gated canonical pipeline — the guard blocks agents from that exact call.
|
|
44
|
-
//
|
|
45
|
-
// If a future package genuinely needs a restart to take effect, it does not belong here: add it to dep-sync's PM2_CACHED map, which already owns
|
|
46
|
-
// install-plus-restart and coalesces them.
|
|
47
|
-
|
|
48
|
-
import type { ScheduledJobConfig } from "@teamscala/db-validation/registry-schemas/scheduled-job";
|
|
49
|
-
import { recordEvent } from "@teamscala/event-log/record-event";
|
|
50
|
-
import { getAppLogger } from "@teamscala/logger/app-loggers";
|
|
51
|
-
import { workspaceRoot } from "@teamscala/os/host-paths";
|
|
52
|
-
import { spawn } from "@teamscala/os/spawn";
|
|
53
|
-
import { pgBossHandlerRegistry } from "../pg-boss-handler-registry.ts";
|
|
54
|
-
|
|
55
|
-
const PKG = "@teamscala/scala-tools";
|
|
56
|
-
const SENTINEL_DIR = `${workspaceRoot()}/scala-sentinel`;
|
|
57
|
-
const INSTALLED_PKG_JSON = `${SENTINEL_DIR}/node_modules/@teamscala/scala-tools/package.json`;
|
|
58
|
-
|
|
59
|
-
interface PkgJson {
|
|
60
|
-
version: string;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function parseSemver(v: string): [number, number, number] | null {
|
|
64
|
-
const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v.trim());
|
|
65
|
-
return m ? [Number(m[1]!), Number(m[2]!), Number(m[3]!)] : null;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/** Strict a < b on major.minor.patch (pre-releases ignored — latest is a stable dist-tag). */
|
|
69
|
-
function semverLt(a: string, b: string): boolean {
|
|
70
|
-
const pa = parseSemver(a);
|
|
71
|
-
const pb = parseSemver(b);
|
|
72
|
-
if (!pa || !pb) return false;
|
|
73
|
-
for (let i = 0; i < 3; i++) if (pa[i] !== pb[i]) return pa[i]! < pb[i]!;
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
async function scalaToolsCurrencyWatch(_ctx: { config: ScheduledJobConfig }): Promise<void> {
|
|
78
|
-
const log = getAppLogger();
|
|
79
|
-
try {
|
|
80
|
-
const installedPkg = await Bun.file(INSTALLED_PKG_JSON).json() as PkgJson;
|
|
81
|
-
const installed = installedPkg.version;
|
|
82
|
-
|
|
83
|
-
const distTag = await spawn({
|
|
84
|
-
name: "scala-tools-currency-watch:dist-tag",
|
|
85
|
-
command: ["scala-tools", "packages", "dist-tag", "get", PKG, "latest"],
|
|
86
|
-
timeoutMs: 30_000,
|
|
87
|
-
});
|
|
88
|
-
// dist-tag get prints the version (last non-empty line).
|
|
89
|
-
const latest =
|
|
90
|
-
(distTag.stdout ?? "")
|
|
91
|
-
.split("\n")
|
|
92
|
-
.map((l) => l.trim())
|
|
93
|
-
.filter(Boolean)
|
|
94
|
-
.pop() ?? "";
|
|
95
|
-
if (!latest || !parseSemver(latest)) {
|
|
96
|
-
log.warn(
|
|
97
|
-
`[scala-tools-currency-watch] no readable latest (exit=${distTag.exitCode}, stdout=${(distTag.stdout ?? "").slice(0, 80)}) — skip`,
|
|
98
|
-
);
|
|
99
|
-
recordEvent({
|
|
100
|
-
kind: "scala-tools.currency.tick",
|
|
101
|
-
payload: { installed, latest: null, note: "dist-tag fetch unreadable" },
|
|
102
|
-
});
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (!semverLt(installed, latest)) {
|
|
107
|
-
recordEvent({
|
|
108
|
-
kind: "scala-tools.currency.tick",
|
|
109
|
-
payload: { installed, latest, status: "current" },
|
|
110
|
-
});
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// STALE — reinstall + restart.
|
|
115
|
-
log.warn(
|
|
116
|
-
`[scala-tools-currency-watch] scala-sentinel ${PKG} ${installed} < latest ${latest} — reinstalling + restarting`,
|
|
117
|
-
);
|
|
118
|
-
const install = await spawn({
|
|
119
|
-
name: "scala-tools-currency-watch:deps-install",
|
|
120
|
-
// A plain install fetches the current version, which is the whole point
|
|
121
|
-
// of this watch. Re-resolving the entire tree is an emergency hatch, not
|
|
122
|
-
// a currency step (operator ruling 2026-07-31).
|
|
123
|
-
command: ["scala-tools", "deps", "install"],
|
|
124
|
-
cwd: SENTINEL_DIR,
|
|
125
|
-
timeoutMs: 300_000,
|
|
126
|
-
});
|
|
127
|
-
if ((install.exitCode ?? -1) !== 0) {
|
|
128
|
-
log.error(
|
|
129
|
-
`[scala-tools-currency-watch] deps install failed (exit ${install.exitCode}) — NOT restarting to avoid boot-looping the enforcement daemon: ${(install.stderr ?? "").slice(0, 200)}`,
|
|
130
|
-
);
|
|
131
|
-
recordEvent({
|
|
132
|
-
kind: "scala-tools.currency.install-failed",
|
|
133
|
-
payload: {
|
|
134
|
-
installed,
|
|
135
|
-
latest,
|
|
136
|
-
exitCode: install.exitCode,
|
|
137
|
-
stderr: (install.stderr ?? "").slice(0, 300),
|
|
138
|
-
},
|
|
139
|
-
});
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
recordEvent({
|
|
143
|
-
kind: "scala-tools.currency.reinstalled",
|
|
144
|
-
payload: { from: installed, to: latest },
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
log.info(
|
|
148
|
-
`[scala-tools-currency-watch] reinstalled ${PKG} ${installed} → ${latest} (no restart: sentinel spawns the binary, it does not import it)`,
|
|
149
|
-
);
|
|
150
|
-
} catch (err) {
|
|
151
|
-
// Boot-safety: never propagate — record + return (a watch error must not crash loga).
|
|
152
|
-
log.error(
|
|
153
|
-
`[scala-tools-currency-watch] caught — fail-open (no restart): ${err instanceof Error ? err.message : String(err)}`,
|
|
154
|
-
);
|
|
155
|
-
recordEvent({
|
|
156
|
-
kind: "scala-tools.currency.watch-error",
|
|
157
|
-
payload: { error: err instanceof Error ? err.message : String(err) },
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
pgBossHandlerRegistry.register("@teamscala/service-boot:scala-tools-currency-watch", scalaToolsCurrencyWatch);
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
// @system codegen
|
|
2
|
-
// @status generated
|
|
3
|
-
// @edit change the suite in the owned-suites band, then re-run codegen. Hand-edits are overwritten.
|
|
4
|
-
//
|
|
5
|
-
// This suite's assertions are OWNED by the codegen band: the band module
|
|
6
|
-
// carries them verbatim, this file is the emission, and hand edits here are
|
|
7
|
-
// overwritten on the next run. The rationale each assertion carries moved
|
|
8
|
-
// with it into the band.
|
|
9
|
-
|
|
10
|
-
import { describe, expect, test } from "bun:test";
|
|
11
|
-
import { join } from "node:path";
|
|
12
|
-
|
|
13
|
-
const SOURCE = await Bun.file(join(import.meta.dir, "singleton-tools-currency-watch.ts")).text();
|
|
14
|
-
|
|
15
|
-
const CODE = SOURCE.split("\n")
|
|
16
|
-
.filter((l) => !l.trimStart().startsWith("//") && !l.trimStart().startsWith("*"))
|
|
17
|
-
.join("\n");
|
|
18
|
-
|
|
19
|
-
describe("singleton-tools-currency-watch", () => {
|
|
20
|
-
test("reads version_source from config/scala-singleton-tools", () => {
|
|
21
|
-
expect(CODE).toContain("scala-singleton-tools");
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test("reinstalls stale tools", () => {
|
|
25
|
-
expect(CODE).toContain("reinstall");
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test("skips bare tools (bun) with a deferred event", () => {
|
|
29
|
-
expect(CODE).toContain("deferred-to-bun-stable-watch");
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test("records tick events for observability", () => {
|
|
33
|
-
expect(CODE).toContain("singleton-tools.currency.tick");
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test("records reinstall events", () => {
|
|
37
|
-
expect(CODE).toContain("singleton-tools.currency.reinstalled");
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test("records install-failed events", () => {
|
|
41
|
-
expect(CODE).toContain("singleton-tools.currency.install-failed");
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("uses semver comparison (not string comparison)", () => {
|
|
45
|
-
expect(CODE).toContain("semverLt");
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
test("handles unreadable installed version gracefully", () => {
|
|
49
|
-
expect(CODE).toContain("unreadable");
|
|
50
|
-
});
|
|
51
|
-
});
|