@jskit-ai/kernel 0.1.65 → 0.1.67
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/moduleBootstrap.js +4 -0
- package/client/moduleBootstrap.test.js +4 -0
- package/client/shellBootstrap.js +2 -0
- package/client/shellBootstrap.test.js +3 -0
- package/client/vite/clientBootstrapPlugin.js +1 -0
- package/client/vite/clientBootstrapPlugin.test.js +3 -3
- package/package.json +2 -1
- package/server/http/lib/httpRuntime.js +3 -1
- package/server/http/lib/kernel.test.js +4 -0
- package/server/runtime/fastifyBootstrap.js +61 -0
- package/server/runtime/fastifyBootstrap.test.js +47 -1
- package/server/support/appConfigFiles.js +3 -2
- package/server/support/appConfigFiles.test.js +15 -0
- package/server/support/index.js +3 -0
- package/server/support/pageTargets.js +98 -63
- package/server/support/pageTargets.test.js +246 -15
- package/server/support/shellOutlets.js +245 -56
- package/server/support/shellOutlets.test.js +31 -67
- package/shared/support/generatedUiContract.js +542 -0
- package/shared/support/generatedUiContract.test.js +208 -0
- package/shared/support/shellLayoutTargets.js +230 -7
- package/shared/support/shellLayoutTargets.test.js +20 -2
|
@@ -8,13 +8,15 @@ import {
|
|
|
8
8
|
describeShellOutletTargets,
|
|
9
9
|
discoverShellOutletTargetsFromVueSource,
|
|
10
10
|
findShellOutletTargetById,
|
|
11
|
+
normalizePlacementTopologyDefinition,
|
|
12
|
+
normalizeSemanticPlacementId,
|
|
11
13
|
normalizeShellOutletTargetId,
|
|
12
14
|
normalizeShellOutletTargetRecord
|
|
13
15
|
} from "../../shared/support/shellLayoutTargets.js";
|
|
14
|
-
import { loadAppConfigFromModuleUrl } from "./appConfigFiles.js";
|
|
15
16
|
|
|
16
17
|
const VUE_DISCOVERY_IGNORED_ERROR_CODES = new Set(["ENOENT", "ENOTDIR", "EACCES", "EPERM"]);
|
|
17
18
|
const LOCK_FILE_RELATIVE_PATH = ".jskit/lock.json";
|
|
19
|
+
const PLACEMENT_TOPOLOGY_RELATIVE_PATH = "src/placementTopology.js";
|
|
18
20
|
const ROUTE_TAG_PATTERN = /<route\b([^>]*)>([\s\S]*?)<\/route>/g;
|
|
19
21
|
const ATTRIBUTE_PATTERN = /([:@]?[A-Za-z_][A-Za-z0-9_-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'))?/g;
|
|
20
22
|
|
|
@@ -68,7 +70,13 @@ function normalizeAppRouteOutletTarget({
|
|
|
68
70
|
});
|
|
69
71
|
}
|
|
70
72
|
|
|
71
|
-
function discoverRouteMetaOutletTargetsFromVueSource(
|
|
73
|
+
function discoverRouteMetaOutletTargetsFromVueSource(
|
|
74
|
+
source = "",
|
|
75
|
+
{
|
|
76
|
+
context = "shell layout",
|
|
77
|
+
enforceSingleDefault = true
|
|
78
|
+
} = {}
|
|
79
|
+
) {
|
|
72
80
|
const sourceText = String(source || "");
|
|
73
81
|
const resolvedContext = normalizeText(context) || "shell layout";
|
|
74
82
|
const targetById = new Map();
|
|
@@ -111,12 +119,14 @@ function discoverRouteMetaOutletTargetsFromVueSource(source = "", { context = "s
|
|
|
111
119
|
throw new Error(`${resolvedContext} contains duplicate route meta placement target "${normalizedTarget.id}".`);
|
|
112
120
|
}
|
|
113
121
|
if (normalizedTarget.default === true) {
|
|
114
|
-
if (defaultTargetId && defaultTargetId !== normalizedTarget.id) {
|
|
122
|
+
if (enforceSingleDefault === true && defaultTargetId && defaultTargetId !== normalizedTarget.id) {
|
|
115
123
|
throw new Error(
|
|
116
124
|
`${resolvedContext} defines multiple default route meta placement targets: "${defaultTargetId}" and "${normalizedTarget.id}".`
|
|
117
125
|
);
|
|
118
126
|
}
|
|
119
|
-
defaultTargetId
|
|
127
|
+
if (!defaultTargetId) {
|
|
128
|
+
defaultTargetId = normalizedTarget.id;
|
|
129
|
+
}
|
|
120
130
|
}
|
|
121
131
|
targetById.set(normalizedTarget.id, normalizedTarget);
|
|
122
132
|
}
|
|
@@ -218,46 +228,6 @@ function normalizePackageOutletTarget({
|
|
|
218
228
|
});
|
|
219
229
|
}
|
|
220
230
|
|
|
221
|
-
async function loadOutletDefaultOverrides(appRoot = "") {
|
|
222
|
-
const resolvedAppRoot = resolveRequiredAppRoot(appRoot, {
|
|
223
|
-
context: "discoverShellOutletTargetsFromApp"
|
|
224
|
-
});
|
|
225
|
-
let appConfig = {};
|
|
226
|
-
try {
|
|
227
|
-
appConfig = normalizeObject(
|
|
228
|
-
await loadAppConfigFromModuleUrl({
|
|
229
|
-
moduleUrl: pathToFileURL(path.join(resolvedAppRoot, "config", "public.js")).href
|
|
230
|
-
})
|
|
231
|
-
);
|
|
232
|
-
} catch {
|
|
233
|
-
return {};
|
|
234
|
-
}
|
|
235
|
-
return normalizeObject(normalizeObject(appConfig.ui).outletDefaults);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function applyOutletDefaultOverrides(target = {}, outletDefaultOverrides = {}) {
|
|
239
|
-
const targetRecord = normalizeObject(target);
|
|
240
|
-
const outletTargetId = normalizeShellOutletTargetId(targetRecord.id);
|
|
241
|
-
if (!outletTargetId) {
|
|
242
|
-
return targetRecord;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const overrideRecord = outletDefaultOverrides?.[outletTargetId];
|
|
246
|
-
const normalizedOverrideToken =
|
|
247
|
-
typeof overrideRecord === "string"
|
|
248
|
-
? normalizeText(overrideRecord)
|
|
249
|
-
: normalizeText(normalizeObject(overrideRecord).linkComponentToken) ||
|
|
250
|
-
normalizeText(normalizeObject(overrideRecord)["link-component-token"]);
|
|
251
|
-
if (!normalizedOverrideToken) {
|
|
252
|
-
return targetRecord;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
return Object.freeze({
|
|
256
|
-
...targetRecord,
|
|
257
|
-
defaultLinkComponentToken: normalizedOverrideToken
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
231
|
async function collectInstalledPackageOutletTargets(appRoot) {
|
|
262
232
|
const installedPackageStates = await readInstalledPackageStates(appRoot);
|
|
263
233
|
const packageIds = Object.keys(installedPackageStates).sort((left, right) => left.localeCompare(right));
|
|
@@ -290,11 +260,185 @@ async function collectInstalledPackageOutletTargets(appRoot) {
|
|
|
290
260
|
return targets;
|
|
291
261
|
}
|
|
292
262
|
|
|
293
|
-
|
|
263
|
+
function withTopologySource(placement = {}, sourcePath = "") {
|
|
264
|
+
return Object.freeze({
|
|
265
|
+
...placement,
|
|
266
|
+
sourcePath
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async function loadAppPlacementTopology(appRoot) {
|
|
271
|
+
const topologyPath = path.resolve(appRoot, PLACEMENT_TOPOLOGY_RELATIVE_PATH);
|
|
272
|
+
try {
|
|
273
|
+
await readFile(topologyPath, "utf8");
|
|
274
|
+
} catch (error) {
|
|
275
|
+
const errorCode = normalizeText(error?.code).toUpperCase();
|
|
276
|
+
if (errorCode === "ENOENT" || errorCode === "ENOTDIR") {
|
|
277
|
+
return [];
|
|
278
|
+
}
|
|
279
|
+
throw error;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
let moduleNamespace = null;
|
|
283
|
+
try {
|
|
284
|
+
moduleNamespace = await import(pathToFileURL(topologyPath).href);
|
|
285
|
+
} catch (error) {
|
|
286
|
+
throw new Error(
|
|
287
|
+
`Could not load ${PLACEMENT_TOPOLOGY_RELATIVE_PATH}: ${String(error?.message || error || "unknown error")}`
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const exported = moduleNamespace?.default;
|
|
292
|
+
const resolved = typeof exported === "function" ? exported() : exported;
|
|
293
|
+
const normalized = normalizePlacementTopologyDefinition(resolved, {
|
|
294
|
+
context: PLACEMENT_TOPOLOGY_RELATIVE_PATH
|
|
295
|
+
});
|
|
296
|
+
return normalized.placements.map((placement) => withTopologySource(placement, PLACEMENT_TOPOLOGY_RELATIVE_PATH));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function collectInstalledPackagePlacementTopology(appRoot) {
|
|
300
|
+
const installedPackageStates = await readInstalledPackageStates(appRoot);
|
|
301
|
+
const packageIds = Object.keys(installedPackageStates).sort((left, right) => left.localeCompare(right));
|
|
302
|
+
const placements = [];
|
|
303
|
+
|
|
304
|
+
for (const packageId of packageIds) {
|
|
305
|
+
const installedPackageState = normalizeObject(installedPackageStates[packageId]);
|
|
306
|
+
const descriptorRecord = await loadInstalledPackageDescriptor({
|
|
307
|
+
appRoot,
|
|
308
|
+
packageId,
|
|
309
|
+
installedPackageState
|
|
310
|
+
});
|
|
311
|
+
const descriptor = normalizeObject(descriptorRecord.descriptor);
|
|
312
|
+
const metadata = normalizeObject(descriptor.metadata);
|
|
313
|
+
const ui = normalizeObject(metadata.ui);
|
|
314
|
+
const placementsMeta = normalizeObject(ui.placements);
|
|
315
|
+
const topology = placementsMeta.topology;
|
|
316
|
+
if (!topology) {
|
|
317
|
+
continue;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const normalized = normalizePlacementTopologyDefinition(topology, {
|
|
321
|
+
context: `package:${packageId}:metadata.ui.placements.topology`
|
|
322
|
+
});
|
|
323
|
+
for (const placement of normalized.placements) {
|
|
324
|
+
placements.push(
|
|
325
|
+
withTopologySource(
|
|
326
|
+
placement,
|
|
327
|
+
`package:${packageId}${descriptorRecord.descriptorPath ? `:${toPosixPath(descriptorRecord.descriptorPath)}` : ""}`
|
|
328
|
+
)
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return placements;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async function discoverPlacementTopologyFromApp({ appRoot } = {}) {
|
|
294
337
|
const resolvedAppRoot = resolveRequiredAppRoot(appRoot, {
|
|
295
|
-
context: "
|
|
338
|
+
context: "discoverPlacementTopologyFromApp"
|
|
339
|
+
});
|
|
340
|
+
const appPlacements = await loadAppPlacementTopology(resolvedAppRoot);
|
|
341
|
+
const packagePlacements = await collectInstalledPackagePlacementTopology(resolvedAppRoot);
|
|
342
|
+
const placementByKey = new Map();
|
|
343
|
+
|
|
344
|
+
for (const placement of [...packagePlacements, ...appPlacements]) {
|
|
345
|
+
const key = `${placement.id}::${placement.owner || ""}`;
|
|
346
|
+
placementByKey.set(key, placement);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return Object.freeze({
|
|
350
|
+
placements: Object.freeze(
|
|
351
|
+
[...placementByKey.values()].sort((left, right) => {
|
|
352
|
+
const idCompare = left.id.localeCompare(right.id);
|
|
353
|
+
if (idCompare !== 0) {
|
|
354
|
+
return idCompare;
|
|
355
|
+
}
|
|
356
|
+
return String(left.owner || "").localeCompare(String(right.owner || ""));
|
|
357
|
+
})
|
|
358
|
+
)
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function findSemanticPlacementById(placements = [], { id = "", owner = "", surface = "" } = {}) {
|
|
363
|
+
const normalizedId = normalizeSemanticPlacementId(id);
|
|
364
|
+
const normalizedOwner = normalizeText(owner).toLowerCase();
|
|
365
|
+
const normalizedSurface = normalizeText(surface).toLowerCase();
|
|
366
|
+
if (!normalizedId) {
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return (Array.isArray(placements) ? placements : []).find((placement) => {
|
|
371
|
+
if (placement.id !== normalizedId) {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
if (normalizedOwner && placement.owner !== normalizedOwner) {
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
if (!normalizedOwner && placement.owner) {
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
const surfaces = Array.isArray(placement.surfaces) ? placement.surfaces : ["*"];
|
|
381
|
+
return !normalizedSurface || surfaces.includes("*") || surfaces.includes(normalizedSurface);
|
|
382
|
+
}) || null;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async function resolveSemanticPlacementTargetFromApp({
|
|
386
|
+
appRoot,
|
|
387
|
+
placement = "",
|
|
388
|
+
owner = "",
|
|
389
|
+
surface = "",
|
|
390
|
+
context = "ui-generator"
|
|
391
|
+
} = {}) {
|
|
392
|
+
const resolvedContext = normalizeText(context) || "ui-generator";
|
|
393
|
+
const topology = await discoverPlacementTopologyFromApp({ appRoot });
|
|
394
|
+
const placements = Array.isArray(topology.placements) ? topology.placements : [];
|
|
395
|
+
if (placements.length < 1) {
|
|
396
|
+
throw new Error(`${resolvedContext} could not find semantic placement topology in ${PLACEMENT_TOPOLOGY_RELATIVE_PATH}.`);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const requestedPlacement = normalizeText(placement);
|
|
400
|
+
if (requestedPlacement) {
|
|
401
|
+
const requestedId = normalizeSemanticPlacementId(requestedPlacement);
|
|
402
|
+
if (!requestedId) {
|
|
403
|
+
throw new Error(`${resolvedContext} option "placement" must be a semantic target in "area.slot" format.`);
|
|
404
|
+
}
|
|
405
|
+
const match = findSemanticPlacementById(placements, {
|
|
406
|
+
id: requestedId,
|
|
407
|
+
owner,
|
|
408
|
+
surface
|
|
409
|
+
}) || (owner
|
|
410
|
+
? findSemanticPlacementById(placements, {
|
|
411
|
+
id: requestedId,
|
|
412
|
+
owner: "",
|
|
413
|
+
surface
|
|
414
|
+
})
|
|
415
|
+
: null);
|
|
416
|
+
if (!match) {
|
|
417
|
+
throw new Error(`${resolvedContext} semantic placement "${requestedId}" is not declared in app placement topology.`);
|
|
418
|
+
}
|
|
419
|
+
return match;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const defaultPlacement =
|
|
423
|
+
placements.find((entry) => entry.default === true && (!surface || entry.surfaces.includes("*") || entry.surfaces.includes(surface))) ||
|
|
424
|
+
placements.find((entry) => !entry.owner && (!surface || entry.surfaces.includes("*") || entry.surfaces.includes(surface))) ||
|
|
425
|
+
null;
|
|
426
|
+
if (defaultPlacement) {
|
|
427
|
+
return defaultPlacement;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
throw new Error(`${resolvedContext} could not resolve a default semantic placement target.`);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async function collectAppSourceShellOutletTargets({
|
|
434
|
+
appRoot,
|
|
435
|
+
sourceRoot = "src",
|
|
436
|
+
enforceSingleDefault = true,
|
|
437
|
+
context = "discoverShellOutletTargetsFromApp"
|
|
438
|
+
} = {}) {
|
|
439
|
+
const resolvedAppRoot = resolveRequiredAppRoot(appRoot, {
|
|
440
|
+
context
|
|
296
441
|
});
|
|
297
|
-
const outletDefaultOverrides = await loadOutletDefaultOverrides(resolvedAppRoot);
|
|
298
442
|
|
|
299
443
|
const sourceDirectory = path.resolve(resolvedAppRoot, String(sourceRoot || "src"));
|
|
300
444
|
const targetById = new Map();
|
|
@@ -311,12 +455,14 @@ async function discoverShellOutletTargetsFromApp({ appRoot, sourceRoot = "src" }
|
|
|
311
455
|
|
|
312
456
|
const discoveredShellOutlets = source.includes("<ShellOutlet")
|
|
313
457
|
? discoverShellOutletTargetsFromVueSource(source, {
|
|
314
|
-
context: relativePath
|
|
458
|
+
context: relativePath,
|
|
459
|
+
enforceSingleDefault
|
|
315
460
|
})
|
|
316
461
|
: { targets: [], defaultTargetId: "" };
|
|
317
462
|
const discoveredRouteMetaOutlets = source.includes("<route")
|
|
318
463
|
? discoverRouteMetaOutletTargetsFromVueSource(source, {
|
|
319
|
-
context: relativePath
|
|
464
|
+
context: relativePath,
|
|
465
|
+
enforceSingleDefault
|
|
320
466
|
})
|
|
321
467
|
: { targets: [], defaultTargetId: "" };
|
|
322
468
|
const discoveredTargets = [
|
|
@@ -342,18 +488,37 @@ async function discoverShellOutletTargetsFromApp({ appRoot, sourceRoot = "src" }
|
|
|
342
488
|
normalizeShellOutletTargetId(discoveredRouteMetaOutlets.defaultTargetId)
|
|
343
489
|
].filter(Boolean);
|
|
344
490
|
for (const discoveredDefaultTargetId of discoveredDefaultTargetIds) {
|
|
345
|
-
if (defaultTargetId && discoveredDefaultTargetId !== defaultTargetId) {
|
|
491
|
+
if (enforceSingleDefault === true && defaultTargetId && discoveredDefaultTargetId !== defaultTargetId) {
|
|
346
492
|
throw new Error(
|
|
347
493
|
`Multiple default ShellOutlet targets found in app source: "${defaultTargetId}" (${defaultTargetSource}) and ` +
|
|
348
494
|
`"${discoveredDefaultTargetId}" (${relativePath}).`
|
|
349
495
|
);
|
|
350
496
|
}
|
|
351
497
|
|
|
352
|
-
defaultTargetId
|
|
353
|
-
|
|
498
|
+
if (!defaultTargetId) {
|
|
499
|
+
defaultTargetId = discoveredDefaultTargetId;
|
|
500
|
+
defaultTargetSource = relativePath;
|
|
501
|
+
}
|
|
354
502
|
}
|
|
355
503
|
}
|
|
356
504
|
|
|
505
|
+
return Object.freeze({
|
|
506
|
+
resolvedAppRoot,
|
|
507
|
+
targetById,
|
|
508
|
+
defaultTargetId
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
async function discoverShellOutletTargetsFromApp({ appRoot, sourceRoot = "src" } = {}) {
|
|
513
|
+
const discoveredAppTargets = await collectAppSourceShellOutletTargets({
|
|
514
|
+
appRoot,
|
|
515
|
+
sourceRoot,
|
|
516
|
+
enforceSingleDefault: true,
|
|
517
|
+
context: "discoverShellOutletTargetsFromApp"
|
|
518
|
+
});
|
|
519
|
+
const resolvedAppRoot = discoveredAppTargets.resolvedAppRoot;
|
|
520
|
+
const targetById = new Map(discoveredAppTargets.targetById);
|
|
521
|
+
|
|
357
522
|
const packageTargets = await collectInstalledPackageOutletTargets(resolvedAppRoot);
|
|
358
523
|
for (const target of packageTargets) {
|
|
359
524
|
if (!targetById.has(target.id)) {
|
|
@@ -363,15 +528,36 @@ async function discoverShellOutletTargetsFromApp({ appRoot, sourceRoot = "src" }
|
|
|
363
528
|
|
|
364
529
|
const targets = [...targetById.values()].sort((left, right) => left.id.localeCompare(right.id));
|
|
365
530
|
const normalizedTargets = targets.map((target) =>
|
|
366
|
-
|
|
531
|
+
Object.freeze({
|
|
367
532
|
...target,
|
|
368
|
-
default: target.id === defaultTargetId
|
|
369
|
-
}
|
|
533
|
+
default: target.id === discoveredAppTargets.defaultTargetId
|
|
534
|
+
})
|
|
370
535
|
);
|
|
371
536
|
|
|
372
537
|
return Object.freeze({
|
|
373
538
|
targets: Object.freeze(normalizedTargets),
|
|
374
|
-
defaultTargetId
|
|
539
|
+
defaultTargetId: discoveredAppTargets.defaultTargetId
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
async function discoverShellOutletSourcePathsFromApp({ appRoot, sourceRoot = "src" } = {}) {
|
|
544
|
+
const discoveredAppTargets = await collectAppSourceShellOutletTargets({
|
|
545
|
+
appRoot,
|
|
546
|
+
sourceRoot,
|
|
547
|
+
enforceSingleDefault: false,
|
|
548
|
+
context: "discoverShellOutletSourcePathsFromApp"
|
|
549
|
+
});
|
|
550
|
+
const targetById = new Map(discoveredAppTargets.targetById);
|
|
551
|
+
|
|
552
|
+
const packageTargets = await collectInstalledPackageOutletTargets(discoveredAppTargets.resolvedAppRoot);
|
|
553
|
+
for (const target of packageTargets) {
|
|
554
|
+
if (!targetById.has(target.id)) {
|
|
555
|
+
targetById.set(target.id, target);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return Object.freeze({
|
|
560
|
+
targets: Object.freeze([...targetById.values()].sort((left, right) => left.id.localeCompare(right.id)))
|
|
375
561
|
});
|
|
376
562
|
}
|
|
377
563
|
|
|
@@ -418,6 +604,9 @@ async function resolveShellOutletPlacementTargetFromApp({ appRoot, placement = "
|
|
|
418
604
|
}
|
|
419
605
|
|
|
420
606
|
export {
|
|
607
|
+
discoverPlacementTopologyFromApp,
|
|
608
|
+
discoverShellOutletSourcePathsFromApp,
|
|
421
609
|
discoverShellOutletTargetsFromApp,
|
|
610
|
+
resolveSemanticPlacementTargetFromApp,
|
|
422
611
|
resolveShellOutletPlacementTargetFromApp
|
|
423
612
|
};
|
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import test from "node:test";
|
|
6
6
|
import {
|
|
7
|
+
discoverShellOutletSourcePathsFromApp,
|
|
7
8
|
discoverShellOutletTargetsFromApp,
|
|
8
9
|
resolveShellOutletPlacementTargetFromApp
|
|
9
10
|
} from "./shellOutlets.js";
|
|
@@ -99,7 +100,6 @@ test("discoverShellOutletTargetsFromApp includes installed package placement out
|
|
|
99
100
|
outlets: [
|
|
100
101
|
{
|
|
101
102
|
target: "admin-cog:primary-menu",
|
|
102
|
-
defaultLinkComponentToken: "local.main.ui.surface-aware-menu-link-item",
|
|
103
103
|
source: "src/client/components/UsersWorkspaceToolsWidget.vue"
|
|
104
104
|
}
|
|
105
105
|
]
|
|
@@ -118,7 +118,6 @@ test("discoverShellOutletTargetsFromApp includes installed package placement out
|
|
|
118
118
|
assert.deepEqual(discovered.targets[0], {
|
|
119
119
|
id: "admin-cog:primary-menu",
|
|
120
120
|
default: false,
|
|
121
|
-
defaultLinkComponentToken: "local.main.ui.surface-aware-menu-link-item",
|
|
122
121
|
sourcePath: "package:@example/users-web:src/client/components/UsersWorkspaceToolsWidget.vue",
|
|
123
122
|
sourcePackageId: "@example/users-web"
|
|
124
123
|
});
|
|
@@ -132,68 +131,6 @@ test("discoverShellOutletTargetsFromApp includes installed package placement out
|
|
|
132
131
|
});
|
|
133
132
|
});
|
|
134
133
|
|
|
135
|
-
test("discoverShellOutletTargetsFromApp applies app config default-link overrides by target id", async () => {
|
|
136
|
-
await withTempApp(async (appRoot) => {
|
|
137
|
-
await writeFileInApp(
|
|
138
|
-
appRoot,
|
|
139
|
-
"config/public.js",
|
|
140
|
-
`export const config = {
|
|
141
|
-
ui: {
|
|
142
|
-
outletDefaults: {
|
|
143
|
-
"admin-cog:primary-menu": {
|
|
144
|
-
linkComponentToken: "local.main.ui.surface-aware-menu-link-item"
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
`
|
|
150
|
-
);
|
|
151
|
-
await writeFileInApp(
|
|
152
|
-
appRoot,
|
|
153
|
-
".jskit/lock.json",
|
|
154
|
-
`${JSON.stringify(
|
|
155
|
-
{
|
|
156
|
-
lockVersion: 1,
|
|
157
|
-
installedPackages: {
|
|
158
|
-
"@example/users-web": {
|
|
159
|
-
packageId: "@example/users-web",
|
|
160
|
-
source: {
|
|
161
|
-
type: "npm-installed-package",
|
|
162
|
-
descriptorPath: "node_modules/@example/users-web/package.descriptor.mjs"
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
},
|
|
167
|
-
null,
|
|
168
|
-
2
|
|
169
|
-
)}\n`
|
|
170
|
-
);
|
|
171
|
-
await writeFileInApp(
|
|
172
|
-
appRoot,
|
|
173
|
-
"node_modules/@example/users-web/package.descriptor.mjs",
|
|
174
|
-
`export default {
|
|
175
|
-
packageId: "@example/users-web",
|
|
176
|
-
metadata: {
|
|
177
|
-
ui: {
|
|
178
|
-
placements: {
|
|
179
|
-
outlets: [
|
|
180
|
-
{
|
|
181
|
-
target: "admin-cog:primary-menu",
|
|
182
|
-
defaultLinkComponentToken: "local.main.ui.surface-aware-menu-link-item"
|
|
183
|
-
}
|
|
184
|
-
]
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
|
-
`
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
const discovered = await discoverShellOutletTargetsFromApp({ appRoot });
|
|
193
|
-
assert.equal(discovered.targets[0].defaultLinkComponentToken, "local.main.ui.surface-aware-menu-link-item");
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
|
|
197
134
|
test("discoverShellOutletTargetsFromApp returns targets with sourcePath and default marker", async () => {
|
|
198
135
|
await withTempApp(async (appRoot) => {
|
|
199
136
|
await writeFileInApp(
|
|
@@ -223,13 +160,11 @@ test("discoverShellOutletTargetsFromApp returns targets with sourcePath and defa
|
|
|
223
160
|
{
|
|
224
161
|
id: "admin-toolbox:widgets",
|
|
225
162
|
default: true,
|
|
226
|
-
defaultLinkComponentToken: "",
|
|
227
163
|
sourcePath: "src/pages/admin/toolbox/index.vue"
|
|
228
164
|
},
|
|
229
165
|
{
|
|
230
166
|
id: "shell-layout:primary-menu",
|
|
231
167
|
default: false,
|
|
232
|
-
defaultLinkComponentToken: "",
|
|
233
168
|
sourcePath: "src/components/ShellLayout.vue"
|
|
234
169
|
}
|
|
235
170
|
]);
|
|
@@ -266,7 +201,6 @@ test("discoverShellOutletTargetsFromApp discovers route meta placement outlets",
|
|
|
266
201
|
{
|
|
267
202
|
id: "contact-tools:sub-pages",
|
|
268
203
|
default: false,
|
|
269
|
-
defaultLinkComponentToken: "",
|
|
270
204
|
sourcePath: "src/pages/w/[workspaceSlug]/admin/contacts/[contactId]/contact-tools.vue"
|
|
271
205
|
}
|
|
272
206
|
]);
|
|
@@ -363,6 +297,36 @@ test("resolveShellOutletPlacementTargetFromApp throws when multiple default outl
|
|
|
363
297
|
});
|
|
364
298
|
});
|
|
365
299
|
|
|
300
|
+
test("discoverShellOutletSourcePathsFromApp ignores default conflicts while keeping source paths", async () => {
|
|
301
|
+
await withTempApp(async (appRoot) => {
|
|
302
|
+
await writeFileInApp(
|
|
303
|
+
appRoot,
|
|
304
|
+
"src/components/ShellLayout.vue",
|
|
305
|
+
`<template>
|
|
306
|
+
<div>
|
|
307
|
+
<ShellOutlet target="shell-layout:primary-menu" default />
|
|
308
|
+
<ShellOutlet target="shell-layout:primary-bottom-nav" default />
|
|
309
|
+
</div>
|
|
310
|
+
</template>
|
|
311
|
+
`
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
const discovered = await discoverShellOutletSourcePathsFromApp({ appRoot });
|
|
315
|
+
assert.deepEqual(discovered.targets, [
|
|
316
|
+
{
|
|
317
|
+
id: "shell-layout:primary-bottom-nav",
|
|
318
|
+
default: true,
|
|
319
|
+
sourcePath: "src/components/ShellLayout.vue"
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
id: "shell-layout:primary-menu",
|
|
323
|
+
default: true,
|
|
324
|
+
sourcePath: "src/components/ShellLayout.vue"
|
|
325
|
+
}
|
|
326
|
+
]);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
366
330
|
test("resolveShellOutletPlacementTargetFromApp requires appRoot", async () => {
|
|
367
331
|
await assert.rejects(
|
|
368
332
|
() =>
|