@teamscala/service-boot 0.23.388 → 0.23.389
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
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system service-boot
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* Locks the mount candidate-chain regression (2026-08-24): scala-agents-ui
|
|
7
|
+
* crash-looped at boot because @teamscala/cli-session exports ./boot/mount.ts
|
|
8
|
+
* and not ./mount.ts. The chain already listed boot/mount.ts as a fallback,
|
|
9
|
+
* but Bun names an unexported subpath by its BARE package, so the first
|
|
10
|
+
* candidate's miss was classified as a real error and boot died before the
|
|
11
|
+
* fallback ran. The whole value of the chain is that candidate 1 may be absent.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { describe, expect, it } from "bun:test";
|
|
15
|
+
import { barePackageOf, isModuleNotFound } from "./module-not-found";
|
|
16
|
+
|
|
17
|
+
describe("barePackageOf", () => {
|
|
18
|
+
it("keeps the scope for scoped packages", () => {
|
|
19
|
+
expect(barePackageOf("@teamscala/cli-session/mount.ts")).toBe(
|
|
20
|
+
"@teamscala/cli-session",
|
|
21
|
+
);
|
|
22
|
+
expect(barePackageOf("@teamscala/cli-session")).toBe(
|
|
23
|
+
"@teamscala/cli-session",
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("takes the first segment for unscoped packages", () => {
|
|
28
|
+
expect(barePackageOf("hono/mount.ts")).toBe("hono");
|
|
29
|
+
expect(barePackageOf("hono")).toBe("hono");
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("isModuleNotFound", () => {
|
|
34
|
+
// The exact shape Bun emits for an unexported subpath: it names the
|
|
35
|
+
// PACKAGE, never the subpath that was asked for.
|
|
36
|
+
const bunSubpathMiss = {
|
|
37
|
+
message:
|
|
38
|
+
"Cannot find package '@teamscala/cli-session' imported from /srv/node_modules/@teamscala/service-boot/src/mount-server-packages.ts",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
it("treats an unexported subpath as not-found so the chain falls through", () => {
|
|
42
|
+
expect(
|
|
43
|
+
isModuleNotFound(bunSubpathMiss, "@teamscala/cli-session/mount.ts"),
|
|
44
|
+
).toBe(true);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("still matches when the error names the full specifier", () => {
|
|
48
|
+
expect(
|
|
49
|
+
isModuleNotFound(
|
|
50
|
+
{ message: "Cannot find module '@teamscala/x/mount.ts'" },
|
|
51
|
+
"@teamscala/x/mount.ts",
|
|
52
|
+
),
|
|
53
|
+
).toBe(true);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("does NOT swallow a transitive miss of a DIFFERENT module", () => {
|
|
57
|
+
// A file inside the candidate failing to resolve something else must
|
|
58
|
+
// surface as a real error, or the true cause hides behind "not found".
|
|
59
|
+
expect(
|
|
60
|
+
isModuleNotFound(
|
|
61
|
+
{ message: "Cannot find package '@teamscala/some-other-dep'" },
|
|
62
|
+
"@teamscala/cli-session/mount.ts",
|
|
63
|
+
),
|
|
64
|
+
).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("does NOT swallow a syntax or runtime error", () => {
|
|
68
|
+
expect(
|
|
69
|
+
isModuleNotFound(
|
|
70
|
+
new SyntaxError("Unexpected token"),
|
|
71
|
+
"@teamscala/cli-session/mount.ts",
|
|
72
|
+
),
|
|
73
|
+
).toBe(false);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("reads non-Error throwables defensively (Bun ResolveMessage is not an Error)", () => {
|
|
77
|
+
expect(
|
|
78
|
+
isModuleNotFound(
|
|
79
|
+
"Cannot find package '@teamscala/cli-session'",
|
|
80
|
+
"@teamscala/cli-session/mount.ts",
|
|
81
|
+
),
|
|
82
|
+
).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @system service-boot
|
|
3
|
+
* @status handwritten
|
|
4
|
+
* @edit edit directly
|
|
5
|
+
*
|
|
6
|
+
* Classifies a failed dynamic import as "this candidate is absent" vs a real
|
|
7
|
+
* error, for the server_package mount candidate chain. Extracted from
|
|
8
|
+
* mount-server-packages so the classification is testable: as a closure it
|
|
9
|
+
* could not be exercised, and it shipped wrong.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** `@scope/name/sub/path.ts` -> `@scope/name`; `name/sub` -> `name`. */
|
|
13
|
+
export function barePackageOf(specifier: string): string {
|
|
14
|
+
const parts = specifier.split("/");
|
|
15
|
+
return specifier.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* True when the import failed because `requestedPath` itself is absent.
|
|
20
|
+
*
|
|
21
|
+
* Bun reports an unexported SUBPATH as `Cannot find package '<bare>'`, naming
|
|
22
|
+
* the package rather than the subpath, so the bare name of THIS candidate
|
|
23
|
+
* counts as not-found. Without that the first candidate's miss is
|
|
24
|
+
* misclassified as fatal and the fallbacks never run (measured 2026-08-24:
|
|
25
|
+
* scala-agents-ui crash-looped because cli-session exports ./boot/mount.ts and
|
|
26
|
+
* not ./mount.ts). A transitive miss naming a DIFFERENT module still surfaces.
|
|
27
|
+
*/
|
|
28
|
+
export function isModuleNotFound(err: unknown, requestedPath: string): boolean {
|
|
29
|
+
const msg =
|
|
30
|
+
(err as { message?: string })?.message ??
|
|
31
|
+
(typeof err === "string" ? err : String(err));
|
|
32
|
+
const bare = barePackageOf(requestedPath);
|
|
33
|
+
return (
|
|
34
|
+
msg.includes(`Cannot find module '${requestedPath}'`) ||
|
|
35
|
+
msg.includes(`Cannot find package '${requestedPath}'`) ||
|
|
36
|
+
msg.includes(`Cannot find module '${bare}'`) ||
|
|
37
|
+
msg.includes(`Cannot find package '${bare}'`)
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
type MountRegistry,
|
|
21
21
|
} from "@teamscala/os/runtime-contracts/mount-context";
|
|
22
22
|
import type { Hono } from "hono";
|
|
23
|
+
import { isModuleNotFound } from "./module-not-found";
|
|
23
24
|
|
|
24
25
|
type ServerPackageMount = (ctx: MountContext) => Promise<void> | void;
|
|
25
26
|
|
|
@@ -68,19 +69,7 @@ export async function mountServerPackages(
|
|
|
68
69
|
return mountRegistry;
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
// Bun's ResolveMessage is not an Error subclass — read message defensively.
|
|
73
|
-
// Match ONLY when the missing module is the candidate path itself; transitive
|
|
74
|
-
// not-found errors (e.g. a file inside the candidate fails to resolve a
|
|
75
|
-
// different module) must surface, not be swallowed as "not found here".
|
|
76
|
-
const msg =
|
|
77
|
-
(err as { message?: string })?.message ??
|
|
78
|
-
(typeof err === "string" ? err : String(err));
|
|
79
|
-
return (
|
|
80
|
-
msg.includes(`Cannot find module '${requestedPath}'`) ||
|
|
81
|
-
msg.includes(`Cannot find package '${requestedPath}'`)
|
|
82
|
-
);
|
|
83
|
-
};
|
|
72
|
+
|
|
84
73
|
|
|
85
74
|
for (const pkg of serverPackages) {
|
|
86
75
|
try {
|