@jskit-ai/kernel 0.1.245 → 0.1.246
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/client/appConfig.js +2 -1
- package/client/asyncModuleRecovery.js +2 -4
- package/client/index.d.ts +13 -75
- package/client/index.js +1 -1
- package/client/logging.js +8 -2
- package/client/moduleBootstrap.js +41 -33
- package/client/moduleBootstrap.test.js +35 -0
- package/client/shellBootstrap.js +1 -8
- package/client/shellBootstrap.test.js +32 -5
- package/client/shellRouting.js +16 -61
- package/client/shellRouting.test.js +30 -1
- package/client/vite/clientBootstrapPlugin.js +0 -6
- package/client/vite/clientBootstrapPlugin.test.js +2 -3
- package/internal/node/installedPackages.js +14 -58
- package/package.json +2 -4
- package/server/actions/actionCatalogue.js +13 -34
- package/{shared → server}/actions/actionContributorHelpers.js +0 -5
- package/{shared → server}/actions/actionDefinitions.js +24 -131
- package/{shared → server}/actions/actionDefinitions.test.js +75 -21
- package/server/actions/actionEvents.js +97 -2
- package/server/actions/actionEvents.test.js +79 -0
- package/{shared → server}/actions/executionContext.js +3 -13
- package/server/actions/idempotency.js +24 -0
- package/server/actions/pipeline.js +87 -0
- package/{shared → server}/actions/policies.js +20 -33
- package/{shared → server}/actions/policies.test.js +6 -6
- package/{shared → server}/actions/registry.js +3 -22
- package/{shared → server}/actions/registry.test.js +70 -32
- package/server/actions/requestMeta.js +17 -0
- package/server/http/capabilityHttpRuntime.js +1 -1
- package/server/http/capabilityHttpRuntime.test.js +56 -0
- package/server/http/errorHandling.js +291 -0
- package/server/{runtime/fastifyBootstrap.test.js → http/errorHandling.test.js} +72 -120
- package/server/http/lib/routeRegistration.js +36 -150
- package/server/http/lib/routeTransport.js +2 -0
- package/server/http/lib/routeValidator.js +5 -47
- package/server/http/lib/routeValidator.test.js +15 -17
- package/server/http/lib/router.js +10 -94
- package/server/platform/providerRuntime/capabilityProviderLoader.js +2 -1
- package/server/platform/providerRuntime/helpers.js +1 -34
- package/server/runtime/integers.js +1 -5
- package/server/runtime/serviceAuthorization.js +4 -9
- package/server/support/appConfig.js +2 -1
- package/server/support/appConfigFiles.js +1 -38
- package/server/support/appConfigFiles.test.js +3 -39
- package/server/support/index.js +1 -20
- package/shared/capabilities/defineProvider.js +1 -1
- package/shared/capabilities/index.js +1 -1
- package/shared/index.js +1 -1
- package/shared/support/index.js +1 -4
- package/shared/support/mobileConfig.js +99 -0
- package/shared/support/mobileConfig.test.js +113 -0
- package/shared/support/normalize.js +0 -108
- package/shared/support/normalize.test.js +0 -134
- package/shared/{actions/actionContributorHelpers.test.js → support/permissions.test.js} +1 -1
- package/shared/support/returnToPath.js +1 -20
- package/shared/support/returnToPath.test.js +1 -36
- package/shared/support/shellLayoutTargets.js +2 -140
- package/shared/support/shellLayoutTargets.test.js +1 -110
- package/shared/support/sorting.js +1 -9
- package/shared/surface/apiPaths.js +1 -82
- package/shared/surface/paths.js +38 -171
- package/shared/surface/registry.js +2 -1
- package/shared/surface/runtime.js +18 -109
- package/shared/surface/runtime.test.js +57 -0
- package/shared/validators/index.js +1 -4
- package/shared/validators/jsonRestSchemaSupport.js +0 -9
- package/shared/validators/recordIdParamsValidator.js +0 -6
- package/shared/validators/schemaDefinitions.js +4 -30
- package/shared/validators/schemaPayloadValidation.js +2 -6
- package/test/barrelExposure.test.js +3 -7
- package/test/exportsContract.test.js +1 -3
- package/client/mobileLaunchRouting.js +0 -231
- package/client/mobileLaunchRouting.test.js +0 -230
- package/server/http/lib/controller.js +0 -183
- package/server/http/lib/controller.test.js +0 -143
- package/server/http/lib/index.js +0 -5
- package/server/runtime/canonicalJson.js +0 -74
- package/server/runtime/entityChangeEvents.js +0 -307
- package/server/runtime/entityChangeEvents.test.js +0 -274
- package/server/runtime/fastifyBootstrap.js +0 -553
- package/server/runtime/moduleConfig.js +0 -223
- package/server/runtime/moduleConfig.test.js +0 -165
- package/server/runtime/pagination.js +0 -13
- package/server/runtime/realtimeNormalization.js +0 -21
- package/server/runtime/routeUtils.js +0 -20
- package/server/runtime/securityAudit.js +0 -269
- package/server/runtime/securityAudit.test.js +0 -41
- package/server/support/pageTargets.js +0 -772
- package/server/support/pageTargets.test.js +0 -659
- package/server/support/shellOutlets.js +0 -576
- package/server/support/shellOutlets.test.js +0 -336
- package/server/surface/index.js +0 -18
- package/shared/actions/audit.js +0 -7
- package/shared/actions/idempotency.js +0 -62
- package/shared/actions/observability.js +0 -10
- package/shared/actions/pipeline.js +0 -309
- package/shared/actions/requestMeta.js +0 -36
- package/shared/actions/textNormalization.js +0 -3
- package/shared/actions/withActionDefaults.js +0 -34
- /package/{shared → server}/actions/executionContext.test.js +0 -0
package/client/appConfig.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { isRecord
|
|
1
|
+
import { isRecord } from "../shared/support/normalize.js";
|
|
2
|
+
import { normalizeMobileConfig } from "../shared/support/mobileConfig.js";
|
|
2
3
|
|
|
3
4
|
const CLIENT_APP_CONFIG_GLOBAL_KEY = "__JSKIT_CLIENT_APP_CONFIG__";
|
|
4
5
|
const EMPTY_CLIENT_APP_CONFIG = Object.freeze({});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isRecord } from "../shared/support/normalize.js";
|
|
2
|
+
|
|
1
3
|
const DYNAMIC_IMPORT_ERROR_PATTERNS = Object.freeze([
|
|
2
4
|
/ChunkLoadError/iu,
|
|
3
5
|
/Failed to fetch dynamically imported module/iu,
|
|
@@ -7,10 +9,6 @@ const DYNAMIC_IMPORT_ERROR_PATTERNS = Object.freeze([
|
|
|
7
9
|
/Unable to preload CSS/iu
|
|
8
10
|
]);
|
|
9
11
|
|
|
10
|
-
function isRecord(value) {
|
|
11
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
12
|
-
}
|
|
13
|
-
|
|
14
12
|
function errorText(error = null) {
|
|
15
13
|
return String(error?.message || error || "").trim();
|
|
16
14
|
}
|
package/client/index.d.ts
CHANGED
|
@@ -6,85 +6,13 @@ export type ClientLogger = {
|
|
|
6
6
|
isDebugEnabled?: boolean;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
export
|
|
10
|
-
attempt: number;
|
|
11
|
-
error: any;
|
|
12
|
-
label: string;
|
|
13
|
-
message: string;
|
|
14
|
-
retry: (() => any) | null;
|
|
15
|
-
stale: boolean;
|
|
16
|
-
visible: boolean;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export function createAsyncModuleRecoveryState(options?: {
|
|
20
|
-
label?: string;
|
|
21
|
-
message?: string;
|
|
22
|
-
retry?: (() => any) | null;
|
|
23
|
-
}): AsyncModuleRecoveryState;
|
|
24
|
-
|
|
25
|
-
export function isDynamicImportError(error?: any): boolean;
|
|
26
|
-
|
|
27
|
-
export function dynamicImportErrorMessage(error?: any, options?: {
|
|
28
|
-
label?: string;
|
|
29
|
-
stale?: boolean;
|
|
30
|
-
}): string;
|
|
31
|
-
|
|
32
|
-
export function notifyAsyncModuleLoadError(
|
|
33
|
-
state: AsyncModuleRecoveryState,
|
|
34
|
-
error?: any,
|
|
35
|
-
options?: {
|
|
36
|
-
label?: string;
|
|
37
|
-
message?: string;
|
|
38
|
-
retry?: (() => any) | null;
|
|
39
|
-
stale?: boolean;
|
|
40
|
-
}
|
|
41
|
-
): AsyncModuleRecoveryState;
|
|
42
|
-
|
|
43
|
-
export function dismissAsyncModuleRecovery(state: AsyncModuleRecoveryState): boolean;
|
|
44
|
-
|
|
45
|
-
export function guardedReloadApp(options?: {
|
|
46
|
-
browserWindow?: any;
|
|
47
|
-
fetchFn?: ((input: string, init?: Record<string, any>) => Promise<any>) | null;
|
|
48
|
-
state?: AsyncModuleRecoveryState | null;
|
|
49
|
-
label?: string;
|
|
50
|
-
message?: string;
|
|
51
|
-
}): Promise<boolean>;
|
|
9
|
+
export * from "./asyncModuleRecovery";
|
|
52
10
|
|
|
53
|
-
export
|
|
54
|
-
router?: any;
|
|
55
|
-
state: AsyncModuleRecoveryState;
|
|
56
|
-
label?: string;
|
|
57
|
-
onNotify?: (state: AsyncModuleRecoveryState) => void;
|
|
58
|
-
windowObject?: any;
|
|
59
|
-
}): Readonly<{
|
|
60
|
-
dispose: () => void;
|
|
61
|
-
}>;
|
|
11
|
+
export const DEFAULT_GUARD_EVALUATOR_KEY: "__JSKIT_WEB_SHELL_GUARD_EVALUATOR__";
|
|
62
12
|
|
|
63
13
|
export function getClientAppConfig(): Readonly<Record<string, any>>;
|
|
64
14
|
export function resolveMobileConfig(appConfig?: Record<string, any>): Readonly<Record<string, any>>;
|
|
65
15
|
export function resolveClientAssetMode(appConfig?: Record<string, any>): string;
|
|
66
|
-
export function normalizeIncomingAppUrl(
|
|
67
|
-
url?: string,
|
|
68
|
-
mobileConfig?: Record<string, any>,
|
|
69
|
-
options?: {
|
|
70
|
-
currentOrigin?: string;
|
|
71
|
-
allowedHttpOrigins?: string[];
|
|
72
|
-
}
|
|
73
|
-
): string;
|
|
74
|
-
export function registerMobileLaunchRouting(options?: {
|
|
75
|
-
router: any;
|
|
76
|
-
mobileConfig?: Record<string, any>;
|
|
77
|
-
getInitialLaunchUrl?: () => Promise<string> | string;
|
|
78
|
-
subscribeToLaunchUrls?: (handler: (url: string) => void) => (() => void) | void;
|
|
79
|
-
currentOrigin?: string;
|
|
80
|
-
allowedHttpOrigins?: string[];
|
|
81
|
-
logger?: ClientLogger;
|
|
82
|
-
}): Readonly<{
|
|
83
|
-
initialize: () => Promise<string>;
|
|
84
|
-
dispose: () => void;
|
|
85
|
-
applyIncomingUrl: (url?: string, reason?: string) => Promise<string>;
|
|
86
|
-
}>;
|
|
87
|
-
|
|
88
16
|
export function resolveClientBootstrapDebugEnabled(options?: {
|
|
89
17
|
env?: Record<string, any>;
|
|
90
18
|
debugEnabled?: boolean;
|
|
@@ -101,7 +29,15 @@ export function createShellRouter(options?: {
|
|
|
101
29
|
surfaceMode?: string;
|
|
102
30
|
fallbackRoute?: any;
|
|
103
31
|
notFoundComponent?: any;
|
|
104
|
-
guard?:
|
|
32
|
+
guard?:
|
|
33
|
+
| false
|
|
34
|
+
| ((to: any) => any)
|
|
35
|
+
| {
|
|
36
|
+
webRootAllowed?: "yes" | "no";
|
|
37
|
+
guardEvaluatorKey?: string;
|
|
38
|
+
authenticatedPolicy?: string;
|
|
39
|
+
publicPolicy?: string;
|
|
40
|
+
};
|
|
105
41
|
/** Defaults to shellScrollBehavior; pass your own function, or false for none. */
|
|
106
42
|
scrollBehavior?: false | ((to: any, from: any, savedPosition: any) => any);
|
|
107
43
|
}): Readonly<{
|
|
@@ -116,6 +52,7 @@ export function bootstrapClientShellApp(options?: {
|
|
|
116
52
|
appConfig?: Record<string, any>;
|
|
117
53
|
appPlugins?: any[];
|
|
118
54
|
pinia?: any;
|
|
55
|
+
queryClient?: any;
|
|
119
56
|
router: any;
|
|
120
57
|
bootClientModules: (context: any) => Promise<any>;
|
|
121
58
|
surfaceRuntime: any;
|
|
@@ -123,6 +60,7 @@ export function bootstrapClientShellApp(options?: {
|
|
|
123
60
|
env?: Record<string, any>;
|
|
124
61
|
fallbackRoute?: any;
|
|
125
62
|
logger?: ClientLogger;
|
|
63
|
+
/** Its isDebugEnabled decides whether bootstrap builds debug payloads; without it, a debug method means enabled. */
|
|
126
64
|
createBootstrapLogger?: (options: {
|
|
127
65
|
env?: Record<string, any>;
|
|
128
66
|
logger?: ClientLogger;
|
package/client/index.js
CHANGED
|
@@ -8,11 +8,11 @@ export {
|
|
|
8
8
|
isDynamicImportError,
|
|
9
9
|
notifyAsyncModuleLoadError
|
|
10
10
|
} from "./asyncModuleRecovery.js";
|
|
11
|
-
export { normalizeIncomingAppUrl, registerMobileLaunchRouting } from "./mobileLaunchRouting.js";
|
|
12
11
|
export {
|
|
13
12
|
resolveClientBootstrapDebugEnabled,
|
|
14
13
|
createSurfaceShellRouter as createShellRouter,
|
|
15
14
|
shellScrollBehavior,
|
|
16
15
|
bootstrapClientShellApp
|
|
17
16
|
} from "./shellBootstrap.js";
|
|
17
|
+
export { DEFAULT_GUARD_EVALUATOR_KEY } from "./shellRouting.js";
|
|
18
18
|
export { createComponentInteractionEmitter } from "./componentInteraction.js";
|
package/client/logging.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { isRecord } from "../shared/support/normalize.js";
|
|
2
2
|
|
|
3
|
+
// isDebugEnabled tells callers whether debug output goes anywhere, so they
|
|
4
|
+
// can skip building debug payloads. A logger may state it; otherwise it is
|
|
5
|
+
// true exactly when the logger has its own debug method.
|
|
3
6
|
function createStructuredLogger(logger = console) {
|
|
4
7
|
if (isRecord(logger)) {
|
|
8
|
+
const hasDebug = typeof logger.debug === "function";
|
|
5
9
|
return Object.freeze({
|
|
6
10
|
info: typeof logger.info === "function" ? logger.info.bind(logger) : console.info.bind(console),
|
|
7
11
|
warn: typeof logger.warn === "function" ? logger.warn.bind(logger) : console.warn.bind(console),
|
|
8
12
|
error: typeof logger.error === "function" ? logger.error.bind(logger) : console.error.bind(console),
|
|
9
|
-
debug:
|
|
13
|
+
debug: hasDebug ? logger.debug.bind(logger) : () => {},
|
|
14
|
+
isDebugEnabled: typeof logger.isDebugEnabled === "boolean" ? logger.isDebugEnabled : hasDebug
|
|
10
15
|
});
|
|
11
16
|
}
|
|
12
17
|
|
|
@@ -14,7 +19,8 @@ function createStructuredLogger(logger = console) {
|
|
|
14
19
|
info: console.info.bind(console),
|
|
15
20
|
warn: console.warn.bind(console),
|
|
16
21
|
error: console.error.bind(console),
|
|
17
|
-
debug: () => {}
|
|
22
|
+
debug: () => {},
|
|
23
|
+
isDebugEnabled: false
|
|
18
24
|
});
|
|
19
25
|
}
|
|
20
26
|
|
|
@@ -136,16 +136,18 @@ function registerClientModuleRoutes({
|
|
|
136
136
|
surfaceMode
|
|
137
137
|
});
|
|
138
138
|
const log = createStructuredLogger(logger);
|
|
139
|
-
log.
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
139
|
+
if (log.isDebugEnabled) {
|
|
140
|
+
log.debug(
|
|
141
|
+
{
|
|
142
|
+
packageId: normalizedPackageId,
|
|
143
|
+
source,
|
|
144
|
+
surfaceMode: String(surfaceMode || "").trim(),
|
|
145
|
+
declaredRoutes: normalizedRoutes.map(toRouteSnapshot),
|
|
146
|
+
activeRoutes: activeRoutes.map(toRouteSnapshot)
|
|
147
|
+
},
|
|
148
|
+
"Client route registration analysis."
|
|
149
|
+
);
|
|
150
|
+
}
|
|
149
151
|
|
|
150
152
|
const declaredPaths = normalizedRoutes.map((route) => route.path);
|
|
151
153
|
const activePaths = activeRoutes.map((route) => route.path);
|
|
@@ -167,14 +169,16 @@ function registerClientModuleRoutes({
|
|
|
167
169
|
|
|
168
170
|
seenRoutePaths.add(normalizedPath);
|
|
169
171
|
router.addRoute(toVueRouteRecord(route));
|
|
170
|
-
log.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
172
|
+
if (log.isDebugEnabled) {
|
|
173
|
+
log.debug(
|
|
174
|
+
{
|
|
175
|
+
packageId: normalizedPackageId,
|
|
176
|
+
source,
|
|
177
|
+
route: toRouteSnapshot(route)
|
|
178
|
+
},
|
|
179
|
+
"Added client route to router."
|
|
180
|
+
);
|
|
181
|
+
}
|
|
178
182
|
registeredCount += 1;
|
|
179
183
|
}
|
|
180
184
|
|
|
@@ -560,14 +564,16 @@ async function bootClientModules({
|
|
|
560
564
|
},
|
|
561
565
|
"Registered client module routes."
|
|
562
566
|
);
|
|
563
|
-
log.
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
567
|
+
if (log.isDebugEnabled) {
|
|
568
|
+
log.debug(
|
|
569
|
+
{
|
|
570
|
+
packageId,
|
|
571
|
+
source,
|
|
572
|
+
routerRoutes: summarizeRouterRoutes(router)
|
|
573
|
+
},
|
|
574
|
+
"Router route table after client route registration."
|
|
575
|
+
);
|
|
576
|
+
}
|
|
571
577
|
routeResults.push(result);
|
|
572
578
|
return result;
|
|
573
579
|
};
|
|
@@ -599,13 +605,15 @@ async function bootClientModules({
|
|
|
599
605
|
},
|
|
600
606
|
"Booted JSKIT client modules."
|
|
601
607
|
);
|
|
602
|
-
log.
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
608
|
+
if (log.isDebugEnabled) {
|
|
609
|
+
log.debug(
|
|
610
|
+
{
|
|
611
|
+
routerRoutes: summarizeRouterRoutes(router),
|
|
612
|
+
currentPath: typeof window !== "undefined" ? String(window.location?.pathname || "") : ""
|
|
613
|
+
},
|
|
614
|
+
"JSKIT client bootstrap final route table."
|
|
615
|
+
);
|
|
616
|
+
}
|
|
609
617
|
}
|
|
610
618
|
|
|
611
619
|
return Object.freeze({
|
|
@@ -433,3 +433,38 @@ test("bootClientModules throws when packageMetadataClientProviders export is mis
|
|
|
433
433
|
/packageMetadata provider export "MissingProvider" must be a capability provider/
|
|
434
434
|
);
|
|
435
435
|
});
|
|
436
|
+
|
|
437
|
+
test("bootClientModules builds route debug payloads only when the logger's debug is enabled", async () => {
|
|
438
|
+
const surfaceRuntime = createSurfaceRuntimeFixture();
|
|
439
|
+
const clientModules = [
|
|
440
|
+
{
|
|
441
|
+
packageId: "@example/auth",
|
|
442
|
+
packageMetadataUiRoutes: [{ id: "auth.login", path: "/auth/login", scope: "global", autoRegister: false }],
|
|
443
|
+
module: {
|
|
444
|
+
clientRoutes: [{ id: "auth.login", name: "auth-login", path: "/auth/login", scope: "global", component: {} }]
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
];
|
|
448
|
+
|
|
449
|
+
for (const isDebugEnabled of [false, true]) {
|
|
450
|
+
const router = createRouterStub();
|
|
451
|
+
let routeTableReads = 0;
|
|
452
|
+
router.getRoutes = () => {
|
|
453
|
+
routeTableReads += 1;
|
|
454
|
+
return [...router.routes];
|
|
455
|
+
};
|
|
456
|
+
const messages = [];
|
|
457
|
+
await bootClientModules({
|
|
458
|
+
clientModules,
|
|
459
|
+
router,
|
|
460
|
+
surfaceRuntime,
|
|
461
|
+
surfaceMode: "all",
|
|
462
|
+
logger: { info() {}, warn() {}, error() {}, debug: (_payload, message) => messages.push(message), isDebugEnabled }
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
assert.equal(router.routes.length, 1);
|
|
466
|
+
assert.equal(routeTableReads > 0, isDebugEnabled);
|
|
467
|
+
assert.equal(messages.includes("Added client route to router."), isDebugEnabled);
|
|
468
|
+
assert.equal(messages.includes("JSKIT client bootstrap final route table."), isDebugEnabled);
|
|
469
|
+
}
|
|
470
|
+
});
|
package/client/shellBootstrap.js
CHANGED
|
@@ -204,14 +204,7 @@ async function bootstrapClientShellApp({
|
|
|
204
204
|
logger: bootstrapLogger
|
|
205
205
|
});
|
|
206
206
|
|
|
207
|
-
const isDebugEnabled =
|
|
208
|
-
typeof resolvedLogger?.isDebugEnabled === "boolean"
|
|
209
|
-
? resolvedLogger.isDebugEnabled
|
|
210
|
-
: resolveClientBootstrapDebugEnabled({
|
|
211
|
-
env,
|
|
212
|
-
debugEnabled,
|
|
213
|
-
debugEnvKey
|
|
214
|
-
});
|
|
207
|
+
const { isDebugEnabled } = bootstrapLogger;
|
|
215
208
|
if (isDebugEnabled) {
|
|
216
209
|
bootstrapLogger.info(
|
|
217
210
|
{
|
|
@@ -53,11 +53,6 @@ test("createSurfaceShellRouter builds active routes and installs a guard", () =>
|
|
|
53
53
|
surfaceMode: "app",
|
|
54
54
|
notFoundComponent: {},
|
|
55
55
|
guard: {
|
|
56
|
-
surfaceDefinitions: {
|
|
57
|
-
app: { id: "app", pagesRoot: "app", requiresAuth: false },
|
|
58
|
-
admin: { id: "admin", pagesRoot: "admin", requiresAuth: false }
|
|
59
|
-
},
|
|
60
|
-
defaultSurfaceId: "app",
|
|
61
56
|
webRootAllowed: "yes"
|
|
62
57
|
}
|
|
63
58
|
});
|
|
@@ -132,6 +127,38 @@ test("createClientBootstrapLogger enables debug from env flag", () => {
|
|
|
132
127
|
assert.equal(logger.isDebugEnabled, true);
|
|
133
128
|
});
|
|
134
129
|
|
|
130
|
+
test("bootstrapClientShellApp hands hooks and modules one logger that carries its debug flag", async () => {
|
|
131
|
+
for (const debugEnabled of [false, true]) {
|
|
132
|
+
let routeTableReads = 0;
|
|
133
|
+
const seenLoggers = [];
|
|
134
|
+
const router = {
|
|
135
|
+
addRoute() {},
|
|
136
|
+
getRoutes() {
|
|
137
|
+
routeTableReads += 1;
|
|
138
|
+
return [];
|
|
139
|
+
},
|
|
140
|
+
async isReady() {}
|
|
141
|
+
};
|
|
142
|
+
const result = await bootstrapClientShellApp({
|
|
143
|
+
createApp: () => ({ use() {}, mount() {} }),
|
|
144
|
+
router,
|
|
145
|
+
surfaceRuntime: createSurfaceRuntimeFixture(),
|
|
146
|
+
logger: { info() {}, warn() {}, error() {} },
|
|
147
|
+
debugEnabled,
|
|
148
|
+
bootClientModules: async ({ logger }) => {
|
|
149
|
+
seenLoggers.push(logger);
|
|
150
|
+
return {};
|
|
151
|
+
},
|
|
152
|
+
onAfterRouterReady: ({ logger }) => seenLoggers.push(logger)
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
assert.equal(result.debugEnabled, debugEnabled);
|
|
156
|
+
assert.equal(result.logger.isDebugEnabled, debugEnabled);
|
|
157
|
+
assert.deepEqual(seenLoggers, [result.logger, result.logger]);
|
|
158
|
+
assert.equal(routeTableReads > 0, debugEnabled);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
135
162
|
test("bootstrapClientShellApp boots modules, reinstalls fallback route, and mounts app", async () => {
|
|
136
163
|
const calls = [];
|
|
137
164
|
const logs = [];
|
package/client/shellRouting.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { deriveSurfaceRouteBaseFromPagesRoot } from "../shared/surface/index.js";
|
|
2
1
|
import { filterRoutesBySurface } from "../shared/surface/runtime.js";
|
|
3
2
|
import { isExternalLinkTarget } from "../shared/support/linkPath.js";
|
|
4
3
|
import { AUTH_POLICY_AUTHENTICATED, AUTH_POLICY_PUBLIC } from "../shared/support/policies.js";
|
|
@@ -101,20 +100,6 @@ function resolveSearchFromFullPath(fullPath) {
|
|
|
101
100
|
return hashStart < 0 ? rawFullPath.slice(queryStart) : rawFullPath.slice(queryStart, hashStart);
|
|
102
101
|
}
|
|
103
102
|
|
|
104
|
-
function resolveSurfaceDefinition(surfaceDefinitions, surfaceId) {
|
|
105
|
-
const normalizedSurfaceId = String(surfaceId || "")
|
|
106
|
-
.trim()
|
|
107
|
-
.toLowerCase();
|
|
108
|
-
if (!normalizedSurfaceId) {
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
const definition = surfaceDefinitions[normalizedSurfaceId];
|
|
112
|
-
if (!definition || typeof definition !== "object") {
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
return definition;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
103
|
function normalizeWebRootAllowed(value) {
|
|
119
104
|
const normalizedValue = String(value || "")
|
|
120
105
|
.trim()
|
|
@@ -125,27 +110,7 @@ function normalizeWebRootAllowed(value) {
|
|
|
125
110
|
return WEB_ROOT_ALLOW_YES;
|
|
126
111
|
}
|
|
127
112
|
|
|
128
|
-
function
|
|
129
|
-
const defaultSurface = resolveSurfaceDefinition(surfaceDefinitions, defaultSurfaceId);
|
|
130
|
-
return String(
|
|
131
|
-
defaultSurface?.routeBase || deriveSurfaceRouteBaseFromPagesRoot(defaultSurface?.pagesRoot || "")
|
|
132
|
-
).trim() || "/";
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function resolveSurfaceRequiresAuth({ pathname, surfaceRuntime, surfaceDefinitions }) {
|
|
136
|
-
const normalizedPathname = String(pathname || "/").trim() || "/";
|
|
137
|
-
const surfaceId = surfaceRuntime.resolveSurfaceFromPathname(normalizedPathname);
|
|
138
|
-
const surfaceDefinition = resolveSurfaceDefinition(surfaceDefinitions, surfaceId);
|
|
139
|
-
return Boolean(surfaceDefinition?.requiresAuth);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function resolveEffectiveRouteGuard({
|
|
143
|
-
to,
|
|
144
|
-
surfaceRuntime,
|
|
145
|
-
surfaceDefinitions,
|
|
146
|
-
authenticatedPolicy = AUTH_POLICY_AUTHENTICATED,
|
|
147
|
-
publicPolicy = AUTH_POLICY_PUBLIC
|
|
148
|
-
}) {
|
|
113
|
+
function resolveEffectiveRouteGuard({ to, surfaceRuntime, authenticatedPolicy, publicPolicy }) {
|
|
149
114
|
const routeGuard = resolveRouteGuard(to);
|
|
150
115
|
const routePolicy = normalizeGuardPolicy(routeGuard?.policy);
|
|
151
116
|
if (routePolicy) {
|
|
@@ -154,13 +119,8 @@ function resolveEffectiveRouteGuard({
|
|
|
154
119
|
};
|
|
155
120
|
}
|
|
156
121
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
pathname: to?.path || "/",
|
|
160
|
-
surfaceRuntime,
|
|
161
|
-
surfaceDefinitions
|
|
162
|
-
})
|
|
163
|
-
) {
|
|
122
|
+
const surfaceId = surfaceRuntime.resolveSurfaceFromPathname(String(to?.path || "/").trim() || "/");
|
|
123
|
+
if (surfaceRuntime.getSurfaceDefinition(surfaceId)?.requiresAuth) {
|
|
164
124
|
return {
|
|
165
125
|
policy: authenticatedPolicy
|
|
166
126
|
};
|
|
@@ -245,31 +205,27 @@ function evaluateShellGuard({ guard, to, guardEvaluatorKey }) {
|
|
|
245
205
|
|
|
246
206
|
function createShellBeforeEachGuard({
|
|
247
207
|
surfaceRuntime,
|
|
248
|
-
surfaceDefinitions,
|
|
249
|
-
defaultSurfaceId,
|
|
250
208
|
webRootAllowed = WEB_ROOT_ALLOW_YES,
|
|
251
209
|
guardEvaluatorKey = DEFAULT_GUARD_EVALUATOR_KEY,
|
|
252
210
|
authenticatedPolicy = AUTH_POLICY_AUTHENTICATED,
|
|
253
211
|
publicPolicy = AUTH_POLICY_PUBLIC
|
|
254
212
|
} = {}) {
|
|
255
|
-
if (
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
213
|
+
if (
|
|
214
|
+
!surfaceRuntime ||
|
|
215
|
+
typeof surfaceRuntime.resolveSurfaceFromPathname !== "function" ||
|
|
216
|
+
typeof surfaceRuntime.getSurfaceDefinition !== "function"
|
|
217
|
+
) {
|
|
218
|
+
throw new TypeError(
|
|
219
|
+
"createShellBeforeEachGuard requires surfaceRuntime.resolveSurfaceFromPathname() and getSurfaceDefinition()."
|
|
220
|
+
);
|
|
260
221
|
}
|
|
261
222
|
|
|
223
|
+
const defaultSurfaceRootPath =
|
|
224
|
+
surfaceRuntime.getSurfaceDefinition(surfaceRuntime.DEFAULT_SURFACE_ID)?.routeBase || "/";
|
|
225
|
+
const redirectWebRoot = normalizeWebRootAllowed(webRootAllowed) === WEB_ROOT_ALLOW_NO && defaultSurfaceRootPath !== "/";
|
|
226
|
+
|
|
262
227
|
return (to) => {
|
|
263
|
-
|
|
264
|
-
const defaultSurfaceRootPath = resolveDefaultSurfaceRootPath({
|
|
265
|
-
surfaceDefinitions,
|
|
266
|
-
defaultSurfaceId
|
|
267
|
-
});
|
|
268
|
-
if (
|
|
269
|
-
normalizedWebRootAllowed === WEB_ROOT_ALLOW_NO &&
|
|
270
|
-
String(to?.path || "/").trim() === "/" &&
|
|
271
|
-
defaultSurfaceRootPath !== "/"
|
|
272
|
-
) {
|
|
228
|
+
if (redirectWebRoot && String(to?.path || "/").trim() === "/") {
|
|
273
229
|
const search = resolveSearchFromFullPath(to?.fullPath || "");
|
|
274
230
|
const hash = String(to?.hash || "").trim();
|
|
275
231
|
return `${defaultSurfaceRootPath}${search}${hash}`;
|
|
@@ -278,7 +234,6 @@ function createShellBeforeEachGuard({
|
|
|
278
234
|
const guard = resolveEffectiveRouteGuard({
|
|
279
235
|
to,
|
|
280
236
|
surfaceRuntime,
|
|
281
|
-
surfaceDefinitions,
|
|
282
237
|
authenticatedPolicy,
|
|
283
238
|
publicPolicy
|
|
284
239
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import test from "node:test";
|
|
3
3
|
import { createSurfaceRuntime } from "../shared/surface/runtime.js";
|
|
4
|
-
import { buildSurfaceAwareRoutes } from "./shellRouting.js";
|
|
4
|
+
import { DEFAULT_GUARD_EVALUATOR_KEY, buildSurfaceAwareRoutes, createShellBeforeEachGuard } from "./shellRouting.js";
|
|
5
5
|
|
|
6
6
|
test("buildSurfaceAwareRoutes keeps declared route paths and filters by surface mode", () => {
|
|
7
7
|
const surfaceRuntime = createSurfaceRuntime({
|
|
@@ -111,3 +111,32 @@ test("buildSurfaceAwareRoutes preserves parent path when nested descendants are
|
|
|
111
111
|
const routePaths = routes.map((route) => route.path);
|
|
112
112
|
assert.equal(routePaths.includes("/account"), true);
|
|
113
113
|
});
|
|
114
|
+
|
|
115
|
+
test("createShellBeforeEachGuard reads surfaces and the default root from the surface runtime", () => {
|
|
116
|
+
const surfaceRuntime = createSurfaceRuntime({
|
|
117
|
+
defaultSurfaceId: "app",
|
|
118
|
+
surfaces: {
|
|
119
|
+
home: { pagesRoot: "" },
|
|
120
|
+
app: { pagesRoot: "w/app", requiresAuth: true }
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
const evaluations = [];
|
|
124
|
+
const previousEvaluator = globalThis[DEFAULT_GUARD_EVALUATOR_KEY];
|
|
125
|
+
globalThis[DEFAULT_GUARD_EVALUATOR_KEY] = ({ guard, context }) => {
|
|
126
|
+
evaluations.push({ policy: guard.policy, pathname: context.location.pathname });
|
|
127
|
+
return { allow: false, redirectTo: "/auth/login" };
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const guard = createShellBeforeEachGuard({ surfaceRuntime, webRootAllowed: "no" });
|
|
132
|
+
assert.equal(guard({ path: "/", fullPath: "/?next=1#top", hash: "#top" }), "/w/app?next=1#top");
|
|
133
|
+
assert.equal(guard({ path: "/about", fullPath: "/about", matched: [] }), true);
|
|
134
|
+
assert.equal(guard({ path: "/w/app/projects", fullPath: "/w/app/projects", matched: [] }), "/auth/login");
|
|
135
|
+
assert.deepEqual(evaluations, [{ policy: "authenticated", pathname: "/w/app/projects" }]);
|
|
136
|
+
|
|
137
|
+
const openGuard = createShellBeforeEachGuard({ surfaceRuntime });
|
|
138
|
+
assert.equal(openGuard({ path: "/", fullPath: "/", matched: [] }), true);
|
|
139
|
+
} finally {
|
|
140
|
+
globalThis[DEFAULT_GUARD_EVALUATOR_KEY] = previousEvaluator;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
@@ -234,11 +234,6 @@ function resolveInstalledViteProxyEntries(installedPackages = [], { proxyTarget
|
|
|
234
234
|
return proxyEntries;
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
-
async function resolveInstalledClientPackageIds(options) {
|
|
238
|
-
const modules = await resolveInstalledClientModules(options);
|
|
239
|
-
return Object.freeze(modules.map((entry) => entry.packageId));
|
|
240
|
-
}
|
|
241
|
-
|
|
242
237
|
async function resolveLocalScopePackageIds({ appRoot }) {
|
|
243
238
|
const appPackageJson = await readJsonFile(path.resolve(appRoot, "package.json"), {});
|
|
244
239
|
return Object.freeze(
|
|
@@ -487,7 +482,6 @@ export {
|
|
|
487
482
|
resolveCanonicalLocalPackageId,
|
|
488
483
|
resolveLocalPackageForSpecifier,
|
|
489
484
|
resolveLocalPackageSources,
|
|
490
|
-
resolveInstalledClientPackageIds,
|
|
491
485
|
resolveLocalScopePackageIds,
|
|
492
486
|
resolveInstalledClientModules,
|
|
493
487
|
resolveInstalledViteProxyEntries,
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
resolveClientOptimizeIncludeSpecifiers,
|
|
15
15
|
resolveClientOptimizeExcludeSpecifiers,
|
|
16
16
|
resolveLocalScopePackageIds,
|
|
17
|
-
resolveInstalledClientPackageIds,
|
|
18
17
|
resolveInstalledClientModules,
|
|
19
18
|
resolveInstalledViteProxyEntries
|
|
20
19
|
} from "./clientBootstrapPlugin.js";
|
|
@@ -260,7 +259,7 @@ test("resolveClientOptimizeIncludeSpecifiers omits excluded package clients", ()
|
|
|
260
259
|
assert.deepEqual(include, ["@c/pkg/client"]);
|
|
261
260
|
});
|
|
262
261
|
|
|
263
|
-
test("
|
|
262
|
+
test("resolveInstalledClientModules returns only installed packages with a client export", async () => {
|
|
264
263
|
const tempRoot = await mkdtemp(path.join(os.tmpdir(), "jskit-client-bootstrap-"));
|
|
265
264
|
const appRoot = tempRoot;
|
|
266
265
|
|
|
@@ -289,7 +288,7 @@ test("resolveInstalledClientPackageIds returns only installed packages with a cl
|
|
|
289
288
|
}
|
|
290
289
|
});
|
|
291
290
|
|
|
292
|
-
const packageIds = await
|
|
291
|
+
const packageIds = (await resolveInstalledClientModules({ appRoot })).map((entry) => entry.packageId);
|
|
293
292
|
|
|
294
293
|
assert.deepEqual(packageIds, ["@example/has-client"]);
|
|
295
294
|
});
|