@teamscala/service-boot 0.23.391 → 0.23.394
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
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
},
|
|
7
7
|
"name": "@teamscala/service-boot",
|
|
8
8
|
"type": "module",
|
|
9
|
-
"version": "0.23.
|
|
9
|
+
"version": "0.23.394",
|
|
10
10
|
"sideEffects": false,
|
|
11
11
|
"imports": {
|
|
12
12
|
"#service-boot/*": "./src/*",
|
|
@@ -34,9 +34,11 @@
|
|
|
34
34
|
"@teamscala/generic-registry": "*",
|
|
35
35
|
"@teamscala/http": "*",
|
|
36
36
|
"@teamscala/logger": "*",
|
|
37
|
+
"@teamscala/notify-cache": "*",
|
|
37
38
|
"@teamscala/object-storage": "*",
|
|
38
39
|
"@teamscala/orpc": "*",
|
|
39
40
|
"@teamscala/os": "*",
|
|
41
|
+
"@teamscala/publish-registry": "*",
|
|
40
42
|
"@teamscala/realtime-utils": "*",
|
|
41
43
|
"@teamscala/resolver-engine": "*",
|
|
42
44
|
"@teamscala/timing": "*",
|
|
@@ -562,6 +564,14 @@
|
|
|
562
564
|
"types": "./src/load-service-prisma-client.d.ts",
|
|
563
565
|
"default": "./src/load-service-prisma-client.ts"
|
|
564
566
|
},
|
|
567
|
+
"./module-not-found": {
|
|
568
|
+
"types": "./src/module-not-found.d.ts",
|
|
569
|
+
"default": "./src/module-not-found.ts"
|
|
570
|
+
},
|
|
571
|
+
"./module-not-found.ts": {
|
|
572
|
+
"types": "./src/module-not-found.d.ts",
|
|
573
|
+
"default": "./src/module-not-found.ts"
|
|
574
|
+
},
|
|
565
575
|
"./mount": {
|
|
566
576
|
"types": "./src/mount.d.ts",
|
|
567
577
|
"default": "./src/mount.ts"
|
|
@@ -119,7 +119,6 @@ async function sendAuthorityReportEmail(opts: {
|
|
|
119
119
|
categoryScores: Record<string, number>;
|
|
120
120
|
ctaUrl?: string;
|
|
121
121
|
organisationId?: string | null;
|
|
122
|
-
brand?: import("@teamscala/email/identity/resolve-email-sender").OrgBrandTokens;
|
|
123
122
|
prisma: any;
|
|
124
123
|
}): Promise<void> {
|
|
125
124
|
const log = getAppLogger();
|
|
@@ -137,7 +136,23 @@ async function sendAuthorityReportEmail(opts: {
|
|
|
137
136
|
}
|
|
138
137
|
initResendClient(decrypted.token);
|
|
139
138
|
|
|
140
|
-
const
|
|
139
|
+
const brandRow =
|
|
140
|
+
opts.organisationId && opts.prisma
|
|
141
|
+
? await fetchOrgBrand(
|
|
142
|
+
opts.prisma as Parameters<typeof fetchOrgBrand>[0],
|
|
143
|
+
opts.organisationId,
|
|
144
|
+
).catch(() => null)
|
|
145
|
+
: null;
|
|
146
|
+
const sender = await resolveEmailSender(opts.organisationId ?? null, {
|
|
147
|
+
brand: brandRow
|
|
148
|
+
? {
|
|
149
|
+
orgName: brandRow.companyName ?? "",
|
|
150
|
+
primaryColor: brandRow.primaryColor,
|
|
151
|
+
accentColor: brandRow.accentColor ?? null,
|
|
152
|
+
logoUrl: brandRow.logo.light ?? null,
|
|
153
|
+
}
|
|
154
|
+
: undefined,
|
|
155
|
+
});
|
|
141
156
|
const primary = sender.brand.primaryColor;
|
|
142
157
|
const muted = "#6b7280"; // structural neutral for body labels
|
|
143
158
|
const logoUrl = sender.brand.logoUrl;
|
|
@@ -441,21 +456,6 @@ export const publicAssessmentRouter = {
|
|
|
441
456
|
(typeof assessment.cta_url === "string" && assessment.cta_url) ||
|
|
442
457
|
undefined;
|
|
443
458
|
if (band) {
|
|
444
|
-
const prisma = (context as { prisma: unknown }).prisma;
|
|
445
|
-
const brandRow = opts.organisationId
|
|
446
|
-
? await fetchOrgBrand(
|
|
447
|
-
prisma as Parameters<typeof fetchOrgBrand>[0],
|
|
448
|
-
opts.organisationId,
|
|
449
|
-
).catch(() => null)
|
|
450
|
-
: null;
|
|
451
|
-
const brand = brandRow
|
|
452
|
-
? {
|
|
453
|
-
orgName: brandRow.companyName ?? "",
|
|
454
|
-
primaryColor: brandRow.primaryColor,
|
|
455
|
-
accentColor: brandRow.accentColor ?? null,
|
|
456
|
-
logoUrl: brandRow.logo.light ?? null,
|
|
457
|
-
}
|
|
458
|
-
: undefined;
|
|
459
459
|
void sendAuthorityReportEmail({
|
|
460
460
|
to: email,
|
|
461
461
|
firstName,
|
|
@@ -463,8 +463,7 @@ export const publicAssessmentRouter = {
|
|
|
463
463
|
bandLabel: band.label,
|
|
464
464
|
categoryScores,
|
|
465
465
|
ctaUrl,
|
|
466
|
-
organisationId:
|
|
467
|
-
brand,
|
|
466
|
+
organisationId: orgId,
|
|
468
467
|
prisma,
|
|
469
468
|
}).catch((e) =>
|
|
470
469
|
log.error(
|
|
@@ -24,9 +24,10 @@
|
|
|
24
24
|
import type { ScheduledJobConfig } from "@teamscala/db-validation/registry-schemas/scheduled-job";
|
|
25
25
|
import { recordEvent } from "@teamscala/event-log/record-event";
|
|
26
26
|
import { getAppLogger } from "@teamscala/logger/app-loggers";
|
|
27
|
+
import { getDistTagVersion } from "@teamscala/publish-registry";
|
|
27
28
|
import { pgBossHandlerRegistry } from "../pg-boss-handler-registry.ts";
|
|
28
29
|
|
|
29
|
-
const
|
|
30
|
+
const NPM_REGISTRY = "https://registry.npmjs.org";
|
|
30
31
|
const TARGET_STABLE = "1.4.0"; // flip back when a stable at or above this ships
|
|
31
32
|
|
|
32
33
|
interface BunLatestManifest {
|
|
@@ -54,23 +55,19 @@ function isStableAtOrAbove(version: string, target: string): boolean {
|
|
|
54
55
|
|
|
55
56
|
async function bunStableWatch(_ctx: { config: ScheduledJobConfig }): Promise<void> {
|
|
56
57
|
const log = getAppLogger();
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (!res.ok) throw new Error(`npm bun/latest HTTP ${res.status}`);
|
|
61
|
-
manifest = (await res.json()) as BunLatestManifest;
|
|
62
|
-
} catch (err) {
|
|
63
|
-
log.warn(`[bun-stable-watch] failed to fetch npm bun/latest: ${err}`);
|
|
58
|
+
const version = await getDistTagVersion("bun", "latest", NPM_REGISTRY);
|
|
59
|
+
if (!version) {
|
|
60
|
+
log.warn("[bun-stable-watch] failed to fetch npm bun/latest");
|
|
64
61
|
return;
|
|
65
62
|
}
|
|
66
|
-
if (isStableAtOrAbove(
|
|
63
|
+
if (isStableAtOrAbove(version, TARGET_STABLE)) {
|
|
67
64
|
log.warn(
|
|
68
|
-
`[bun-stable-watch] Bun stable ${
|
|
65
|
+
`[bun-stable-watch] Bun stable ${version} (>=${TARGET_STABLE}) shipped — escape hatch 'bun-14-lock-resolution-pin' is ready to retire. Run the atomic flip-back: config/scala-tools-deps.lockResolutionBun -> "bun" + railpack_config rows -> 1.4.0 stable + delete /usr/local/bin/bun-1.3.14 + a green deploy.`,
|
|
69
66
|
);
|
|
70
67
|
recordEvent({
|
|
71
68
|
kind: "bun.stable.shipped",
|
|
72
69
|
payload: {
|
|
73
|
-
latest:
|
|
70
|
+
latest: version,
|
|
74
71
|
targetStable: TARGET_STABLE,
|
|
75
72
|
note: "escape hatch bun-14-lock-resolution-pin ready to retire; verify railpack 1.4.0 support then run the atomic flip-back",
|
|
76
73
|
},
|
|
@@ -78,11 +75,11 @@ async function bunStableWatch(_ctx: { config: ScheduledJobConfig }): Promise<voi
|
|
|
78
75
|
return;
|
|
79
76
|
}
|
|
80
77
|
log.info(
|
|
81
|
-
`[bun-stable-watch] npm bun/latest = ${
|
|
78
|
+
`[bun-stable-watch] npm bun/latest = ${version} — not yet stable ${TARGET_STABLE}; escape hatch remains`,
|
|
82
79
|
);
|
|
83
80
|
recordEvent({
|
|
84
81
|
kind: "bun.watch.tick",
|
|
85
|
-
payload: { latest:
|
|
82
|
+
payload: { latest: version, targetStable: TARGET_STABLE },
|
|
86
83
|
});
|
|
87
84
|
}
|
|
88
85
|
|
|
@@ -28,6 +28,7 @@ import { loadRegistryConfig } from "@teamscala/db/registry/load-config";
|
|
|
28
28
|
import { recordEvent } from "@teamscala/event-log/record-event";
|
|
29
29
|
import { getAppLogger } from "@teamscala/logger/app-loggers";
|
|
30
30
|
import { spawn } from "@teamscala/os/spawn";
|
|
31
|
+
import { getDistTagVersion } from "@teamscala/publish-registry";
|
|
31
32
|
import { pgBossHandlerRegistry } from "../pg-boss-handler-registry.ts";
|
|
32
33
|
|
|
33
34
|
/**
|
|
@@ -180,7 +181,7 @@ async function fetchLatestGithubRelease(repo: string): Promise<string | null> {
|
|
|
180
181
|
async function fetchLatestVersion(pkg: string, distTag: string): Promise<string | null> {
|
|
181
182
|
try {
|
|
182
183
|
// @teamscala/* packages use the internal dist-tag tool; public npm packages
|
|
183
|
-
// fetch directly from the npm registry
|
|
184
|
+
// fetch directly from the npm registry via the typed primitive.
|
|
184
185
|
if (pkg.startsWith("@teamscala/")) {
|
|
185
186
|
const result = await spawn({
|
|
186
187
|
name: "singleton-tools-currency:dist-tag",
|
|
@@ -196,12 +197,7 @@ async function fetchLatestVersion(pkg: string, distTag: string): Promise<string
|
|
|
196
197
|
if (!latest || !parseSemver(latest)) return null;
|
|
197
198
|
return latest;
|
|
198
199
|
}
|
|
199
|
-
|
|
200
|
-
const url = `https://registry.npmjs.org/${encodeURIComponent(pkg)}/${encodeURIComponent(distTag)}`;
|
|
201
|
-
const res = await fetch(url, { headers: { accept: "application/json" } });
|
|
202
|
-
if (!res.ok) return null;
|
|
203
|
-
const manifest = (await res.json()) as { version: string };
|
|
204
|
-
return manifest.version ?? null;
|
|
200
|
+
return getDistTagVersion(pkg, distTag, "https://registry.npmjs.org");
|
|
205
201
|
} catch {
|
|
206
202
|
return null;
|
|
207
203
|
}
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
* bytes across restarts).
|
|
40
40
|
*/
|
|
41
41
|
|
|
42
|
+
import { createCache } from "@teamscala/cache/create-cache";
|
|
42
43
|
import { readClientBuildStatus } from "./build-client-assets.ts";
|
|
43
44
|
import { join } from "node:path";
|
|
44
45
|
|
|
@@ -70,7 +71,15 @@ const PAGE_DOCUMENT = `<!doctype html>
|
|
|
70
71
|
</body>
|
|
71
72
|
</html>`;
|
|
72
73
|
|
|
73
|
-
|
|
74
|
+
interface CachedHtml {
|
|
75
|
+
html: string;
|
|
76
|
+
manifestMtime: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const htmlCache = createCache<CachedHtml>("service-boot:html-template", {
|
|
80
|
+
ttlMs: Number.POSITIVE_INFINITY,
|
|
81
|
+
maxSize: 1,
|
|
82
|
+
});
|
|
74
83
|
|
|
75
84
|
function manifestPath(): string {
|
|
76
85
|
return join(process.cwd(), "dist", "client", "asset-manifest.json");
|
|
@@ -98,8 +107,9 @@ async function loadManifest(): Promise<AssetManifest> {
|
|
|
98
107
|
}
|
|
99
108
|
|
|
100
109
|
export async function readHtmlTemplate(): Promise<string> {
|
|
101
|
-
const
|
|
102
|
-
|
|
110
|
+
const manifestMtime = await manifestMtimeMs();
|
|
111
|
+
const cached = htmlCache.get("default");
|
|
112
|
+
if (cached && cached.manifestMtime === manifestMtime) return cached.html;
|
|
103
113
|
const manifest = await loadManifest();
|
|
104
114
|
let html = PAGE_DOCUMENT
|
|
105
115
|
.replace(/\{\{stylesPath\}\}/g, manifest.stylesPath)
|
|
@@ -116,6 +126,6 @@ export async function readHtmlTemplate(): Promise<string> {
|
|
|
116
126
|
? html.replace("</head>", `${meta}</head>`)
|
|
117
127
|
: `${meta}${html}`;
|
|
118
128
|
}
|
|
119
|
-
|
|
129
|
+
htmlCache.set("default", { html, manifestMtime });
|
|
120
130
|
return html;
|
|
121
131
|
}
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Polls the installed scala-tools package against GitHub Packages and repairs
|
|
7
7
|
* stale daemon tooling by reinstalling and letting PM2 restart the process.
|
|
8
|
+
* The fetch routes through `getGithubPackagesLatestVersion` so the URL
|
|
9
|
+
* encoding and GitHub-Packages auth header live in the typed primitive.
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
12
|
import { recordEvent } from "@teamscala/event-log/record-event";
|
|
@@ -14,6 +16,7 @@ import { createWatchdog } from "@teamscala/watchdog/create-watchdog";
|
|
|
14
16
|
import type { WatchdogHandle } from "@teamscala/watchdog/types";
|
|
15
17
|
import { loadRegistryConfig } from "@teamscala/db/registry/load-config";
|
|
16
18
|
import { decryptSecretConfig } from "@teamscala/encryption/crypto/secret-config";
|
|
19
|
+
import { getGithubPackagesLatestVersion } from "@teamscala/publish-registry";
|
|
17
20
|
import * as v from "valibot";
|
|
18
21
|
|
|
19
22
|
const PACKAGE_NAME = "@teamscala/scala-tools";
|
|
@@ -55,10 +58,6 @@ interface PackageManifest {
|
|
|
55
58
|
version?: unknown;
|
|
56
59
|
}
|
|
57
60
|
|
|
58
|
-
interface PackagePackument {
|
|
59
|
-
"dist-tags"?: { latest?: unknown };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
61
|
let watchdog: WatchdogHandle | null = null;
|
|
63
62
|
|
|
64
63
|
async function readInstalledVersion(cwd: string): Promise<string | null> {
|
|
@@ -70,24 +69,25 @@ async function readInstalledVersion(cwd: string): Promise<string | null> {
|
|
|
70
69
|
}
|
|
71
70
|
}
|
|
72
71
|
|
|
73
|
-
|
|
72
|
+
/**
|
|
73
|
+
* PACKAGE_URL is `<registry>/<encoded-name>`. Split it so the typed primitive
|
|
74
|
+
* owns the URL encoding (encodeURIComponent) and the GitHub-Packages auth
|
|
75
|
+
* header shape — neither is this file's concern.
|
|
76
|
+
*/
|
|
77
|
+
function registryFromPackageUrl(pkgUrl: string): { registry: string; name: string } {
|
|
78
|
+
const lastSlash = pkgUrl.lastIndexOf("/");
|
|
79
|
+
if (lastSlash < 0) return { registry: pkgUrl, name: "" };
|
|
80
|
+
return {
|
|
81
|
+
registry: pkgUrl.slice(0, lastSlash),
|
|
82
|
+
name: decodeURIComponent(pkgUrl.slice(lastSlash + 1)),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function readLatestVersion(_fetchTimeoutMs: number): Promise<string | null> {
|
|
74
87
|
const token = await getGithubPackagesToken();
|
|
75
88
|
if (!token) return null;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
headers: {
|
|
79
|
-
accept: "application/vnd.github+json",
|
|
80
|
-
authorization: `Bearer ${token}`,
|
|
81
|
-
},
|
|
82
|
-
signal: AbortSignal.timeout(fetchTimeoutMs),
|
|
83
|
-
});
|
|
84
|
-
if (!response.ok) return null;
|
|
85
|
-
const packument = (await response.json()) as PackagePackument;
|
|
86
|
-
const latest = packument["dist-tags"]?.latest;
|
|
87
|
-
return typeof latest === "string" ? latest : null;
|
|
88
|
-
} catch {
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
89
|
+
const { registry, name } = registryFromPackageUrl(PACKAGE_URL);
|
|
90
|
+
return getGithubPackagesLatestVersion(name, token, registry);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
async function checkCurrency(): Promise<{ healthy: boolean; details?: Record<string, unknown> }> {
|