@teamscala/service-boot 0.23.284
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 +1016 -0
- package/src/agreement-gate.test.ts +118 -0
- package/src/agreement-gate.ts +887 -0
- package/src/asset-cache-key.ts +59 -0
- package/src/asset-optimize.ts +93 -0
- package/src/asset-proxy.ts +148 -0
- package/src/asset-social-card.ts +58 -0
- package/src/attach-agreement-magic.ts +62 -0
- package/src/attach-portal-members.ts +107 -0
- package/src/attach-proposal-accept.ts +123 -0
- package/src/attach-proposal-magic.ts +55 -0
- package/src/attach-transfers-upload.ts +100 -0
- package/src/auth-gate.ts +385 -0
- package/src/bind-page-handler.ts +223 -0
- package/src/build-client-assets/input-hashing.ts +206 -0
- package/src/build-client-assets/source-candidates.ts +115 -0
- package/src/build-client-assets.test.ts +216 -0
- package/src/build-client-assets.ts +962 -0
- package/src/compose-page-title.test.ts +88 -0
- package/src/compose-page-title.ts +73 -0
- package/src/content-gate/access.ts +89 -0
- package/src/content-gate/config-types.ts +108 -0
- package/src/content-gate/gate.ts +74 -0
- package/src/content-gate/parse-cookie.ts +23 -0
- package/src/content-gate/token.test.ts +70 -0
- package/src/content-gate/token.ts +148 -0
- package/src/content-page-floor.test.ts +127 -0
- package/src/content-page-floor.ts +70 -0
- package/src/discover-blocks.ts +228 -0
- package/src/expand-roster.ts +197 -0
- package/src/fn-routers/agreement-completion-email.ts +115 -0
- package/src/fn-routers/agreement-send-router.ts +303 -0
- package/src/fn-routers/ai-health-router.ts +64 -0
- package/src/fn-routers/auth-router.ts +131 -0
- package/src/fn-routers/cache-admin-router.ts +50 -0
- package/src/fn-routers/chat-session-router.ts +455 -0
- package/src/fn-routers/email-defaults.ts +22 -0
- package/src/fn-routers/email-link-ttl.ts +23 -0
- package/src/fn-routers/index.ts +70 -0
- package/src/fn-routers/panel-data-router.ts +93 -0
- package/src/fn-routers/persist-chat-messages.test.ts +86 -0
- package/src/fn-routers/persist-chat-messages.ts +96 -0
- package/src/fn-routers/plans-router.ts +141 -0
- package/src/fn-routers/proposal-send-router.ts +207 -0
- package/src/fn-routers/public-agreement-router.ts +865 -0
- package/src/fn-routers/public-assessment-router.ts +421 -0
- package/src/fn-routers/public-gpt-session-router.ts +373 -0
- package/src/fn-routers/sign-in-flow-router.ts +196 -0
- package/src/fn-routers/task-sync-bind.test.ts +105 -0
- package/src/fn-routers/task-sync-classify.test.ts +109 -0
- package/src/fn-routers/task-sync-dispatch.test.ts +44 -0
- package/src/fn-routers/task-sync-dispatch.ts +124 -0
- package/src/fn-routers/task-sync-router.test.ts +279 -0
- package/src/fn-routers/task-sync-router.ts +1036 -0
- package/src/fn-routers/tasks-router.ts +77 -0
- package/src/fn-routers/user-preferences-router.ts +139 -0
- package/src/fn-routers/work-items-router.ts +341 -0
- package/src/gate-ttls.ts +56 -0
- package/src/gpt-gate.ts +76 -0
- package/src/handlers/bun-stable-watch.ts +89 -0
- package/src/handlers/projects-digest.ts +198 -0
- package/src/handlers/prune-stale-data.ts +110 -0
- package/src/handlers/registry-stale-inactive-sweep.ts +82 -0
- package/src/handlers/scala-tools-currency-watch.test.ts +60 -0
- package/src/handlers/scala-tools-currency-watch.ts +171 -0
- package/src/handlers/scheduled-browser-tests.ts +85 -0
- package/src/handlers/sweep-event-log.ts +98 -0
- package/src/handlers/sweep-host-cache.ts +211 -0
- package/src/handlers/upstream-issue-sync.ts +210 -0
- package/src/handlers/whatsapp-liveness-handler.test.ts +59 -0
- package/src/handlers/whatsapp-liveness-logic.ts +53 -0
- package/src/handlers/whatsapp-liveness.test.ts +78 -0
- package/src/handlers/whatsapp-liveness.ts +125 -0
- package/src/hono-error-handler.ts +55 -0
- package/src/install-harness.ts +80 -0
- package/src/install-logging.ts +182 -0
- package/src/load-service-install-row.ts +98 -0
- package/src/load-service-prisma-client.ts +53 -0
- package/src/mount-orpc-router.ts +148 -0
- package/src/mount-server-packages.ts +155 -0
- package/src/mount.ts +18 -0
- package/src/mutable-app-ref.ts +68 -0
- package/src/page-route-shell-types.ts +16 -0
- package/src/page-server-orpc/audit-revert-router.ts +102 -0
- package/src/page-server-orpc/hub-mcp-client.ts +20 -0
- package/src/page-server-orpc/portal-diagnostics-router.ts +170 -0
- package/src/page-server-orpc/puck-pages-router.ts +272 -0
- package/src/page-server-orpc/server-client.ts +31 -0
- package/src/page-server-orpc/templates-router.ts +116 -0
- package/src/page-server-orpc/user-preferences-router.ts +132 -0
- package/src/pg-boss-handler-registry.ts +36 -0
- package/src/pg-boss-mount.ts +295 -0
- package/src/proposal-authorized-emails.ts +65 -0
- package/src/proposal-chat-inject.ts +141 -0
- package/src/proposal-gate.ts +175 -0
- package/src/read-html-template.test.ts +74 -0
- package/src/read-html-template.ts +102 -0
- package/src/read-viewport-from-request.ts +36 -0
- package/src/readiness-gate.ts +39 -0
- package/src/register-api-routes.ts +127 -0
- package/src/register-crash-handlers.ts +39 -0
- package/src/register-page-routes.ts +62 -0
- package/src/register-page-server-client.ts +109 -0
- package/src/register-registry-page-route/ai-chat-prefetch.ts +106 -0
- package/src/register-registry-page-route/graph-cache.test.ts +84 -0
- package/src/register-registry-page-route/graph-cache.ts +90 -0
- package/src/register-registry-page-route/meta-title.ts +73 -0
- package/src/register-registry-page-route/proposal-approval-email-token.ts +78 -0
- package/src/register-registry-page-route/proposal-brand-stamping.ts +60 -0
- package/src/register-registry-page-route/resolve-page-data.ts +518 -0
- package/src/register-registry-page-route.ts +765 -0
- package/src/register-static-assets.ts +180 -0
- package/src/register-ws-routes.ts +180 -0
- package/src/resolve-app-base-url.ts +28 -0
- package/src/resolve-brand-favicon.ts +65 -0
- package/src/resolve-from-loader-config.test.ts +44 -0
- package/src/resolve-from-loader-config.ts +291 -0
- package/src/resolve-service-context.ts +67 -0
- package/src/resolve-theme-mode.ts +61 -0
- package/src/resolvers/fetch-org-brand.ts +28 -0
- package/src/resolvers/portal-route-paths.test.ts +102 -0
- package/src/resolvers/portal-route-paths.ts +83 -0
- package/src/resolvers/website-by-host-map.ts +49 -0
- package/src/resource-chat-inject.ts +168 -0
- package/src/resource-not-found.ts +263 -0
- package/src/run-boot-hook-list.ts +75 -0
- package/src/run-boot-hooks.ts +97 -0
- package/src/scala-tools-currency-watchdog.ts +160 -0
- package/src/security-headers.ts +59 -0
- package/src/serve/serve-ws.test.ts +135 -0
- package/src/serve/serve.ts +190 -0
- package/src/server-error-page.test.ts +102 -0
- package/src/server-error-page.ts +95 -0
- package/src/start-scheduled-jobs.ts +84 -0
- package/src/two-phase-boot.ts +449 -0
- package/src/types.ts +52 -0
- package/src/ui-service.ts +171 -0
- package/src/ws-boot-self-test.ts +114 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system server-handlers
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* Regression guard for stripRemoteImports. Third-party package CSS must not
|
|
7
|
+
* inject a remote @import (the dropped/CSP-blocked Inter-font class, scala-docs
|
|
8
|
+
* 2026-06-13): @puckeditor/core/dist/index.css leads with `@import
|
|
9
|
+
* "https://rsms.me/inter/inter.css"`, a CDN our CSP forbids. The compiled
|
|
10
|
+
* bundle must carry zero remote @imports; the app loads its font from the
|
|
11
|
+
* CSP-allowlisted Google Fonts <link> in index.html instead.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
import { dirname, join } from "node:path";
|
|
17
|
+
import { afterAll, describe, expect, test } from "bun:test";
|
|
18
|
+
import {
|
|
19
|
+
computeInputFingerprint,
|
|
20
|
+
stripRemoteImports,
|
|
21
|
+
} from "./build-client-assets.ts";
|
|
22
|
+
|
|
23
|
+
describe("stripRemoteImports", () => {
|
|
24
|
+
test("removes a remote @import (Puck's rsms.me font)", () => {
|
|
25
|
+
const css = `.a { color: red; }\n@import "https://rsms.me/inter/inter.css";\n:root { --x: 1; }`;
|
|
26
|
+
const out = stripRemoteImports(css);
|
|
27
|
+
expect(out).not.toContain("@import");
|
|
28
|
+
expect(out).not.toContain("rsms.me");
|
|
29
|
+
expect(out).toContain(".a { color: red; }");
|
|
30
|
+
expect(out).toContain("--x: 1;");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("removes the url() form with a media suffix", () => {
|
|
34
|
+
const css = `.a{}\n@import url("https://fonts.example/f.css") screen;\n.b{}`;
|
|
35
|
+
const out = stripRemoteImports(css);
|
|
36
|
+
expect(out).not.toContain("@import");
|
|
37
|
+
expect(out).toContain(".a{}");
|
|
38
|
+
expect(out).toContain(".b{}");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("removes multiple remote @imports", () => {
|
|
42
|
+
const css = `@import "https://a.test/x.css";\n.a{}\n@import "https://b.test/y.css";\n.b{}`;
|
|
43
|
+
expect(stripRemoteImports(css)).not.toContain("@import");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("leaves local/relative @imports untouched (Tailwind inlines those)", () => {
|
|
47
|
+
const css = `@import "./local.css";\n.a{}`;
|
|
48
|
+
expect(stripRemoteImports(css)).toBe(css);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("no remote @import → unchanged", () => {
|
|
52
|
+
const css = `:root{--x:1}\n.a{color:red}`;
|
|
53
|
+
expect(stripRemoteImports(css)).toBe(css);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// The client-bundle freshness check keys on input CONTENT, never mtime
|
|
58
|
+
// (cache-key-hashes-source-inputs-only). mtime comparison silently served a
|
|
59
|
+
// stale bundle on Railway (deploy-build bakes at image-build time; hoisted
|
|
60
|
+
// hardlinks carry old mtimes; baked manifest is newer → wrongly skips), so a
|
|
61
|
+
// published+installed @teamscala/* fix never reached the browser (2026-06-14).
|
|
62
|
+
// These tests prove the fingerprint changes iff an input's CONTENT changes.
|
|
63
|
+
describe("computeInputFingerprint", () => {
|
|
64
|
+
const dirs: string[] = [];
|
|
65
|
+
async function makeService(files: Record<string, string>): Promise<string> {
|
|
66
|
+
const dir = await mkdtemp(join(tmpdir(), "bca-fp-"));
|
|
67
|
+
dirs.push(dir);
|
|
68
|
+
await mkdir(join(dir, "src"), { recursive: true });
|
|
69
|
+
for (const [rel, content] of Object.entries(files)) {
|
|
70
|
+
await mkdir(dirname(join(dir, rel)), { recursive: true });
|
|
71
|
+
await writeFile(join(dir, rel), content);
|
|
72
|
+
}
|
|
73
|
+
return dir;
|
|
74
|
+
}
|
|
75
|
+
afterAll(async () => {
|
|
76
|
+
for (const d of dirs) await rm(d, { recursive: true, force: true });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("identical inputs → identical fingerprint (skip path)", async () => {
|
|
80
|
+
const files = {
|
|
81
|
+
"bun.lock": `{ "ui-foundation": "99.100.86" }`,
|
|
82
|
+
"src/entry-client.ts": "export {};",
|
|
83
|
+
"src/page-manifest.ts": "export const M = {};",
|
|
84
|
+
"src/app.css": ".a{}",
|
|
85
|
+
};
|
|
86
|
+
const a = await makeService(files);
|
|
87
|
+
const b = await makeService(files);
|
|
88
|
+
expect(await computeInputFingerprint(a)).toBe(
|
|
89
|
+
await computeInputFingerprint(b),
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// `no-uncontrolled-repetition-or-cascade`: bun.lock is NOT an input. A
|
|
94
|
+
// version bump of a package NOT in this service's client bundle (here
|
|
95
|
+
// ui-foundation is named in bun.lock but never imported and not installed)
|
|
96
|
+
// MUST NOT invalidate — otherwise every backend @teamscala/* publish rebuilds
|
|
97
|
+
// every UI service (the publish→rebuild storm). The closure content hashes
|
|
98
|
+
// are unchanged, so the fingerprint is identical → skip → storm cannot form.
|
|
99
|
+
test("a bun.lock change for a NON-closure package → SAME fingerprint (no false rebuild)", async () => {
|
|
100
|
+
const base = {
|
|
101
|
+
"src/entry-client.ts": "export {};",
|
|
102
|
+
"src/page-manifest.ts": "export const M = {};",
|
|
103
|
+
"src/app.css": ".a{}",
|
|
104
|
+
};
|
|
105
|
+
const oldD = await makeService({
|
|
106
|
+
...base,
|
|
107
|
+
"bun.lock": `{ "ui-foundation": "99.100.85" }`,
|
|
108
|
+
});
|
|
109
|
+
const newD = await makeService({
|
|
110
|
+
...base,
|
|
111
|
+
"bun.lock": `{ "ui-foundation": "99.100.86" }`,
|
|
112
|
+
});
|
|
113
|
+
expect(await computeInputFingerprint(oldD)).toBe(
|
|
114
|
+
await computeInputFingerprint(newD),
|
|
115
|
+
);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// The closure-scoped third-party boundary versions: a version bump of a
|
|
119
|
+
// third-party package that IS in the client closure still invalidates — the
|
|
120
|
+
// correctness property dropping bun.lock must not regress. The canonical case
|
|
121
|
+
// is react; a react version bump in the bundle MUST rebuild.
|
|
122
|
+
test("a third-party closure-dep version bump → different fingerprint (rebuild)", async () => {
|
|
123
|
+
const makeWith = (version: string) => ({
|
|
124
|
+
"src/entry-client.ts": `import "fake-react";\n`,
|
|
125
|
+
"src/page-manifest.ts": "export const M = {};\n",
|
|
126
|
+
"src/app.css": ".a{}",
|
|
127
|
+
"node_modules/fake-react/package.json":
|
|
128
|
+
`{"name":"fake-react","version":"${version}","exports":{".":"./index.js"}}\n`,
|
|
129
|
+
"node_modules/fake-react/index.js": `export const R = "${version}";\n`,
|
|
130
|
+
});
|
|
131
|
+
const before = await makeService(makeWith("1.0.0"));
|
|
132
|
+
const after = await makeService(makeWith("1.0.1"));
|
|
133
|
+
expect(await computeInputFingerprint(before)).not.toBe(
|
|
134
|
+
await computeInputFingerprint(after),
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test("a regenerated page-manifest → different fingerprint", async () => {
|
|
139
|
+
const base = {
|
|
140
|
+
"bun.lock": `{ "x": "1" }`,
|
|
141
|
+
"src/entry-client.ts": "export {};",
|
|
142
|
+
"src/app.css": ".a{}",
|
|
143
|
+
};
|
|
144
|
+
const before = await makeService({
|
|
145
|
+
...base,
|
|
146
|
+
"src/page-manifest.ts": "export const M = { Hero: 1 };",
|
|
147
|
+
});
|
|
148
|
+
const after = await makeService({
|
|
149
|
+
...base,
|
|
150
|
+
"src/page-manifest.ts": "export const M = { Hero: 1, Cta: 2 };",
|
|
151
|
+
});
|
|
152
|
+
expect(await computeInputFingerprint(before)).not.toBe(
|
|
153
|
+
await computeInputFingerprint(after),
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("a missing input (backend service, no app.css) is stable, not a throw", async () => {
|
|
158
|
+
const dir = await makeService({
|
|
159
|
+
"bun.lock": `{ "x": "1" }`,
|
|
160
|
+
"src/entry-client.ts": "export {};",
|
|
161
|
+
"src/page-manifest.ts": "export const M = {};",
|
|
162
|
+
});
|
|
163
|
+
const fp1 = await computeInputFingerprint(dir);
|
|
164
|
+
const fp2 = await computeInputFingerprint(dir);
|
|
165
|
+
expect(fp1).toBe(fp2);
|
|
166
|
+
expect(fp1).toMatch(/^[a-f0-9]{16}$/);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Regression guard for the 2026-07-12 scala-agents stale-bundle bug. A boot
|
|
170
|
+
// that builds MID-INSTALL (package-poller restarts after writing the new
|
|
171
|
+
// bun.lock but before node_modules extraction finishes) bakes OLD @teamscala/*
|
|
172
|
+
// JS while the lock-derived fingerprint records "current" — so every later
|
|
173
|
+
// restart recomputes the same fingerprint, skips the rebuild, and serves the
|
|
174
|
+
// STALE bundle forever. Bun.build itself never caches stale modules (an
|
|
175
|
+
// isolated splitting+minify repro proved it reads fresh content across
|
|
176
|
+
// processes), so the defense is to hash the actual @teamgbg JS CONTENT the
|
|
177
|
+
// bundle is built from (the JS twin of hashResolvedPackageStylesheets). With
|
|
178
|
+
// that in the fingerprint, the freshly-installed bytes hash differently and
|
|
179
|
+
// the bundle self-heals on the next boot. (bun.lock + entry files are
|
|
180
|
+
// IDENTICAL across these two fixtures — only the @teamgbg JS body changes.)
|
|
181
|
+
test("a @teamgbg JS module content change → different fingerprint (mid-install stale-bake self-heal)", async () => {
|
|
182
|
+
const makeWith = (pkgJs: string) => ({
|
|
183
|
+
"bun.lock": `{ "locks": { "@teamscala/repro-pkg": "1.0.0" } }`,
|
|
184
|
+
"src/entry-client.ts": `import { M } from "@teamscala/repro-pkg";\nconsole.log(M);\n`,
|
|
185
|
+
"src/page-manifest.ts": "export const M = {};\n",
|
|
186
|
+
"src/app.css": ".a{}",
|
|
187
|
+
"node_modules/@teamscala/repro-pkg/package.json":
|
|
188
|
+
`{"name":"@teamscala/repro-pkg","version":"1.0.0","exports":{".":"./index.ts"}}\n`,
|
|
189
|
+
"node_modules/@teamscala/repro-pkg/index.ts": pkgJs,
|
|
190
|
+
});
|
|
191
|
+
const before = await makeService(makeWith("export const M = 'AAA_REPRO';\n"));
|
|
192
|
+
const after = await makeService(makeWith("export const M = 'BBB_REPRO';\n"));
|
|
193
|
+
expect(await computeInputFingerprint(before)).not.toBe(
|
|
194
|
+
await computeInputFingerprint(after),
|
|
195
|
+
);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// The JS graph walk runs bounded-concurrent; the aggregate must be stable
|
|
199
|
+
// across runs (independent of async scheduling) — the per-file hashes are
|
|
200
|
+
// folded in sorted path order.
|
|
201
|
+
test("@teamgbg JS graph fingerprint is deterministic across runs", async () => {
|
|
202
|
+
const dir = await makeService({
|
|
203
|
+
"bun.lock": `{ "locks": { "@teamscala/repro-pkg": "1.0.0" } }`,
|
|
204
|
+
"src/entry-client.ts": `import { M } from "@teamscala/repro-pkg";\nconsole.log(M);\n`,
|
|
205
|
+
"src/page-manifest.ts": "export const M = {};\n",
|
|
206
|
+
"src/app.css": ".a{}",
|
|
207
|
+
"node_modules/@teamscala/repro-pkg/package.json":
|
|
208
|
+
`{"name":"@teamscala/repro-pkg","version":"1.0.0","exports":{".":"./index.ts"}}\n`,
|
|
209
|
+
"node_modules/@teamscala/repro-pkg/index.ts": "export const M = 'STABLE';\n",
|
|
210
|
+
});
|
|
211
|
+
const a = await computeInputFingerprint(dir);
|
|
212
|
+
const b = await computeInputFingerprint(dir);
|
|
213
|
+
expect(a).toBe(b);
|
|
214
|
+
expect(a).toMatch(/^[a-f0-9]{16}$/);
|
|
215
|
+
});
|
|
216
|
+
});
|