@jskit-ai/jskit-cli 0.2.74 → 0.2.76
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 +4 -4
- package/src/server/cliRuntime/completion.js +40 -4
- package/src/server/cliRuntime/mutations/textMutations.js +8 -2
- package/src/server/cliRuntime/packageIntrospection/placementNormalization.js +19 -5
- package/src/server/commandHandlers/health.js +10 -6
- package/src/server/commandHandlers/list.js +420 -17
- package/src/server/commandHandlers/mobile.js +178 -277
- package/src/server/commandHandlers/mobileCommandCatalog.js +34 -39
- package/src/server/commandHandlers/mobileShellSupport.js +30 -7
- package/src/server/core/argParser.js +6 -0
- package/src/server/core/buildCommandDeps.js +3 -1
- package/src/server/core/commandCatalog.js +18 -12
- package/src/server/core/createCliRunner.js +8 -2
|
@@ -14,6 +14,9 @@ const REGISTER_MAIN_CLIENT_COMPONENT_PATTERN = /registerMainClientComponent\(\s*
|
|
|
14
14
|
const LINK_ITEM_TOKEN_SUFFIX = "link-item";
|
|
15
15
|
const JSKIT_SCOPE_PREFIX = "@jskit-ai/";
|
|
16
16
|
const FEATURE_SERVER_GENERATOR_PACKAGE_ID = "@jskit-ai/feature-server-generator";
|
|
17
|
+
const PLACEMENT_LAYOUT_CLASSES = Object.freeze(["compact", "medium", "expanded"]);
|
|
18
|
+
const PLACEMENT_KIND_COMPONENT = "component";
|
|
19
|
+
const PLACEMENT_KIND_LINK = "link";
|
|
17
20
|
const PROVIDER_SOURCE_EXTENSIONS = new Set([".js", ".mjs", ".cjs", ".ts", ".tsx"]);
|
|
18
21
|
const READ_FILE_IGNORE_ERROR_CODES = new Set(["ENOENT", "ENOTDIR", "EISDIR", "EACCES", "EPERM"]);
|
|
19
22
|
const READ_DIRECTORY_IGNORE_ERROR_CODES = new Set(["ENOENT", "ENOTDIR", "EACCES", "EPERM"]);
|
|
@@ -82,6 +85,331 @@ function isLinkItemToken(token = "") {
|
|
|
82
85
|
return String(token || "").trim().toLowerCase().endsWith(LINK_ITEM_TOKEN_SUFFIX);
|
|
83
86
|
}
|
|
84
87
|
|
|
88
|
+
function collectPlacementRendererKinds(placementTarget = {}) {
|
|
89
|
+
const kinds = new Set();
|
|
90
|
+
const variants = ensureObject(placementTarget.variants);
|
|
91
|
+
for (const layoutClass of PLACEMENT_LAYOUT_CLASSES) {
|
|
92
|
+
const variant = ensureObject(variants[layoutClass]);
|
|
93
|
+
const renderers = ensureObject(variant.renderers);
|
|
94
|
+
for (const kind of Object.keys(renderers)) {
|
|
95
|
+
const normalizedKind = String(kind || "").trim();
|
|
96
|
+
if (normalizedKind) {
|
|
97
|
+
kinds.add(normalizedKind);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (kinds.size < 1) {
|
|
102
|
+
kinds.add(PLACEMENT_KIND_COMPONENT);
|
|
103
|
+
}
|
|
104
|
+
return sortStrings([...kinds]);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function collectPlacementConcreteOutlets(placementTarget = {}) {
|
|
108
|
+
const outlets = new Set();
|
|
109
|
+
const variants = ensureObject(placementTarget.variants);
|
|
110
|
+
for (const layoutClass of PLACEMENT_LAYOUT_CLASSES) {
|
|
111
|
+
const variant = ensureObject(variants[layoutClass]);
|
|
112
|
+
const outlet = String(variant.outlet || "").trim();
|
|
113
|
+
if (outlet) {
|
|
114
|
+
outlets.add(outlet);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return sortStrings([...outlets]);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function classifyPlacementTarget(placementTarget = {}) {
|
|
121
|
+
const owner = String(placementTarget.owner || "").trim();
|
|
122
|
+
const kinds = collectPlacementRendererKinds(placementTarget);
|
|
123
|
+
const acceptsLinks = kinds.includes(PLACEMENT_KIND_LINK);
|
|
124
|
+
const acceptsComponents = kinds.includes(PLACEMENT_KIND_COMPONENT);
|
|
125
|
+
|
|
126
|
+
if (acceptsLinks && acceptsComponents) {
|
|
127
|
+
return owner ? "ownerMixed" : "mixed";
|
|
128
|
+
}
|
|
129
|
+
if (acceptsLinks) {
|
|
130
|
+
return owner ? "ownerLinks" : "links";
|
|
131
|
+
}
|
|
132
|
+
return owner ? "ownerComponents" : "components";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function createConcreteTargetSourcePathMap(concreteTargets = []) {
|
|
136
|
+
const sourcePathByTarget = new Map();
|
|
137
|
+
for (const concreteTarget of ensureArray(concreteTargets)) {
|
|
138
|
+
const target = String(ensureObject(concreteTarget).id || "").trim();
|
|
139
|
+
const sourcePath = String(ensureObject(concreteTarget).sourcePath || "").trim();
|
|
140
|
+
if (target && sourcePath && !sourcePathByTarget.has(target)) {
|
|
141
|
+
sourcePathByTarget.set(target, sourcePath);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return sourcePathByTarget;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function resolveChildPagePatternFromHostSourcePath(sourcePath = "") {
|
|
148
|
+
const normalizedPath = String(sourcePath || "").replaceAll("\\", "/").trim();
|
|
149
|
+
if (!normalizedPath.startsWith("src/pages/")) {
|
|
150
|
+
return "";
|
|
151
|
+
}
|
|
152
|
+
const pagePath = normalizedPath.slice("src/pages/".length);
|
|
153
|
+
if (pagePath.endsWith("/index.vue")) {
|
|
154
|
+
const hostPath = pagePath.slice(0, -"/index.vue".length);
|
|
155
|
+
return hostPath
|
|
156
|
+
? `${hostPath}/<page>.vue or ${hostPath}/<page>/index.vue`
|
|
157
|
+
: "<page>.vue or <page>/index.vue";
|
|
158
|
+
}
|
|
159
|
+
if (pagePath.endsWith(".vue")) {
|
|
160
|
+
const hostPath = pagePath.slice(0, -".vue".length);
|
|
161
|
+
return hostPath
|
|
162
|
+
? `${hostPath}/<page>.vue or ${hostPath}/<page>/index.vue`
|
|
163
|
+
: "<page>.vue or <page>/index.vue";
|
|
164
|
+
}
|
|
165
|
+
return "";
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function resolveOwnerScopedChildPagePattern(placementTarget = {}, concreteSourcePathByTarget = new Map()) {
|
|
169
|
+
const outlets = collectPlacementConcreteOutlets(placementTarget);
|
|
170
|
+
for (const outlet of outlets) {
|
|
171
|
+
const childPagePattern = resolveChildPagePatternFromHostSourcePath(
|
|
172
|
+
concreteSourcePathByTarget.get(outlet) || ""
|
|
173
|
+
);
|
|
174
|
+
if (childPagePattern) {
|
|
175
|
+
return childPagePattern;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return "";
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function createPlacementTargetSummary(placementTarget = {}, color, { concreteSourcePathByTarget = new Map() } = {}) {
|
|
182
|
+
const placementId = String(placementTarget.id || "").trim();
|
|
183
|
+
const owner = String(placementTarget.owner || "").trim();
|
|
184
|
+
const ownerLabel = owner ? color.dim(` [owner:${owner}]`) : "";
|
|
185
|
+
const defaultLabel = placementTarget.default === true ? color.installed(" (default)") : "";
|
|
186
|
+
const description = String(placementTarget.description || "").trim();
|
|
187
|
+
const childPagePattern = owner
|
|
188
|
+
? resolveOwnerScopedChildPagePattern(placementTarget, concreteSourcePathByTarget)
|
|
189
|
+
: "";
|
|
190
|
+
const childPagePatternLabel = childPagePattern ? ` -> ${color.dim(childPagePattern)}` : "";
|
|
191
|
+
const descriptionSuffix = description ? `: ${description}` : "";
|
|
192
|
+
return `- ${color.item(placementId)}${ownerLabel}${defaultLabel}${childPagePatternLabel}${descriptionSuffix}`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function appendPlacementLayoutDetails(lines, placementTarget = {}, color) {
|
|
196
|
+
const variants = ensureObject(placementTarget.variants);
|
|
197
|
+
for (const layoutClass of PLACEMENT_LAYOUT_CLASSES) {
|
|
198
|
+
const variant = ensureObject(variants[layoutClass]);
|
|
199
|
+
const outlet = String(variant.outlet || "").trim();
|
|
200
|
+
if (outlet) {
|
|
201
|
+
lines.push(` ${layoutClass} -> ${color.dim(outlet)}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function collectMappedConcreteOutletIds(semanticPlacements = []) {
|
|
207
|
+
const mapped = new Set();
|
|
208
|
+
for (const placementTarget of ensureArray(semanticPlacements)) {
|
|
209
|
+
const variants = ensureObject(placementTarget.variants);
|
|
210
|
+
for (const layoutClass of PLACEMENT_LAYOUT_CLASSES) {
|
|
211
|
+
const variant = ensureObject(variants[layoutClass]);
|
|
212
|
+
const outlet = String(variant.outlet || "").trim();
|
|
213
|
+
if (outlet) {
|
|
214
|
+
mapped.add(outlet);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return mapped;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function resolveUnmappedConcreteTargets({
|
|
222
|
+
semanticPlacements = [],
|
|
223
|
+
concreteTargets = []
|
|
224
|
+
} = {}) {
|
|
225
|
+
const mappedConcreteOutletIds = collectMappedConcreteOutletIds(semanticPlacements);
|
|
226
|
+
return ensureArray(concreteTargets)
|
|
227
|
+
.map((entry) => ensureObject(entry))
|
|
228
|
+
.filter((entry) => {
|
|
229
|
+
const id = String(entry.id || "").trim();
|
|
230
|
+
return id && !mappedConcreteOutletIds.has(id);
|
|
231
|
+
})
|
|
232
|
+
.sort((left, right) => String(left.id || "").localeCompare(String(right.id || "")));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function formatPlacementGuidanceLine(line = "", color) {
|
|
236
|
+
const guidanceLine = String(line || "").trim();
|
|
237
|
+
const labelMatch = /^([^:]+):\s*(.*)$/u.exec(guidanceLine);
|
|
238
|
+
if (!labelMatch) {
|
|
239
|
+
return ` ${color.dim(guidanceLine)}`;
|
|
240
|
+
}
|
|
241
|
+
const label = String(labelMatch[1] || "").trim();
|
|
242
|
+
const value = String(labelMatch[2] || "").trim();
|
|
243
|
+
const renderedLabel = color.installed(`${label}:`);
|
|
244
|
+
const renderedValue = value ? ` ${color.provider(value)}` : "";
|
|
245
|
+
return ` ${renderedLabel}${renderedValue}`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function appendPlacementGroup(lines, {
|
|
249
|
+
color,
|
|
250
|
+
title = "",
|
|
251
|
+
description = "",
|
|
252
|
+
guidance = [],
|
|
253
|
+
targets = [],
|
|
254
|
+
concreteSourcePathByTarget = new Map(),
|
|
255
|
+
showLayoutDetails = false
|
|
256
|
+
} = {}) {
|
|
257
|
+
const placementTargets = ensureArray(targets);
|
|
258
|
+
if (placementTargets.length < 1) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (lines.length > 1) {
|
|
262
|
+
lines.push("");
|
|
263
|
+
}
|
|
264
|
+
lines.push(color.heading(title));
|
|
265
|
+
if (description) {
|
|
266
|
+
lines.push(description);
|
|
267
|
+
}
|
|
268
|
+
const guidanceLines = ensureArray(guidance).map((value) => String(value || "").trim()).filter(Boolean);
|
|
269
|
+
for (const guidanceLine of guidanceLines) {
|
|
270
|
+
lines.push(formatPlacementGuidanceLine(guidanceLine, color));
|
|
271
|
+
}
|
|
272
|
+
for (const placementTarget of placementTargets) {
|
|
273
|
+
lines.push(createPlacementTargetSummary(placementTarget, color, { concreteSourcePathByTarget }));
|
|
274
|
+
if (showLayoutDetails) {
|
|
275
|
+
appendPlacementLayoutDetails(lines, placementTarget, color);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function appendUnmappedConcreteTargetWarnings(lines, {
|
|
281
|
+
color,
|
|
282
|
+
concreteTargets = []
|
|
283
|
+
} = {}) {
|
|
284
|
+
const targets = ensureArray(concreteTargets);
|
|
285
|
+
if (targets.length < 1) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (lines.length > 1) {
|
|
290
|
+
lines.push("");
|
|
291
|
+
}
|
|
292
|
+
lines.push(color.heading("Unmapped concrete outlets:"));
|
|
293
|
+
lines.push(
|
|
294
|
+
"These concrete ShellOutlet recipients are discoverable but are not reached by semantic topology. Add a semantic mapping or keep them private/internal."
|
|
295
|
+
);
|
|
296
|
+
lines.push(
|
|
297
|
+
color.dim(
|
|
298
|
+
"Format: npx jskit generate ui-generator topology --placement <area.slot> --kind <link|component> --compact-target <host:position> --medium-target <host:position> --expanded-target <host:position>"
|
|
299
|
+
)
|
|
300
|
+
);
|
|
301
|
+
for (const placementTarget of targets) {
|
|
302
|
+
const placementId = String(placementTarget.id || "").trim();
|
|
303
|
+
const sourcePath = String(placementTarget.sourcePath || "").trim();
|
|
304
|
+
const sourceLabel = sourcePath ? ` ${color.dim(`[${sourcePath}]`)}` : "";
|
|
305
|
+
lines.push(`- ${color.item(placementId)}${sourceLabel}`);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function appendSemanticPlacementGroups(lines, {
|
|
310
|
+
color,
|
|
311
|
+
semanticPlacements = [],
|
|
312
|
+
concreteTargets = [],
|
|
313
|
+
showLayoutDetails = false
|
|
314
|
+
} = {}) {
|
|
315
|
+
const concreteSourcePathByTarget = createConcreteTargetSourcePathMap(concreteTargets);
|
|
316
|
+
const groupedTargets = {
|
|
317
|
+
links: [],
|
|
318
|
+
ownerLinks: [],
|
|
319
|
+
components: [],
|
|
320
|
+
ownerComponents: [],
|
|
321
|
+
mixed: [],
|
|
322
|
+
ownerMixed: []
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
for (const placementTarget of ensureArray(semanticPlacements)) {
|
|
326
|
+
const groupKey = classifyPlacementTarget(placementTarget);
|
|
327
|
+
if (groupedTargets[groupKey]) {
|
|
328
|
+
groupedTargets[groupKey].push(placementTarget);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
appendPlacementGroup(lines, {
|
|
333
|
+
color,
|
|
334
|
+
title: "Navigation link placements",
|
|
335
|
+
guidance: [
|
|
336
|
+
'Format: npx jskit generate ui-generator page <page-file> --name "Label" --link-placement <placement>',
|
|
337
|
+
'Example: npx jskit generate ui-generator page admin/reports/index.vue --name "Reports" --link-placement admin.tools-menu'
|
|
338
|
+
],
|
|
339
|
+
targets: groupedTargets.links,
|
|
340
|
+
concreteSourcePathByTarget,
|
|
341
|
+
showLayoutDetails
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
appendPlacementGroup(lines, {
|
|
345
|
+
color,
|
|
346
|
+
title: "Owner-scoped navigation link placements",
|
|
347
|
+
guidance: [
|
|
348
|
+
'Format: npx jskit generate ui-generator page <host-path>/<page>.vue --name "Label"',
|
|
349
|
+
'Alternative: npx jskit generate ui-generator page <host-path>/<page>/index.vue --name "Label"',
|
|
350
|
+
'Example: npx jskit generate ui-generator page home/settings/profile.vue --name "Profile"'
|
|
351
|
+
],
|
|
352
|
+
targets: groupedTargets.ownerLinks,
|
|
353
|
+
concreteSourcePathByTarget,
|
|
354
|
+
showLayoutDetails
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
appendPlacementGroup(lines, {
|
|
358
|
+
color,
|
|
359
|
+
title: "Component, widget, and section placements",
|
|
360
|
+
guidance: [
|
|
361
|
+
'Format: npx jskit generate ui-generator placed-element --name "Widget Name" --placement <placement>',
|
|
362
|
+
'Example: npx jskit generate ui-generator placed-element --name "Connection Status" --placement shell.status'
|
|
363
|
+
],
|
|
364
|
+
targets: groupedTargets.components,
|
|
365
|
+
concreteSourcePathByTarget,
|
|
366
|
+
showLayoutDetails
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
appendPlacementGroup(lines, {
|
|
370
|
+
color,
|
|
371
|
+
title: "Owner-scoped component and section placements",
|
|
372
|
+
guidance: [
|
|
373
|
+
'Format: npx jskit generate ui-generator placed-element --name "Section Name" --placement <placement> --owner <owner>',
|
|
374
|
+
'Example: npx jskit generate ui-generator placed-element --name "Security Section" --placement settings.sections --owner account-settings'
|
|
375
|
+
],
|
|
376
|
+
targets: groupedTargets.ownerComponents,
|
|
377
|
+
concreteSourcePathByTarget,
|
|
378
|
+
showLayoutDetails
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
appendPlacementGroup(lines, {
|
|
382
|
+
color,
|
|
383
|
+
title: "Mixed-kind placements",
|
|
384
|
+
description: "These topology entries accept more than one semantic kind. Choose the generator by what you are adding.",
|
|
385
|
+
guidance: [
|
|
386
|
+
'Link format: npx jskit generate ui-generator page <page-file> --name "Label" --link-placement <placement>',
|
|
387
|
+
'Component format: npx jskit generate ui-generator placed-element --name "Widget Name" --placement <placement>',
|
|
388
|
+
'Link example: npx jskit generate ui-generator page admin/reports/index.vue --name "Reports" --link-placement <placement>',
|
|
389
|
+
'Component example: npx jskit generate ui-generator placed-element --name "Ops Panel" --placement <placement>'
|
|
390
|
+
],
|
|
391
|
+
targets: groupedTargets.mixed,
|
|
392
|
+
concreteSourcePathByTarget,
|
|
393
|
+
showLayoutDetails
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
appendPlacementGroup(lines, {
|
|
397
|
+
color,
|
|
398
|
+
title: "Owner-scoped mixed-kind placements",
|
|
399
|
+
description: "These owner-scoped entries accept more than one semantic kind. Include the owner when adding manual placement entries.",
|
|
400
|
+
guidance: [
|
|
401
|
+
'Link format: npx jskit generate ui-generator page <host-path>/<page>.vue --name "Label"',
|
|
402
|
+
'Link alternative: npx jskit generate ui-generator page <host-path>/<page>/index.vue --name "Label"',
|
|
403
|
+
'Component format: npx jskit generate ui-generator placed-element --name "Widget Name" --placement <placement> --owner <owner>',
|
|
404
|
+
'Link example: npx jskit generate ui-generator page home/settings/profile.vue --name "Profile"',
|
|
405
|
+
'Component example: npx jskit generate ui-generator placed-element --name "Security Section" --placement <placement> --owner <owner>'
|
|
406
|
+
],
|
|
407
|
+
targets: groupedTargets.ownerMixed,
|
|
408
|
+
concreteSourcePathByTarget,
|
|
409
|
+
showLayoutDetails
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
|
|
85
413
|
async function readFileIfExists(filePath = "") {
|
|
86
414
|
try {
|
|
87
415
|
return await readFile(filePath, "utf8");
|
|
@@ -177,6 +505,8 @@ function createListCommands(ctx = {}) {
|
|
|
177
505
|
loadBundleRegistry,
|
|
178
506
|
loadAppLocalPackageRegistry,
|
|
179
507
|
resolveInstalledNodeModulePackageEntry,
|
|
508
|
+
discoverPlacementTopologyFromApp,
|
|
509
|
+
discoverShellOutletSourcePathsFromApp,
|
|
180
510
|
discoverShellOutletTargetsFromApp,
|
|
181
511
|
normalizePlacementContributions,
|
|
182
512
|
resolvePackageKind
|
|
@@ -435,18 +765,66 @@ function createListCommands(ctx = {}) {
|
|
|
435
765
|
|
|
436
766
|
async function commandListPlacements({ options, cwd, stdout }) {
|
|
437
767
|
const appRoot = await resolveAppRootFromCwd(cwd);
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
const
|
|
768
|
+
const showConcreteOnly = options.concrete === true && options.all !== true;
|
|
769
|
+
const showConcrete = options.concrete === true || options.all === true;
|
|
770
|
+
const showSemantic = showConcreteOnly !== true;
|
|
771
|
+
const showLayoutDetails = options.details === true;
|
|
772
|
+
const shouldLookupHostPaths = showSemantic;
|
|
773
|
+
const discoveredTopology = await discoverPlacementTopologyFromApp({ appRoot });
|
|
774
|
+
const semanticPlacements = ensureArray(discoveredTopology.placements)
|
|
775
|
+
.map((entry) => ensureObject(entry))
|
|
776
|
+
.filter((entry) => String(entry.id || "").trim())
|
|
777
|
+
.sort((left, right) => {
|
|
778
|
+
const idCompare = String(left.id || "").localeCompare(String(right.id || ""));
|
|
779
|
+
if (idCompare !== 0) {
|
|
780
|
+
return idCompare;
|
|
781
|
+
}
|
|
782
|
+
return String(left.owner || "").localeCompare(String(right.owner || ""));
|
|
783
|
+
});
|
|
784
|
+
let discoveredConcrete = { targets: [] };
|
|
785
|
+
if (showConcrete) {
|
|
786
|
+
discoveredConcrete = await discoverShellOutletTargetsFromApp({
|
|
787
|
+
appRoot,
|
|
788
|
+
sourceRoot: "src"
|
|
789
|
+
});
|
|
790
|
+
} else if (shouldLookupHostPaths) {
|
|
791
|
+
discoveredConcrete = await discoverShellOutletSourcePathsFromApp({
|
|
792
|
+
appRoot,
|
|
793
|
+
sourceRoot: "src"
|
|
794
|
+
});
|
|
795
|
+
}
|
|
796
|
+
const concreteTargets = ensureArray(discoveredConcrete.targets)
|
|
443
797
|
.map((entry) => ensureObject(entry))
|
|
444
798
|
.filter((entry) => String(entry.id || "").trim())
|
|
445
799
|
.sort((left, right) => String(left.id || "").localeCompare(String(right.id || "")));
|
|
800
|
+
const unmappedConcreteTargets = showSemantic
|
|
801
|
+
? resolveUnmappedConcreteTargets({
|
|
802
|
+
semanticPlacements,
|
|
803
|
+
concreteTargets
|
|
804
|
+
})
|
|
805
|
+
: [];
|
|
446
806
|
|
|
447
807
|
if (options.json) {
|
|
448
808
|
const payload = {
|
|
449
|
-
placements:
|
|
809
|
+
placements: showSemantic
|
|
810
|
+
? semanticPlacements.map((placementTarget) => ({
|
|
811
|
+
target: String(placementTarget.id || "").trim(),
|
|
812
|
+
owner: String(placementTarget.owner || "").trim(),
|
|
813
|
+
default: placementTarget.default === true,
|
|
814
|
+
description: String(placementTarget.description || "").trim(),
|
|
815
|
+
surfaces: ensureArray(placementTarget.surfaces).map((entry) => String(entry || "").trim()).filter(Boolean),
|
|
816
|
+
variants: ensureObject(placementTarget.variants),
|
|
817
|
+
sourcePath: String(placementTarget.sourcePath || "").trim()
|
|
818
|
+
}))
|
|
819
|
+
: [],
|
|
820
|
+
concretePlacements: showConcrete
|
|
821
|
+
? concreteTargets.map((placementTarget) => ({
|
|
822
|
+
target: String(placementTarget.id || "").trim(),
|
|
823
|
+
default: placementTarget.default === true,
|
|
824
|
+
sourcePath: String(placementTarget.sourcePath || "").trim()
|
|
825
|
+
}))
|
|
826
|
+
: [],
|
|
827
|
+
unmappedConcretePlacements: unmappedConcreteTargets.map((placementTarget) => ({
|
|
450
828
|
target: String(placementTarget.id || "").trim(),
|
|
451
829
|
default: placementTarget.default === true,
|
|
452
830
|
sourcePath: String(placementTarget.sourcePath || "").trim()
|
|
@@ -457,17 +835,42 @@ function createListCommands(ctx = {}) {
|
|
|
457
835
|
}
|
|
458
836
|
|
|
459
837
|
const color = createColorFormatter(stdout);
|
|
460
|
-
const lines = [
|
|
461
|
-
if (
|
|
462
|
-
lines.push("
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
838
|
+
const lines = [];
|
|
839
|
+
if (showSemantic) {
|
|
840
|
+
lines.push(color.heading("Available placements:"));
|
|
841
|
+
lines.push("Semantic placement targets are the stable IDs you should author against. Use --concrete only for low-level ShellOutlet recipients.");
|
|
842
|
+
if (semanticPlacements.length < 1) {
|
|
843
|
+
lines.push("- none");
|
|
844
|
+
} else {
|
|
845
|
+
appendSemanticPlacementGroups(lines, {
|
|
846
|
+
color,
|
|
847
|
+
semanticPlacements,
|
|
848
|
+
concreteTargets,
|
|
849
|
+
showLayoutDetails
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
appendUnmappedConcreteTargetWarnings(lines, {
|
|
853
|
+
color,
|
|
854
|
+
concreteTargets: unmappedConcreteTargets
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
if (showConcrete) {
|
|
859
|
+
if (lines.length > 0) {
|
|
860
|
+
lines.push("");
|
|
861
|
+
}
|
|
862
|
+
lines.push(color.heading("Available concrete outlets:"));
|
|
863
|
+
if (concreteTargets.length < 1) {
|
|
864
|
+
lines.push("- none");
|
|
865
|
+
} else {
|
|
866
|
+
for (const placementTarget of concreteTargets) {
|
|
867
|
+
const placementId = String(placementTarget.id || "").trim();
|
|
868
|
+
const sourcePath = String(placementTarget.sourcePath || "").trim();
|
|
869
|
+
const isDefault = placementTarget.default === true;
|
|
870
|
+
const defaultLabel = isDefault ? color.installed(" (default)") : "";
|
|
871
|
+
const sourceLabel = sourcePath ? ` ${color.dim(`[${sourcePath}]`)}` : "";
|
|
872
|
+
lines.push(`- ${color.item(placementId)}${defaultLabel}${sourceLabel}`);
|
|
873
|
+
}
|
|
471
874
|
}
|
|
472
875
|
}
|
|
473
876
|
|