@jskit-ai/ui-generator 0.1.5 → 0.1.7
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/README.md +104 -4
- package/package.descriptor.mjs +72 -5
- package/package.json +2 -2
- package/src/server/buildTemplateContext.js +117 -1
- package/src/server/subcommands/container.js +259 -96
- package/src/server/subcommands/outlet.js +274 -0
- package/test/buildTemplateContext.test.js +43 -0
- package/test/containerSubcommand.test.js +182 -8
- package/test/outletSubcommand.test.js +219 -0
|
@@ -24,8 +24,27 @@ import {
|
|
|
24
24
|
|
|
25
25
|
const CONTAINER_OUTLET_POSITION = "sub-pages";
|
|
26
26
|
const SECTION_CONTAINER_SHELL_COMPONENT = "SectionContainerShell";
|
|
27
|
-
const
|
|
28
|
-
const
|
|
27
|
+
const TAB_LINK_COMPONENT = "TabLinkItem";
|
|
28
|
+
const TAB_LINK_COMPONENT_TOKEN = "local.main.ui.tab-link-item";
|
|
29
|
+
const ROUTE_TAG_PATTERN = /<route\b([^>]*)>([\s\S]*?)<\/route>/;
|
|
30
|
+
const ATTRIBUTE_PATTERN = /([:@]?[A-Za-z_][A-Za-z0-9_-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'))?/g;
|
|
31
|
+
|
|
32
|
+
function isBracketRouteParamSegment(value = "") {
|
|
33
|
+
return value.startsWith("[") && value.endsWith("]");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function normalizeRoutePrefixSegment(value = "") {
|
|
37
|
+
const source = normalizeText(value);
|
|
38
|
+
if (!source) {
|
|
39
|
+
return "";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (isBracketRouteParamSegment(source)) {
|
|
43
|
+
return source;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return toKebabCase(source);
|
|
47
|
+
}
|
|
29
48
|
|
|
30
49
|
function normalizeRoutePrefix(value = "") {
|
|
31
50
|
const source = normalizeText(value).replaceAll("\\", "/");
|
|
@@ -35,11 +54,160 @@ function normalizeRoutePrefix(value = "") {
|
|
|
35
54
|
|
|
36
55
|
const parts = source
|
|
37
56
|
.split("/")
|
|
38
|
-
.map((entry) =>
|
|
57
|
+
.map((entry) => normalizeRoutePrefixSegment(entry))
|
|
39
58
|
.filter(Boolean);
|
|
40
59
|
return parts.join("/");
|
|
41
60
|
}
|
|
42
61
|
|
|
62
|
+
function resolveContainerRoutePath({ name = "", routePath = "" } = {}) {
|
|
63
|
+
const rawRoutePath = normalizeText(routePath);
|
|
64
|
+
const normalizedRoutePath = normalizeRoutePrefix(routePath);
|
|
65
|
+
if (rawRoutePath && !normalizedRoutePath) {
|
|
66
|
+
throw new Error("ui-generator container requires a valid --route-path when provided.");
|
|
67
|
+
}
|
|
68
|
+
if (normalizedRoutePath) {
|
|
69
|
+
return normalizedRoutePath;
|
|
70
|
+
}
|
|
71
|
+
return toKebabCase(name);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function parseTagAttributes(attributesSource = "") {
|
|
75
|
+
const attributes = {};
|
|
76
|
+
const source = String(attributesSource || "");
|
|
77
|
+
for (const match of source.matchAll(ATTRIBUTE_PATTERN)) {
|
|
78
|
+
const attributeName = normalizeText(match[1]);
|
|
79
|
+
if (!attributeName) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const hasValue = match[2] != null || match[3] != null;
|
|
84
|
+
const attributeValue = hasValue ? String(match[2] ?? match[3] ?? "") : true;
|
|
85
|
+
attributes[attributeName] = attributeValue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return attributes;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function createContainerRouteMeta({
|
|
92
|
+
surface = "",
|
|
93
|
+
containerHost = "",
|
|
94
|
+
containerPosition = CONTAINER_OUTLET_POSITION
|
|
95
|
+
} = {}) {
|
|
96
|
+
return {
|
|
97
|
+
meta: {
|
|
98
|
+
jskit: {
|
|
99
|
+
surface,
|
|
100
|
+
placements: {
|
|
101
|
+
outlets: [
|
|
102
|
+
{
|
|
103
|
+
host: containerHost,
|
|
104
|
+
position: containerPosition
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function renderContainerRouteMetaBlock(routeMeta = {}) {
|
|
114
|
+
return `<route lang="json">
|
|
115
|
+
${JSON.stringify(routeMeta, null, 2)}
|
|
116
|
+
</route>
|
|
117
|
+
`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function normalizeOutletTargetId(outlet = {}) {
|
|
121
|
+
const host = normalizeText(outlet?.host);
|
|
122
|
+
const position = normalizeText(outlet?.position);
|
|
123
|
+
if (!host || !position) {
|
|
124
|
+
return "";
|
|
125
|
+
}
|
|
126
|
+
return `${host}:${position}`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function ensureContainerRouteMetaOutlets(source = "", { surface = "", containerHost = "", containerPosition = "" } = {}) {
|
|
130
|
+
const sourceText = String(source || "");
|
|
131
|
+
const routeTagMatch = ROUTE_TAG_PATTERN.exec(sourceText);
|
|
132
|
+
const expectedTargetId = normalizeOutletTargetId({
|
|
133
|
+
host: containerHost,
|
|
134
|
+
position: containerPosition
|
|
135
|
+
});
|
|
136
|
+
if (!expectedTargetId) {
|
|
137
|
+
return {
|
|
138
|
+
changed: false,
|
|
139
|
+
content: sourceText
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!routeTagMatch) {
|
|
144
|
+
const appendedContent = `${sourceText.trimEnd()}\n\n${renderContainerRouteMetaBlock(createContainerRouteMeta({
|
|
145
|
+
surface,
|
|
146
|
+
containerHost,
|
|
147
|
+
containerPosition
|
|
148
|
+
}))}`;
|
|
149
|
+
return {
|
|
150
|
+
changed: appendedContent !== sourceText,
|
|
151
|
+
content: appendedContent
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const routeTagAttributes = parseTagAttributes(routeTagMatch[1]);
|
|
156
|
+
const routeTagLanguage = normalizeText(routeTagAttributes.lang).toLowerCase();
|
|
157
|
+
if (routeTagLanguage && routeTagLanguage !== "json") {
|
|
158
|
+
return {
|
|
159
|
+
changed: false,
|
|
160
|
+
content: sourceText
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let routeMetaRecord = null;
|
|
165
|
+
try {
|
|
166
|
+
routeMetaRecord = JSON.parse(String(routeTagMatch[2] || "").trim());
|
|
167
|
+
} catch {
|
|
168
|
+
return {
|
|
169
|
+
changed: false,
|
|
170
|
+
content: sourceText
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const routeMeta = normalizeObject(routeMetaRecord);
|
|
175
|
+
const metadata = normalizeObject(routeMeta.meta);
|
|
176
|
+
const jskitMetadata = normalizeObject(metadata.jskit);
|
|
177
|
+
const placementsMetadata = normalizeObject(jskitMetadata.placements);
|
|
178
|
+
const outletRecords = Array.isArray(placementsMetadata.outlets) ? [...placementsMetadata.outlets] : [];
|
|
179
|
+
const knownTargetIds = new Set(outletRecords.map((entry) => normalizeOutletTargetId(entry)).filter(Boolean));
|
|
180
|
+
if (!knownTargetIds.has(expectedTargetId)) {
|
|
181
|
+
outletRecords.push({
|
|
182
|
+
host: containerHost,
|
|
183
|
+
position: containerPosition
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const normalizedSurface = normalizeText(jskitMetadata.surface) || normalizeText(surface);
|
|
188
|
+
const nextRouteMeta = {
|
|
189
|
+
...routeMeta,
|
|
190
|
+
meta: {
|
|
191
|
+
...metadata,
|
|
192
|
+
jskit: {
|
|
193
|
+
...jskitMetadata,
|
|
194
|
+
surface: normalizedSurface,
|
|
195
|
+
placements: {
|
|
196
|
+
...placementsMetadata,
|
|
197
|
+
outlets: outletRecords
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
const renderedRouteMeta = renderContainerRouteMetaBlock(nextRouteMeta);
|
|
203
|
+
const replacementContent =
|
|
204
|
+
`${sourceText.slice(0, routeTagMatch.index)}${renderedRouteMeta}${sourceText.slice(routeTagMatch.index + routeTagMatch[0].length)}`;
|
|
205
|
+
return {
|
|
206
|
+
changed: replacementContent !== sourceText,
|
|
207
|
+
content: replacementContent
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
43
211
|
async function loadPublicConfig(appRoot = "") {
|
|
44
212
|
const configPath = path.join(appRoot, "config", "public.js");
|
|
45
213
|
|
|
@@ -144,7 +312,7 @@ const resolvedSubtitle = computed(() => String(props.subtitle || "").trim());
|
|
|
144
312
|
scrollbar-width: thin;
|
|
145
313
|
}
|
|
146
314
|
|
|
147
|
-
.section-container-shell__tabs :deep(.
|
|
315
|
+
.section-container-shell__tabs :deep(.tab-link-item) {
|
|
148
316
|
flex: 0 0 auto;
|
|
149
317
|
}
|
|
150
318
|
|
|
@@ -158,12 +326,15 @@ const resolvedSubtitle = computed(() => String(props.subtitle || "").trim());
|
|
|
158
326
|
`;
|
|
159
327
|
}
|
|
160
328
|
|
|
161
|
-
function
|
|
329
|
+
function renderTabLinkItemSource() {
|
|
162
330
|
return `<script setup>
|
|
163
331
|
import { computed } from "vue";
|
|
164
332
|
import { useRoute } from "vue-router";
|
|
165
333
|
import { usePaths } from "@jskit-ai/users-web/client/composables/usePaths";
|
|
166
|
-
import {
|
|
334
|
+
import {
|
|
335
|
+
normalizeMenuLinkPathname,
|
|
336
|
+
resolveMenuLinkTarget
|
|
337
|
+
} from "@jskit-ai/users-web/client/support/menuLinkTarget";
|
|
167
338
|
|
|
168
339
|
const props = defineProps({
|
|
169
340
|
label: {
|
|
@@ -194,51 +365,25 @@ const props = defineProps({
|
|
|
194
365
|
|
|
195
366
|
const route = useRoute();
|
|
196
367
|
const paths = usePaths();
|
|
197
|
-
const { currentSurfaceId, workspaceSlugFromRoute } = useWorkspaceRouteContext();
|
|
198
|
-
|
|
199
|
-
function normalizePathname(pathname = "") {
|
|
200
|
-
const source = String(pathname || "").trim();
|
|
201
|
-
if (!source) {
|
|
202
|
-
return "";
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const queryIndex = source.indexOf("?");
|
|
206
|
-
const hashIndex = source.indexOf("#");
|
|
207
|
-
const cutoff =
|
|
208
|
-
queryIndex < 0
|
|
209
|
-
? hashIndex
|
|
210
|
-
: hashIndex < 0
|
|
211
|
-
? queryIndex
|
|
212
|
-
: Math.min(queryIndex, hashIndex);
|
|
213
|
-
return cutoff < 0 ? source : source.slice(0, cutoff);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const targetSurfaceId = computed(() => {
|
|
217
|
-
const explicitSurface = String(props.surface || "").trim().toLowerCase();
|
|
218
|
-
if (explicitSurface && explicitSurface !== "*") {
|
|
219
|
-
return explicitSurface;
|
|
220
|
-
}
|
|
221
|
-
return String(currentSurfaceId.value || paths.currentSurfaceId.value || "").trim().toLowerCase();
|
|
222
|
-
});
|
|
223
368
|
|
|
224
369
|
const resolvedTo = computed(() => {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
370
|
+
return resolveMenuLinkTarget({
|
|
371
|
+
to: props.to,
|
|
372
|
+
surface: props.surface,
|
|
373
|
+
currentSurfaceId: paths.currentSurfaceId.value,
|
|
374
|
+
placementContext: paths.placementContext.value,
|
|
375
|
+
workspaceSuffix: props.workspaceSuffix,
|
|
376
|
+
nonWorkspaceSuffix: props.nonWorkspaceSuffix,
|
|
377
|
+
routeParams: route.params || {},
|
|
378
|
+
resolvePagePath(relativePath, options = {}) {
|
|
379
|
+
return paths.page(relativePath, options);
|
|
380
|
+
}
|
|
236
381
|
});
|
|
237
382
|
});
|
|
238
383
|
|
|
239
384
|
const isActive = computed(() => {
|
|
240
|
-
const targetPathname =
|
|
241
|
-
const currentPathname =
|
|
385
|
+
const targetPathname = normalizeMenuLinkPathname(resolvedTo.value);
|
|
386
|
+
const currentPathname = normalizeMenuLinkPathname(route.fullPath || route.path);
|
|
242
387
|
if (!targetPathname || !currentPathname) {
|
|
243
388
|
return false;
|
|
244
389
|
}
|
|
@@ -249,7 +394,7 @@ const isActive = computed(() => {
|
|
|
249
394
|
<template>
|
|
250
395
|
<v-btn
|
|
251
396
|
v-if="resolvedTo"
|
|
252
|
-
class="
|
|
397
|
+
class="tab-link-item text-none"
|
|
253
398
|
:to="resolvedTo"
|
|
254
399
|
rounded="pill"
|
|
255
400
|
size="small"
|
|
@@ -272,6 +417,11 @@ function renderContainerPageSource({
|
|
|
272
417
|
containerPosition = CONTAINER_OUTLET_POSITION,
|
|
273
418
|
sectionContainerComponentImportPath = "/src/components/SectionContainerShell.vue"
|
|
274
419
|
} = {}) {
|
|
420
|
+
const routeMeta = createContainerRouteMeta({
|
|
421
|
+
surface,
|
|
422
|
+
containerHost,
|
|
423
|
+
containerPosition
|
|
424
|
+
});
|
|
275
425
|
return `<script setup>
|
|
276
426
|
import { RouterView } from "vue-router";
|
|
277
427
|
import SectionContainerShell from "${sectionContainerComponentImportPath}";
|
|
@@ -288,15 +438,7 @@ import SectionContainerShell from "${sectionContainerComponentImportPath}";
|
|
|
288
438
|
</SectionContainerShell>
|
|
289
439
|
</template>
|
|
290
440
|
|
|
291
|
-
|
|
292
|
-
{
|
|
293
|
-
"meta": {
|
|
294
|
-
"jskit": {
|
|
295
|
-
"surface": "${surface}"
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
</route>
|
|
441
|
+
${renderContainerRouteMetaBlock(routeMeta)}
|
|
300
442
|
`;
|
|
301
443
|
}
|
|
302
444
|
|
|
@@ -322,18 +464,19 @@ async function runGeneratorSubcommand({
|
|
|
322
464
|
const surface = requireOption(options, "surface", { context: "ui-generator container" }).toLowerCase();
|
|
323
465
|
const routePrefix = normalizeRoutePrefix(options?.["directory-prefix"]);
|
|
324
466
|
const componentDirectory = normalizeText(options?.path) || DEFAULT_COMPONENT_DIRECTORY;
|
|
467
|
+
const containerRoutePath = resolveContainerRoutePath({
|
|
468
|
+
name,
|
|
469
|
+
routePath: options?.["route-path"]
|
|
470
|
+
});
|
|
325
471
|
const containerSlug = toKebabCase(name);
|
|
326
|
-
if (!containerSlug) {
|
|
472
|
+
if (!containerSlug || !containerRoutePath) {
|
|
327
473
|
throw new Error("ui-generator container requires a valid --name.");
|
|
328
474
|
}
|
|
329
475
|
|
|
330
|
-
const routePath = routePrefix ? `${routePrefix}/${
|
|
476
|
+
const routePath = routePrefix ? `${routePrefix}/${containerRoutePath}` : containerRoutePath;
|
|
331
477
|
const pagesDirectory = await resolveSurfacePagesDirectory(resolvedAppRoot, surface);
|
|
332
478
|
const containerFilePath = path.join(pagesDirectory, `${routePath}.vue`);
|
|
333
479
|
const containerRelativePath = toPosixPath(path.relative(resolvedAppRoot, containerFilePath));
|
|
334
|
-
const placementPath = resolvePathWithinApp(resolvedAppRoot, PLACEMENT_FILE, {
|
|
335
|
-
context: "ui-generator container"
|
|
336
|
-
});
|
|
337
480
|
const providerPath = resolvePathWithinApp(resolvedAppRoot, MAIN_CLIENT_PROVIDER_FILE, {
|
|
338
481
|
context: "ui-generator container"
|
|
339
482
|
});
|
|
@@ -346,17 +489,20 @@ async function runGeneratorSubcommand({
|
|
|
346
489
|
);
|
|
347
490
|
const sectionTabLinkPath = resolvePathWithinApp(
|
|
348
491
|
resolvedAppRoot,
|
|
349
|
-
path.join(componentDirectory, `${
|
|
492
|
+
path.join(componentDirectory, `${TAB_LINK_COMPONENT}.vue`),
|
|
350
493
|
{
|
|
351
494
|
context: "ui-generator container"
|
|
352
495
|
}
|
|
353
496
|
);
|
|
354
497
|
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
498
|
+
const placementOption = normalizeText(options?.placement);
|
|
499
|
+
const placementTarget = placementOption
|
|
500
|
+
? await resolveShellOutletPlacementTargetFromApp({
|
|
501
|
+
appRoot: resolvedAppRoot,
|
|
502
|
+
context: "ui-generator container",
|
|
503
|
+
placement: placementOption
|
|
504
|
+
})
|
|
505
|
+
: null;
|
|
360
506
|
|
|
361
507
|
const touchedFiles = new Set();
|
|
362
508
|
|
|
@@ -383,7 +529,7 @@ async function runGeneratorSubcommand({
|
|
|
383
529
|
if (!existingSectionTabLinkSource) {
|
|
384
530
|
if (dryRun !== true) {
|
|
385
531
|
await mkdir(path.dirname(sectionTabLinkPath.absolutePath), { recursive: true });
|
|
386
|
-
await writeFile(sectionTabLinkPath.absolutePath,
|
|
532
|
+
await writeFile(sectionTabLinkPath.absolutePath, renderTabLinkItemSource(), "utf8");
|
|
387
533
|
}
|
|
388
534
|
touchedFiles.add(sectionTabLinkPath.relativePath);
|
|
389
535
|
}
|
|
@@ -395,9 +541,9 @@ async function runGeneratorSubcommand({
|
|
|
395
541
|
);
|
|
396
542
|
}
|
|
397
543
|
|
|
398
|
-
const providerImportLine = `import ${
|
|
544
|
+
const providerImportLine = `import ${TAB_LINK_COMPONENT} from "/${toPosixPath(path.join(componentDirectory, `${TAB_LINK_COMPONENT}.vue`))}";`;
|
|
399
545
|
const providerRegisterLine =
|
|
400
|
-
`registerMainClientComponent("${
|
|
546
|
+
`registerMainClientComponent("${TAB_LINK_COMPONENT_TOKEN}", () => ${TAB_LINK_COMPONENT});`;
|
|
401
547
|
const providerImportApplied = insertImportIfMissing(providerSource, providerImportLine);
|
|
402
548
|
const providerRegisterApplied = insertBeforeClassDeclaration(
|
|
403
549
|
providerImportApplied.content,
|
|
@@ -436,36 +582,53 @@ async function runGeneratorSubcommand({
|
|
|
436
582
|
);
|
|
437
583
|
}
|
|
438
584
|
touchedFiles.add(containerRelativePath);
|
|
585
|
+
} else {
|
|
586
|
+
const routeMetaApplied = ensureContainerRouteMetaOutlets(existingContainerSource, {
|
|
587
|
+
surface,
|
|
588
|
+
containerHost: containerSlug,
|
|
589
|
+
containerPosition: CONTAINER_OUTLET_POSITION
|
|
590
|
+
});
|
|
591
|
+
if (routeMetaApplied.changed) {
|
|
592
|
+
if (dryRun !== true) {
|
|
593
|
+
await writeFile(containerFilePath, routeMetaApplied.content, "utf8");
|
|
594
|
+
}
|
|
595
|
+
touchedFiles.add(containerRelativePath);
|
|
596
|
+
}
|
|
439
597
|
}
|
|
440
598
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
"
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
599
|
+
if (placementTarget) {
|
|
600
|
+
const placementPath = resolvePathWithinApp(resolvedAppRoot, PLACEMENT_FILE, {
|
|
601
|
+
context: "ui-generator container"
|
|
602
|
+
});
|
|
603
|
+
const placementSource = await readFile(placementPath.absolutePath, "utf8");
|
|
604
|
+
const placementIdSuffix = routePath.replaceAll("/", "-");
|
|
605
|
+
const placementMarker = `jskit:ui-generator.container.menu:${surface}:${routePath}`;
|
|
606
|
+
const placementBlock =
|
|
607
|
+
`// ${placementMarker}\n` +
|
|
608
|
+
"{\n" +
|
|
609
|
+
" addPlacement({\n" +
|
|
610
|
+
` id: "ui-generator.container.${placementIdSuffix}.menu",\n` +
|
|
611
|
+
` host: "${placementTarget.host}",\n` +
|
|
612
|
+
` position: "${placementTarget.position}",\n` +
|
|
613
|
+
` surfaces: ["${surface}"],\n` +
|
|
614
|
+
" order: 155,\n" +
|
|
615
|
+
' componentToken: "users.web.shell.surface-aware-menu-link-item",\n' +
|
|
616
|
+
" props: {\n" +
|
|
617
|
+
` label: "${name}",\n` +
|
|
618
|
+
` surface: "${surface}",\n` +
|
|
619
|
+
` workspaceSuffix: "/${routePath}",\n` +
|
|
620
|
+
` nonWorkspaceSuffix: "/${routePath}"\n` +
|
|
621
|
+
" },\n" +
|
|
622
|
+
" when: ({ auth }) => Boolean(auth?.authenticated)\n" +
|
|
623
|
+
" });\n" +
|
|
624
|
+
"}\n";
|
|
625
|
+
const placementApplied = appendBlockIfMarkerMissing(placementSource, placementMarker, placementBlock);
|
|
626
|
+
if (placementApplied.changed) {
|
|
627
|
+
if (dryRun !== true) {
|
|
628
|
+
await writeFile(placementPath.absolutePath, placementApplied.content, "utf8");
|
|
629
|
+
}
|
|
630
|
+
touchedFiles.add(placementPath.relativePath);
|
|
467
631
|
}
|
|
468
|
-
touchedFiles.add(placementPath.relativePath);
|
|
469
632
|
}
|
|
470
633
|
|
|
471
634
|
const touchedFileList = [...touchedFiles].sort((left, right) => left.localeCompare(right));
|