@jskit-ai/kernel 0.1.57 → 0.1.58
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 +1 -1
- package/server/http/lib/kernel.test.js +155 -0
- package/server/http/lib/routeRegistration.js +31 -3
- package/server/platform/providerRuntime.js +12 -0
- package/server/platform/providerRuntime.test.js +112 -0
- package/server/support/pageTargets.js +40 -15
- package/server/support/pageTargets.test.js +2 -2
package/package.json
CHANGED
|
@@ -760,6 +760,51 @@ test("registerRoutes attaches request.input when route input transforms are conf
|
|
|
760
760
|
assert.equal(reply.statusCode, 200);
|
|
761
761
|
});
|
|
762
762
|
|
|
763
|
+
test("registerRoutes strips Fastify body schemas when JSKIT runtime body validation is present", () => {
|
|
764
|
+
const fastify = createFastifyStub();
|
|
765
|
+
|
|
766
|
+
registerRoutes(fastify, {
|
|
767
|
+
routes: [
|
|
768
|
+
{
|
|
769
|
+
method: "POST",
|
|
770
|
+
path: "/body-schema-runtime-validation",
|
|
771
|
+
schema: {
|
|
772
|
+
body: {
|
|
773
|
+
type: "object",
|
|
774
|
+
properties: {
|
|
775
|
+
flag: {
|
|
776
|
+
anyOf: [{ type: "boolean" }, { type: "null" }]
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
},
|
|
780
|
+
querystring: {
|
|
781
|
+
type: "object",
|
|
782
|
+
properties: {
|
|
783
|
+
limit: { type: "integer" }
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
},
|
|
787
|
+
input: {
|
|
788
|
+
body: (body) => ({
|
|
789
|
+
flag: body?.flag ?? null
|
|
790
|
+
})
|
|
791
|
+
},
|
|
792
|
+
handler: async (_request, reply) => {
|
|
793
|
+
reply.code(200).send({ ok: true });
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
]
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
assert.equal(Object.prototype.hasOwnProperty.call(fastify.routes[0].schema, "body"), false);
|
|
800
|
+
assert.deepEqual(fastify.routes[0].schema.querystring, {
|
|
801
|
+
type: "object",
|
|
802
|
+
properties: {
|
|
803
|
+
limit: { type: "integer" }
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
});
|
|
807
|
+
|
|
763
808
|
test("registerRoutes applies transport request transforms before route input normalization", async () => {
|
|
764
809
|
const fastify = createFastifyStub();
|
|
765
810
|
|
|
@@ -979,6 +1024,116 @@ test("registerRoutes applies transport response transforms before reply serializ
|
|
|
979
1024
|
});
|
|
980
1025
|
});
|
|
981
1026
|
|
|
1027
|
+
test("registerRoutes bypasses success transport and output transforms for error replies", async () => {
|
|
1028
|
+
const fastify = createFastifyStub();
|
|
1029
|
+
let successTransportCalls = 0;
|
|
1030
|
+
let outputTransformCalls = 0;
|
|
1031
|
+
|
|
1032
|
+
registerRoutes(fastify, {
|
|
1033
|
+
routes: [
|
|
1034
|
+
{
|
|
1035
|
+
method: "GET",
|
|
1036
|
+
path: "/transport-error-bypass",
|
|
1037
|
+
transport: {
|
|
1038
|
+
kind: "jsonapi-resource",
|
|
1039
|
+
contentType: "application/vnd.api+json",
|
|
1040
|
+
response() {
|
|
1041
|
+
successTransportCalls += 1;
|
|
1042
|
+
return {
|
|
1043
|
+
data: {
|
|
1044
|
+
type: "contacts",
|
|
1045
|
+
id: "7",
|
|
1046
|
+
attributes: {
|
|
1047
|
+
name: "Alice"
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
},
|
|
1053
|
+
output() {
|
|
1054
|
+
outputTransformCalls += 1;
|
|
1055
|
+
return {
|
|
1056
|
+
mutated: true
|
|
1057
|
+
};
|
|
1058
|
+
},
|
|
1059
|
+
handler: async (_request, reply) => {
|
|
1060
|
+
reply.code(500).send({
|
|
1061
|
+
errors: [
|
|
1062
|
+
{
|
|
1063
|
+
status: "500",
|
|
1064
|
+
code: "internal_server_error",
|
|
1065
|
+
title: "Internal server error."
|
|
1066
|
+
}
|
|
1067
|
+
]
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
]
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
const request = {};
|
|
1075
|
+
const reply = createReplyStub();
|
|
1076
|
+
|
|
1077
|
+
await fastify.routes[0].handler(request, reply);
|
|
1078
|
+
|
|
1079
|
+
assert.equal(reply.statusCode, 500);
|
|
1080
|
+
assert.equal(reply.headers["Content-Type"], "application/vnd.api+json");
|
|
1081
|
+
assert.equal(successTransportCalls, 0);
|
|
1082
|
+
assert.equal(outputTransformCalls, 0);
|
|
1083
|
+
assert.deepEqual(reply.payload, {
|
|
1084
|
+
errors: [
|
|
1085
|
+
{
|
|
1086
|
+
status: "500",
|
|
1087
|
+
code: "internal_server_error",
|
|
1088
|
+
title: "Internal server error."
|
|
1089
|
+
}
|
|
1090
|
+
]
|
|
1091
|
+
});
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
test("registerRoutes bypasses success transport for Error payloads before status normalization", async () => {
|
|
1095
|
+
const fastify = createFastifyStub();
|
|
1096
|
+
let successTransportCalls = 0;
|
|
1097
|
+
|
|
1098
|
+
registerRoutes(fastify, {
|
|
1099
|
+
routes: [
|
|
1100
|
+
{
|
|
1101
|
+
method: "GET",
|
|
1102
|
+
path: "/transport-error-instance",
|
|
1103
|
+
transport: {
|
|
1104
|
+
kind: "jsonapi-resource",
|
|
1105
|
+
contentType: "application/vnd.api+json",
|
|
1106
|
+
response() {
|
|
1107
|
+
successTransportCalls += 1;
|
|
1108
|
+
return {
|
|
1109
|
+
data: {
|
|
1110
|
+
type: "contacts",
|
|
1111
|
+
id: "7",
|
|
1112
|
+
attributes: {
|
|
1113
|
+
name: "Alice"
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
};
|
|
1117
|
+
}
|
|
1118
|
+
},
|
|
1119
|
+
handler: async (_request, reply) => {
|
|
1120
|
+
reply.send(new Error("Boom"));
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
]
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
const request = {};
|
|
1127
|
+
const reply = createReplyStub();
|
|
1128
|
+
|
|
1129
|
+
await fastify.routes[0].handler(request, reply);
|
|
1130
|
+
|
|
1131
|
+
assert.equal(successTransportCalls, 0);
|
|
1132
|
+
assert.equal(reply.headers["Content-Type"], "application/vnd.api+json");
|
|
1133
|
+
assert.equal(reply.payload instanceof Error, true);
|
|
1134
|
+
assert.equal(reply.payload.message, "Boom");
|
|
1135
|
+
});
|
|
1136
|
+
|
|
982
1137
|
test("registerRoutes exposes full transport runtime on Fastify route config for pre-handler error serialization", () => {
|
|
983
1138
|
const fastify = createFastifyStub();
|
|
984
1139
|
const transport = {
|
|
@@ -17,6 +17,9 @@ const JSON_API_CONTENT_TYPE = "application/vnd.api+json";
|
|
|
17
17
|
function toFastifyRouteOptions(route) {
|
|
18
18
|
const sourceRoute = normalizeObject(route);
|
|
19
19
|
const schema = cloneRouteSchema(sourceRoute.schema);
|
|
20
|
+
if (shouldStripFastifyBodySchema(sourceRoute, schema)) {
|
|
21
|
+
delete schema.body;
|
|
22
|
+
}
|
|
20
23
|
const existingConfig = normalizeObject(sourceRoute.config);
|
|
21
24
|
const transportKind = normalizeText(sourceRoute?.transport?.kind).toLowerCase();
|
|
22
25
|
const existingTransportConfig =
|
|
@@ -48,6 +51,18 @@ function toFastifyRouteOptions(route) {
|
|
|
48
51
|
};
|
|
49
52
|
}
|
|
50
53
|
|
|
54
|
+
function shouldStripFastifyBodySchema(route = null, schema = null) {
|
|
55
|
+
if (!route || !schema || typeof schema !== "object" || Array.isArray(schema)) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!Object.prototype.hasOwnProperty.call(schema, "body")) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return typeof route?.input?.body === "function";
|
|
64
|
+
}
|
|
65
|
+
|
|
51
66
|
function normalizeHeaderValue(value) {
|
|
52
67
|
if (Array.isArray(value)) {
|
|
53
68
|
return String(value[0] || "").trim();
|
|
@@ -210,6 +225,19 @@ function wrapReplySend({ reply = null, request = null, route = null, outputTrans
|
|
|
210
225
|
|
|
211
226
|
const originalSend = reply.send.bind(reply);
|
|
212
227
|
reply.send = function transformedSend(payload) {
|
|
228
|
+
const statusCode = Number(reply?.statusCode || 200);
|
|
229
|
+
|
|
230
|
+
if (payload instanceof Error || statusCode >= 400) {
|
|
231
|
+
if (
|
|
232
|
+
normalizeText(transport?.contentType) &&
|
|
233
|
+
!replyHasHeader(reply, "content-type")
|
|
234
|
+
) {
|
|
235
|
+
reply.header("Content-Type", transport.contentType);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return originalSend(payload);
|
|
239
|
+
}
|
|
240
|
+
|
|
213
241
|
let nextPayload = payload;
|
|
214
242
|
if (typeof transport?.response === "function") {
|
|
215
243
|
const transportedPayload = transport.response(payload, {
|
|
@@ -217,7 +245,7 @@ function wrapReplySend({ reply = null, request = null, route = null, outputTrans
|
|
|
217
245
|
reply,
|
|
218
246
|
route,
|
|
219
247
|
transport,
|
|
220
|
-
statusCode
|
|
248
|
+
statusCode
|
|
221
249
|
});
|
|
222
250
|
|
|
223
251
|
if (transportedPayload && typeof transportedPayload.then === "function") {
|
|
@@ -235,7 +263,7 @@ function wrapReplySend({ reply = null, request = null, route = null, outputTrans
|
|
|
235
263
|
reply,
|
|
236
264
|
route,
|
|
237
265
|
transport,
|
|
238
|
-
statusCode
|
|
266
|
+
statusCode
|
|
239
267
|
});
|
|
240
268
|
|
|
241
269
|
if (transformedPayload && typeof transformedPayload.then === "function") {
|
|
@@ -249,7 +277,7 @@ function wrapReplySend({ reply = null, request = null, route = null, outputTrans
|
|
|
249
277
|
|
|
250
278
|
if (
|
|
251
279
|
normalizeText(transport?.contentType) &&
|
|
252
|
-
|
|
280
|
+
statusCode !== 204 &&
|
|
253
281
|
!replyHasHeader(reply, "content-type")
|
|
254
282
|
) {
|
|
255
283
|
reply.header("Content-Type", transport.contentType);
|
|
@@ -39,6 +39,18 @@ async function createProviderRuntimeApp({
|
|
|
39
39
|
|
|
40
40
|
await app.start({ providers });
|
|
41
41
|
|
|
42
|
+
if (fastify && typeof fastify.addHook === "function") {
|
|
43
|
+
let didShutdown = false;
|
|
44
|
+
fastify.addHook("onClose", async () => {
|
|
45
|
+
if (didShutdown) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
didShutdown = true;
|
|
50
|
+
await app.shutdown();
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
42
54
|
const routeRegistration = httpRuntime ? httpRuntime.registerRoutes() : { routeCount: 0 };
|
|
43
55
|
return Object.freeze({
|
|
44
56
|
app,
|
|
@@ -215,3 +215,115 @@ test("createProviderRuntimeFromApp resolves descriptor using source.packagePath"
|
|
|
215
215
|
await rm(appRoot, { recursive: true, force: true });
|
|
216
216
|
}
|
|
217
217
|
});
|
|
218
|
+
|
|
219
|
+
test("createProviderRuntimeFromApp wires fastify onClose to provider shutdown exactly once", async () => {
|
|
220
|
+
const appRoot = await createTestAppRoot("kernel-provider-runtime-fastify-close-");
|
|
221
|
+
try {
|
|
222
|
+
await mkdir(path.join(appRoot, "packages", "local-example", "src", "server", "providers"), { recursive: true });
|
|
223
|
+
await writeFile(
|
|
224
|
+
path.join(appRoot, ".jskit", "lock.json"),
|
|
225
|
+
`${JSON.stringify(
|
|
226
|
+
{
|
|
227
|
+
lockVersion: 1,
|
|
228
|
+
installedPackages: {
|
|
229
|
+
"@local/example": {
|
|
230
|
+
packageId: "@local/example",
|
|
231
|
+
version: "0.1.0",
|
|
232
|
+
source: {
|
|
233
|
+
type: "local-package",
|
|
234
|
+
packagePath: "packages/local-example"
|
|
235
|
+
},
|
|
236
|
+
managed: {
|
|
237
|
+
packageJson: {
|
|
238
|
+
dependencies: {},
|
|
239
|
+
devDependencies: {},
|
|
240
|
+
scripts: {}
|
|
241
|
+
},
|
|
242
|
+
text: {},
|
|
243
|
+
files: []
|
|
244
|
+
},
|
|
245
|
+
options: {},
|
|
246
|
+
installedAt: "2026-01-01T00:00:00.000Z"
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
null,
|
|
251
|
+
2
|
|
252
|
+
)}\n`,
|
|
253
|
+
"utf8"
|
|
254
|
+
);
|
|
255
|
+
await writeFile(
|
|
256
|
+
path.join(appRoot, "packages", "local-example", "package.descriptor.mjs"),
|
|
257
|
+
[
|
|
258
|
+
"export default Object.freeze({",
|
|
259
|
+
" packageVersion: 1,",
|
|
260
|
+
" packageId: \"@local/example\",",
|
|
261
|
+
" version: \"0.1.0\",",
|
|
262
|
+
" description: \"Local example package\",",
|
|
263
|
+
" dependsOn: [],",
|
|
264
|
+
" capabilities: {",
|
|
265
|
+
" provides: [],",
|
|
266
|
+
" requires: []",
|
|
267
|
+
" },",
|
|
268
|
+
" runtime: {",
|
|
269
|
+
" server: {",
|
|
270
|
+
" providers: [",
|
|
271
|
+
" { discover: { dir: \"src/server/providers\", pattern: \"*Provider.js\" } }",
|
|
272
|
+
" ]",
|
|
273
|
+
" }",
|
|
274
|
+
" }",
|
|
275
|
+
"});"
|
|
276
|
+
].join("\n"),
|
|
277
|
+
"utf8"
|
|
278
|
+
);
|
|
279
|
+
await writeFile(
|
|
280
|
+
path.join(appRoot, "packages", "local-example", "src", "server", "providers", "CloseAwareProvider.js"),
|
|
281
|
+
[
|
|
282
|
+
"export default class CloseAwareProvider {",
|
|
283
|
+
" static id = \"example.close-aware\";",
|
|
284
|
+
" register(app) {",
|
|
285
|
+
" app.instance(\"example.close.state\", { shutdownCalls: 0 });",
|
|
286
|
+
" }",
|
|
287
|
+
" async boot() {}",
|
|
288
|
+
" async shutdown(app) {",
|
|
289
|
+
" app.make(\"example.close.state\").shutdownCalls += 1;",
|
|
290
|
+
" }",
|
|
291
|
+
"}"
|
|
292
|
+
].join("\n"),
|
|
293
|
+
"utf8"
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
const hooks = [];
|
|
297
|
+
const fastify = {
|
|
298
|
+
route() {},
|
|
299
|
+
setErrorHandler() {},
|
|
300
|
+
addContentTypeParser() {},
|
|
301
|
+
hasContentTypeParser() {
|
|
302
|
+
return false;
|
|
303
|
+
},
|
|
304
|
+
getDefaultJsonParser() {
|
|
305
|
+
return (_request, body, done) => done(null, body);
|
|
306
|
+
},
|
|
307
|
+
addHook(name, handler) {
|
|
308
|
+
hooks.push({ name, handler });
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const runtime = await createProviderRuntimeFromApp({
|
|
313
|
+
appRoot,
|
|
314
|
+
profile: "app",
|
|
315
|
+
fastify
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
const closeHook = hooks.find((entry) => entry.name === "onClose");
|
|
319
|
+
assert.ok(closeHook);
|
|
320
|
+
assert.equal(runtime.app.make("example.close.state").shutdownCalls, 0);
|
|
321
|
+
|
|
322
|
+
await closeHook.handler();
|
|
323
|
+
await closeHook.handler();
|
|
324
|
+
|
|
325
|
+
assert.equal(runtime.app.make("example.close.state").shutdownCalls, 1);
|
|
326
|
+
} finally {
|
|
327
|
+
await rm(appRoot, { recursive: true, force: true });
|
|
328
|
+
}
|
|
329
|
+
});
|
|
@@ -19,9 +19,11 @@ import { resolveRequiredAppRoot, toPosixPath } from "./path.js";
|
|
|
19
19
|
import { loadAppConfigFromModuleUrl } from "./appConfigFiles.js";
|
|
20
20
|
|
|
21
21
|
const DEFAULT_PAGE_LINK_COMPONENT_TOKEN = "local.main.ui.surface-aware-menu-link-item";
|
|
22
|
-
const DEFAULT_SUBPAGE_LINK_COMPONENT_TOKEN = "local.main.ui.
|
|
22
|
+
const DEFAULT_SUBPAGE_LINK_COMPONENT_TOKEN = "local.main.ui.surface-aware-menu-link-item";
|
|
23
23
|
const PAGE_ROOT_PREFIX = "src/pages/";
|
|
24
24
|
const ROUTER_VIEW_TAG_PATTERN = /<RouterView\b/i;
|
|
25
|
+
const SECTION_CONTAINER_SHELL_TAG_PATTERN = /<SectionContainerShell\b/i;
|
|
26
|
+
const TABS_SLOT_PATTERN = /<template\b[^>]*(?:#tabs|v-slot:tabs)\b[^>]*>([\s\S]*?)<\/template>/i;
|
|
25
27
|
|
|
26
28
|
function normalizeRelativeFilePath(value = "") {
|
|
27
29
|
return String(value || "")
|
|
@@ -430,7 +432,6 @@ function resolveSubpagesHostTargetFromPageSource(source = "") {
|
|
|
430
432
|
if (!ROUTER_VIEW_TAG_PATTERN.test(sourceText)) {
|
|
431
433
|
return null;
|
|
432
434
|
}
|
|
433
|
-
|
|
434
435
|
const discoveredTargets = discoverShellOutletTargetsFromVueSource(sourceText, {
|
|
435
436
|
context: "subpages host"
|
|
436
437
|
});
|
|
@@ -444,8 +445,25 @@ function resolveSubpagesHostTargetFromPageSource(source = "") {
|
|
|
444
445
|
return null;
|
|
445
446
|
}
|
|
446
447
|
|
|
448
|
+
let isSectionSubpagesHost = false;
|
|
449
|
+
if (SECTION_CONTAINER_SHELL_TAG_PATTERN.test(sourceText)) {
|
|
450
|
+
const tabsSlotMatch = TABS_SLOT_PATTERN.exec(sourceText);
|
|
451
|
+
if (tabsSlotMatch) {
|
|
452
|
+
const tabsTargets = discoverShellOutletTargetsFromVueSource(String(tabsSlotMatch[1] || ""), {
|
|
453
|
+
context: "subpages host tabs slot"
|
|
454
|
+
});
|
|
455
|
+
const tabTargetIds = Array.isArray(tabsTargets.targets)
|
|
456
|
+
? tabsTargets.targets.map((entry) => normalizeShellOutletTargetId(entry?.id)).filter(Boolean)
|
|
457
|
+
: [];
|
|
458
|
+
if (tabTargetIds.length === 1 && tabTargetIds[0] === normalizeShellOutletTargetId(target.id)) {
|
|
459
|
+
isSectionSubpagesHost = true;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
447
464
|
return Object.freeze({
|
|
448
|
-
id: target.id
|
|
465
|
+
id: target.id,
|
|
466
|
+
isSectionSubpagesHost
|
|
449
467
|
});
|
|
450
468
|
}
|
|
451
469
|
|
|
@@ -594,16 +612,11 @@ function resolveInferredPageLinkTo({
|
|
|
594
612
|
const parentTargetId = normalizePlacementTargetId(parentHost);
|
|
595
613
|
const placementTargetId = normalizePlacementTargetId(placementTarget);
|
|
596
614
|
if (parentTargetId && parentTargetId === placementTargetId) {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
if (normalizeText(parentHost?.pageShape) === "index") {
|
|
604
|
-
const inferredLinkTo = resolveRelativeLinkToFromParent(pageTarget, parentHost);
|
|
605
|
-
if (inferredLinkTo) {
|
|
606
|
-
return inferredLinkTo;
|
|
615
|
+
if (parentHost?.isSectionSubpagesHost === true) {
|
|
616
|
+
const inferredLinkTo = resolveRelativeLinkToFromParent(pageTarget, parentHost);
|
|
617
|
+
if (inferredLinkTo) {
|
|
618
|
+
return inferredLinkTo;
|
|
619
|
+
}
|
|
607
620
|
}
|
|
608
621
|
}
|
|
609
622
|
|
|
@@ -633,7 +646,11 @@ function resolveInferredPageLinkComponentToken({
|
|
|
633
646
|
|
|
634
647
|
const parentTargetId = normalizePlacementTargetId(parentHost);
|
|
635
648
|
const placementTargetId = normalizePlacementTargetId(placementTarget);
|
|
636
|
-
if (
|
|
649
|
+
if (
|
|
650
|
+
parentHost?.isSectionSubpagesHost === true &&
|
|
651
|
+
parentTargetId &&
|
|
652
|
+
parentTargetId === placementTargetId
|
|
653
|
+
) {
|
|
637
654
|
return normalizeText(subpageComponentToken) || DEFAULT_SUBPAGE_LINK_COMPONENT_TOKEN;
|
|
638
655
|
}
|
|
639
656
|
|
|
@@ -681,6 +698,12 @@ async function resolvePageLinkTargetDetails({
|
|
|
681
698
|
defaultComponentToken,
|
|
682
699
|
subpageComponentToken
|
|
683
700
|
});
|
|
701
|
+
const parentTargetId = normalizePlacementTargetId(parentHost);
|
|
702
|
+
const placementTargetId = normalizePlacementTargetId(placementTarget);
|
|
703
|
+
const preservesRelativeSubpageLinks =
|
|
704
|
+
parentHost?.isSectionSubpagesHost === true &&
|
|
705
|
+
Boolean(parentTargetId) &&
|
|
706
|
+
parentTargetId === placementTargetId;
|
|
684
707
|
|
|
685
708
|
return Object.freeze({
|
|
686
709
|
pageTarget: resolvedPageTarget,
|
|
@@ -693,7 +716,9 @@ async function resolvePageLinkTargetDetails({
|
|
|
693
716
|
pageTarget: resolvedPageTarget,
|
|
694
717
|
parentHost,
|
|
695
718
|
placementTarget,
|
|
696
|
-
suppressImplicitRelativeLinks:
|
|
719
|
+
suppressImplicitRelativeLinks:
|
|
720
|
+
resolvedComponentToken === (normalizeText(defaultComponentToken) || DEFAULT_PAGE_LINK_COMPONENT_TOKEN) &&
|
|
721
|
+
preservesRelativeSubpageLinks !== true
|
|
697
722
|
})
|
|
698
723
|
});
|
|
699
724
|
}
|
|
@@ -354,7 +354,7 @@ test("resolvePageLinkTargetDetails inherits a file-route parent subpages host",
|
|
|
354
354
|
|
|
355
355
|
assert.equal(details.parentHost?.id, "contact-view:sub-pages");
|
|
356
356
|
assert.equal(details.placementTarget.id, "contact-view:sub-pages");
|
|
357
|
-
assert.equal(details.componentToken, "local.main.ui.
|
|
357
|
+
assert.equal(details.componentToken, "local.main.ui.surface-aware-menu-link-item");
|
|
358
358
|
assert.equal(details.linkTo, "./notes");
|
|
359
359
|
});
|
|
360
360
|
});
|
|
@@ -422,7 +422,7 @@ test("resolvePageLinkTargetDetails inherits an index-route parent subpages host
|
|
|
422
422
|
assert.equal(details.parentHost?.id, "customer-view:sub-pages");
|
|
423
423
|
assert.equal(details.parentHost?.pageFile, "src/pages/admin/customers/[customerId]/index.vue");
|
|
424
424
|
assert.equal(details.placementTarget.id, "customer-view:sub-pages");
|
|
425
|
-
assert.equal(details.componentToken, "local.main.ui.
|
|
425
|
+
assert.equal(details.componentToken, "local.main.ui.surface-aware-menu-link-item");
|
|
426
426
|
assert.equal(details.linkTo, "./pets");
|
|
427
427
|
});
|
|
428
428
|
});
|