@jskit-ai/ui-generator 0.1.48 → 0.1.50
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.descriptor.mjs +117 -24
- package/package.json +3 -3
- package/src/server/buildTemplateContext.js +44 -3
- package/src/server/subcommands/addSubpages.js +89 -2
- package/src/server/subcommands/element.js +42 -10
- package/src/server/subcommands/outlet.js +399 -20
- package/src/server/subcommands/page.js +49 -23
- package/src/server/subcommands/pageSupport.js +115 -37
- package/src/server/subcommands/support.js +128 -23
- package/test/addSubpagesSubcommand.test.js +163 -15
- package/test/buildTemplateContext.test.js +227 -34
- package/test/elementSubcommand.test.js +89 -12
- package/test/outletSubcommand.test.js +305 -14
- package/test/packageDescriptor.test.js +11 -0
- package/test/pageSubcommand.test.js +234 -17
|
@@ -13,21 +13,27 @@ import {
|
|
|
13
13
|
readLocalLinkItemComponentSource
|
|
14
14
|
} from "@jskit-ai/shell-web/server/support/localLinkItemScaffolds";
|
|
15
15
|
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
16
|
+
import {
|
|
17
|
+
buildGeneratedUiScreenClassName,
|
|
18
|
+
resolveGeneratedUiSurfaceProfile
|
|
19
|
+
} from "@jskit-ai/kernel/shared/support/generatedUiContract";
|
|
16
20
|
import {
|
|
17
21
|
DEFAULT_COMPONENT_DIRECTORY,
|
|
18
22
|
MAIN_CLIENT_PROVIDER_FILE,
|
|
19
23
|
resolvePathWithinApp,
|
|
20
24
|
insertImportIfMissing,
|
|
21
25
|
insertBeforeClassDeclaration,
|
|
22
|
-
|
|
26
|
+
findScriptSetupBlock,
|
|
27
|
+
insertScriptSetupBlock,
|
|
23
28
|
indentBlock
|
|
24
29
|
} from "./support.js";
|
|
25
30
|
|
|
26
31
|
const DEFAULT_SUBPAGES_POSITION = "sub-pages";
|
|
27
32
|
const SECTION_CONTAINER_SHELL_COMPONENT = "SectionContainerShell";
|
|
28
|
-
const SUBPAGES_LINK_COMPONENT_TOKEN = "local.main.ui.
|
|
33
|
+
const SUBPAGES_LINK_COMPONENT_TOKEN = "local.main.ui.tab-link-item";
|
|
29
34
|
const DEFAULT_MENU_COMPONENT_DIRECTORY = path.join(DEFAULT_COMPONENT_DIRECTORY, "menus");
|
|
30
35
|
const SUBPAGES_LINK_COMPONENT_DEFINITION = findLocalLinkItemDefinition(SUBPAGES_LINK_COMPONENT_TOKEN);
|
|
36
|
+
const OPERATOR_SURFACE_IDS = new Set(["admin", "console"]);
|
|
31
37
|
|
|
32
38
|
if (!SUBPAGES_LINK_COMPONENT_DEFINITION) {
|
|
33
39
|
throw new Error(`ui-generator add-subpages could not resolve ${SUBPAGES_LINK_COMPONENT_TOKEN} scaffold definition.`);
|
|
@@ -35,25 +41,97 @@ if (!SUBPAGES_LINK_COMPONENT_DEFINITION) {
|
|
|
35
41
|
|
|
36
42
|
const SUBPAGES_LINK_COMPONENT = SUBPAGES_LINK_COMPONENT_DEFINITION.componentName;
|
|
37
43
|
|
|
38
|
-
const ROUTE_TAG_PATTERN = /<route\b[^>]*>[\s\S]*?<\/route>\s*/gi;
|
|
39
44
|
const TEMPLATE_TOKEN_PATTERN = /<\/?template\b[^>]*>/gi;
|
|
40
45
|
const SHELL_OUTLET_TAG_PATTERN = /<ShellOutlet\b[^>]*\/?>\s*/gi;
|
|
41
46
|
const ROUTER_VIEW_TAG_PATTERN = /<RouterView\b/i;
|
|
42
47
|
const ROUTER_VIEW_LINE_PATTERN = /^\s*<RouterView(?:\s[^>]*)?\s*\/>\s*$/gm;
|
|
43
48
|
|
|
49
|
+
function resolveGeneratedPageSurfaceProfile({
|
|
50
|
+
surfaceId = "",
|
|
51
|
+
routePath = ""
|
|
52
|
+
} = {}) {
|
|
53
|
+
const routeSegments = normalizeText(routePath)
|
|
54
|
+
.replaceAll("\\", "/")
|
|
55
|
+
.split("/")
|
|
56
|
+
.map((entry) => normalizeText(entry).toLowerCase())
|
|
57
|
+
.filter(Boolean);
|
|
58
|
+
if (routeSegments.includes("settings")) {
|
|
59
|
+
return "settings";
|
|
60
|
+
}
|
|
61
|
+
if (OPERATOR_SURFACE_IDS.has(normalizeText(surfaceId).toLowerCase())) {
|
|
62
|
+
return "operator";
|
|
63
|
+
}
|
|
64
|
+
return "task";
|
|
65
|
+
}
|
|
66
|
+
|
|
44
67
|
function trimEdgeBlankLines(source = "") {
|
|
45
68
|
return String(source || "")
|
|
46
69
|
.replace(/^\s*\n/, "")
|
|
47
70
|
.replace(/\n\s*$/, "");
|
|
48
71
|
}
|
|
49
72
|
|
|
50
|
-
function renderPlainPageSource(pageTitle = ""
|
|
73
|
+
function renderPlainPageSource(pageTitle = "", {
|
|
74
|
+
surfaceId = "",
|
|
75
|
+
routePath = ""
|
|
76
|
+
} = {}) {
|
|
77
|
+
const surfaceProfileId = resolveGeneratedPageSurfaceProfile({ surfaceId, routePath });
|
|
78
|
+
const surfaceProfile = resolveGeneratedUiSurfaceProfile(surfaceProfileId);
|
|
79
|
+
const screenClass = buildGeneratedUiScreenClassName("generated-page-screen d-flex flex-column ga-4", {
|
|
80
|
+
surfaceProfile: surfaceProfileId
|
|
81
|
+
});
|
|
51
82
|
return `<template>
|
|
52
|
-
<section class="
|
|
53
|
-
<
|
|
54
|
-
|
|
83
|
+
<section class="${screenClass}">
|
|
84
|
+
<header>
|
|
85
|
+
<p class="text-overline text-medium-emphasis mb-1">${surfaceProfile.titleLabel}</p>
|
|
86
|
+
<h1 class="generated-page-screen__title">${pageTitle}</h1>
|
|
87
|
+
</header>
|
|
88
|
+
|
|
89
|
+
<v-sheet rounded="lg" border class="generated-page-screen__empty-state">
|
|
90
|
+
<h2 class="text-h6 mb-2">No ${pageTitle} activity yet</h2>
|
|
91
|
+
<p class="text-body-2 text-medium-emphasis mb-0">
|
|
92
|
+
${surfaceProfile.emptyStateBody}
|
|
93
|
+
</p>
|
|
94
|
+
</v-sheet>
|
|
55
95
|
</section>
|
|
56
96
|
</template>
|
|
97
|
+
|
|
98
|
+
<style scoped>
|
|
99
|
+
.generated-ui-screen {
|
|
100
|
+
--generated-ui-screen-title-size: clamp(1.35rem, 2vw, 1.85rem);
|
|
101
|
+
--generated-ui-screen-panel-padding: 2rem 1.25rem;
|
|
102
|
+
--generated-ui-screen-panel-align: center;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.generated-ui-screen--operator {
|
|
106
|
+
--generated-ui-screen-panel-padding: 1.5rem 1rem;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.generated-ui-screen--settings {
|
|
110
|
+
--generated-ui-screen-panel-padding: 1.5rem 1rem;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.generated-page-screen__title {
|
|
114
|
+
font-size: var(--generated-ui-screen-title-size);
|
|
115
|
+
font-weight: 650;
|
|
116
|
+
letter-spacing: -0.02em;
|
|
117
|
+
line-height: 1.15;
|
|
118
|
+
margin: 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.generated-page-screen__empty-state {
|
|
122
|
+
margin-inline: auto;
|
|
123
|
+
max-width: 34rem;
|
|
124
|
+
padding: var(--generated-ui-screen-panel-padding);
|
|
125
|
+
text-align: var(--generated-ui-screen-panel-align);
|
|
126
|
+
width: 100%;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
@media (max-width: 640px) {
|
|
130
|
+
.generated-ui-screen {
|
|
131
|
+
--generated-ui-screen-panel-padding: 1.25rem 1rem;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
</style>
|
|
57
135
|
`;
|
|
58
136
|
}
|
|
59
137
|
|
|
@@ -81,41 +159,50 @@ const hasTabs = computed(() => Boolean(slots.tabs));
|
|
|
81
159
|
|
|
82
160
|
<template>
|
|
83
161
|
<section class="section-container-shell d-flex flex-column ga-4">
|
|
84
|
-
<v-
|
|
85
|
-
<
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
<slot name="tabs" />
|
|
93
|
-
</v-card-text>
|
|
94
|
-
</template>
|
|
95
|
-
</v-card>
|
|
162
|
+
<header v-if="hasHeading" class="section-container-shell__heading">
|
|
163
|
+
<h1 v-if="resolvedTitle" class="section-container-shell__title">{{ resolvedTitle }}</h1>
|
|
164
|
+
<p v-if="resolvedSubtitle" class="text-body-2 text-medium-emphasis mb-0">{{ resolvedSubtitle }}</p>
|
|
165
|
+
</header>
|
|
166
|
+
|
|
167
|
+
<v-sheet v-if="hasTabs" rounded="lg" border class="section-container-shell__nav">
|
|
168
|
+
<slot name="tabs" />
|
|
169
|
+
</v-sheet>
|
|
96
170
|
|
|
97
171
|
<slot />
|
|
98
172
|
</section>
|
|
99
173
|
</template>
|
|
100
174
|
|
|
101
175
|
<style scoped>
|
|
102
|
-
.section-container-
|
|
103
|
-
|
|
176
|
+
.section-container-shell__heading {
|
|
177
|
+
min-width: 0;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.section-container-shell__title {
|
|
181
|
+
font-size: clamp(1.35rem, 2vw, 1.85rem);
|
|
182
|
+
font-weight: 650;
|
|
183
|
+
letter-spacing: -0.02em;
|
|
184
|
+
line-height: 1.15;
|
|
185
|
+
margin: 0 0 0.35rem;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.section-container-shell__nav {
|
|
104
189
|
align-items: center;
|
|
190
|
+
display: flex;
|
|
105
191
|
gap: 0.5rem;
|
|
106
192
|
overflow-x: auto;
|
|
107
|
-
padding: 0.
|
|
193
|
+
padding: 0.5rem;
|
|
108
194
|
scrollbar-width: thin;
|
|
109
195
|
}
|
|
110
196
|
|
|
111
|
-
.section-container-
|
|
197
|
+
.section-container-shell__nav :deep(.tab-link-item) {
|
|
112
198
|
flex: 0 0 auto;
|
|
199
|
+
min-height: 48px;
|
|
113
200
|
}
|
|
114
201
|
|
|
115
202
|
@media (max-width: 640px) {
|
|
116
|
-
.section-container-
|
|
203
|
+
.section-container-shell__nav {
|
|
117
204
|
gap: 0.375rem;
|
|
118
|
-
|
|
205
|
+
margin-inline: -0.25rem;
|
|
119
206
|
}
|
|
120
207
|
}
|
|
121
208
|
</style>
|
|
@@ -313,7 +400,7 @@ function renderSubpagesTemplate({
|
|
|
313
400
|
"<template>",
|
|
314
401
|
renderSectionContainerOpenTag({ title, subtitle }),
|
|
315
402
|
" <template #tabs>",
|
|
316
|
-
` <ShellOutlet target="${normalizedTarget}"
|
|
403
|
+
` <ShellOutlet target="${normalizedTarget}" />`,
|
|
317
404
|
" </template>"
|
|
318
405
|
];
|
|
319
406
|
|
|
@@ -332,7 +419,7 @@ function renderSubpagesTemplate({
|
|
|
332
419
|
|
|
333
420
|
function applySubpagesScriptImports(source = "", { sectionContainerComponentImportPath = "" } = {}) {
|
|
334
421
|
const sourceText = String(source || "");
|
|
335
|
-
const scriptBlock =
|
|
422
|
+
const scriptBlock = findScriptSetupBlock(sourceText);
|
|
336
423
|
|
|
337
424
|
const importLines = [
|
|
338
425
|
"import ShellOutlet from \"@jskit-ai/shell-web/client/components/ShellOutlet\";",
|
|
@@ -341,16 +428,7 @@ function applySubpagesScriptImports(source = "", { sectionContainerComponentImpo
|
|
|
341
428
|
];
|
|
342
429
|
|
|
343
430
|
if (!scriptBlock) {
|
|
344
|
-
|
|
345
|
-
let insertionIndex = 0;
|
|
346
|
-
for (const match of sourceText.matchAll(ROUTE_TAG_PATTERN)) {
|
|
347
|
-
insertionIndex = match.index + String(match[0] || "").length;
|
|
348
|
-
}
|
|
349
|
-
const separator = insertionIndex > 0 ? "\n" : "";
|
|
350
|
-
return {
|
|
351
|
-
changed: true,
|
|
352
|
-
content: `${sourceText.slice(0, insertionIndex)}${separator}${scriptSetupBlock}\n${sourceText.slice(insertionIndex)}`
|
|
353
|
-
};
|
|
431
|
+
return insertScriptSetupBlock(sourceText, importLines.join("\n"));
|
|
354
432
|
}
|
|
355
433
|
|
|
356
434
|
let nextScriptContent = scriptBlock.content;
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import {
|
|
3
|
+
importFreshModuleFromAbsolutePath,
|
|
3
4
|
resolveRequiredAppRoot,
|
|
4
5
|
toPosixPath
|
|
5
6
|
} from "@jskit-ai/kernel/server/support";
|
|
6
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
PLACEMENT_LAYOUT_CLASSES,
|
|
9
|
+
normalizePlacementOwnerId,
|
|
10
|
+
normalizePlacementTopologyDefinition,
|
|
11
|
+
normalizeSemanticPlacementId,
|
|
12
|
+
normalizeShellOutletTargetId
|
|
13
|
+
} from "@jskit-ai/kernel/shared/support/shellLayoutTargets";
|
|
7
14
|
import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
|
|
8
15
|
import { toCamelCase, toSnakeCase } from "@jskit-ai/kernel/shared/support/stringCase";
|
|
9
16
|
|
|
10
17
|
const DEFAULT_COMPONENT_DIRECTORY = "src/components";
|
|
11
18
|
const MAIN_CLIENT_PROVIDER_FILE = "packages/main/src/client/providers/MainClientProvider.js";
|
|
12
19
|
const PLACEMENT_FILE = "src/placement.js";
|
|
20
|
+
const PLACEMENT_TOPOLOGY_FILE = "src/placementTopology.js";
|
|
13
21
|
const SCRIPT_TAG_PATTERN = /<script\b([^>]*)>([\s\S]*?)<\/script>/gi;
|
|
14
22
|
const SCRIPT_SETUP_ATTRIBUTE_PATTERN = /\bsetup\b/i;
|
|
15
23
|
const ATTRIBUTE_PATTERN = /([:@]?[A-Za-z_][A-Za-z0-9_-]*)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'))?/g;
|
|
@@ -207,38 +215,132 @@ function insertBeforeClassDeclaration(source = "", line = "", { className = "",
|
|
|
207
215
|
};
|
|
208
216
|
}
|
|
209
217
|
|
|
210
|
-
function
|
|
218
|
+
function findScriptSetupBlock(source = "") {
|
|
211
219
|
const sourceText = String(source || "");
|
|
212
|
-
let firstMatch = null;
|
|
213
|
-
|
|
214
220
|
for (const match of sourceText.matchAll(SCRIPT_TAG_PATTERN)) {
|
|
215
|
-
if (!firstMatch) {
|
|
216
|
-
firstMatch = match;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
221
|
const attributesSource = String(match[1] || "");
|
|
220
|
-
if (SCRIPT_SETUP_ATTRIBUTE_PATTERN.test(attributesSource)) {
|
|
221
|
-
|
|
222
|
-
index: match.index,
|
|
223
|
-
source: String(match[0] || ""),
|
|
224
|
-
attributesSource,
|
|
225
|
-
content: String(match[2] || "")
|
|
226
|
-
});
|
|
222
|
+
if (!SCRIPT_SETUP_ATTRIBUTE_PATTERN.test(attributesSource)) {
|
|
223
|
+
continue;
|
|
227
224
|
}
|
|
225
|
+
|
|
226
|
+
return Object.freeze({
|
|
227
|
+
index: match.index,
|
|
228
|
+
source: String(match[0] || ""),
|
|
229
|
+
attributesSource,
|
|
230
|
+
content: String(match[2] || "")
|
|
231
|
+
});
|
|
228
232
|
}
|
|
229
233
|
|
|
230
|
-
|
|
231
|
-
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function insertScriptSetupBlock(source = "", content = "") {
|
|
238
|
+
const sourceText = String(source || "");
|
|
239
|
+
const normalizedContent = String(content || "").trim();
|
|
240
|
+
if (!normalizedContent) {
|
|
241
|
+
return {
|
|
242
|
+
changed: false,
|
|
243
|
+
content: sourceText
|
|
244
|
+
};
|
|
232
245
|
}
|
|
233
246
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
247
|
+
const scriptSetupBlock = `<script setup>\n${normalizedContent}\n</script>\n`;
|
|
248
|
+
let insertionIndex = 0;
|
|
249
|
+
for (const match of sourceText.matchAll(/<route\b[^>]*>[\s\S]*?<\/route>\s*/gi)) {
|
|
250
|
+
insertionIndex = match.index + String(match[0] || "").length;
|
|
251
|
+
}
|
|
252
|
+
const separator = insertionIndex > 0 ? "\n" : "";
|
|
253
|
+
return {
|
|
254
|
+
changed: true,
|
|
255
|
+
content: `${sourceText.slice(0, insertionIndex)}${separator}${scriptSetupBlock}\n${sourceText.slice(insertionIndex)}`
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function loadPlacementTopologyDefinitionFromPath(topologyPath = {}, { context = "ui-generator topology" } = {}) {
|
|
260
|
+
const moduleNamespace = await importFreshModuleFromAbsolutePath(topologyPath.absolutePath);
|
|
261
|
+
const exported = moduleNamespace?.default;
|
|
262
|
+
const resolved = typeof exported === "function" ? exported() : exported;
|
|
263
|
+
return normalizePlacementTopologyDefinition(resolved, {
|
|
264
|
+
context
|
|
239
265
|
});
|
|
240
266
|
}
|
|
241
267
|
|
|
268
|
+
function normalizeExpectedTopologyVariantTargets(variantTargets = null) {
|
|
269
|
+
if (!variantTargets || typeof variantTargets !== "object" || Array.isArray(variantTargets)) {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const targets = {};
|
|
274
|
+
for (const layoutClass of PLACEMENT_LAYOUT_CLASSES) {
|
|
275
|
+
const target = normalizeShellOutletTargetId(variantTargets[layoutClass]);
|
|
276
|
+
if (target) {
|
|
277
|
+
targets[layoutClass] = target;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return Object.keys(targets).length > 0 ? Object.freeze(targets) : null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function describeTopologyVariantTargets(variantTargets = {}) {
|
|
285
|
+
return PLACEMENT_LAYOUT_CLASSES
|
|
286
|
+
.map((layoutClass) => {
|
|
287
|
+
const target = normalizeShellOutletTargetId(variantTargets?.[layoutClass]);
|
|
288
|
+
return target ? `${layoutClass}:${target}` : "";
|
|
289
|
+
})
|
|
290
|
+
.filter(Boolean)
|
|
291
|
+
.join(", ");
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function placementMatchesExpectedVariantTargets(placement = {}, expectedVariantTargets = null) {
|
|
295
|
+
if (!expectedVariantTargets) {
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const variants = placement?.variants && typeof placement.variants === "object" ? placement.variants : {};
|
|
300
|
+
return Object.entries(expectedVariantTargets).every(([layoutClass, expectedTarget]) =>
|
|
301
|
+
normalizeShellOutletTargetId(variants?.[layoutClass]?.outlet) === expectedTarget
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
async function appendTopologyBlockIfPlacementMissing({
|
|
306
|
+
topologyPath = {},
|
|
307
|
+
source = "",
|
|
308
|
+
marker = "",
|
|
309
|
+
block = "",
|
|
310
|
+
placementId = "",
|
|
311
|
+
owner = "",
|
|
312
|
+
variantTargets = null,
|
|
313
|
+
context = "ui-generator topology"
|
|
314
|
+
} = {}) {
|
|
315
|
+
const normalizedPlacementId = normalizeSemanticPlacementId(placementId);
|
|
316
|
+
const normalizedOwner = normalizePlacementOwnerId(owner);
|
|
317
|
+
if (!normalizedPlacementId) {
|
|
318
|
+
throw new Error(`${context} requires semantic placement id in "area.slot" format.`);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const topology = await loadPlacementTopologyDefinitionFromPath(topologyPath, { context });
|
|
322
|
+
const existingPlacement = (Array.isArray(topology.placements) ? topology.placements : []).find(
|
|
323
|
+
(placement) => placement.id === normalizedPlacementId && (placement.owner || "") === normalizedOwner
|
|
324
|
+
);
|
|
325
|
+
if (existingPlacement) {
|
|
326
|
+
const expectedVariantTargets = normalizeExpectedTopologyVariantTargets(variantTargets);
|
|
327
|
+
if (!placementMatchesExpectedVariantTargets(existingPlacement, expectedVariantTargets)) {
|
|
328
|
+
const ownerLabel = normalizedOwner ? ` for owner "${normalizedOwner}"` : "";
|
|
329
|
+
throw new Error(
|
|
330
|
+
`${context} semantic placement "${normalizedPlacementId}"${ownerLabel} already exists with different outlet mapping. ` +
|
|
331
|
+
`Existing: ${describeTopologyVariantTargets(existingPlacement.variants)}. ` +
|
|
332
|
+
`Requested: ${describeTopologyVariantTargets(expectedVariantTargets)}.`
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
return {
|
|
336
|
+
changed: false,
|
|
337
|
+
content: String(source || "")
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return appendBlockIfMarkerMissing(source, marker, block);
|
|
342
|
+
}
|
|
343
|
+
|
|
242
344
|
function parseTagAttributes(attributesSource = "") {
|
|
243
345
|
const attributes = {};
|
|
244
346
|
const source = String(attributesSource || "");
|
|
@@ -266,6 +368,7 @@ export {
|
|
|
266
368
|
DEFAULT_COMPONENT_DIRECTORY,
|
|
267
369
|
MAIN_CLIENT_PROVIDER_FILE,
|
|
268
370
|
PLACEMENT_FILE,
|
|
371
|
+
PLACEMENT_TOPOLOGY_FILE,
|
|
269
372
|
toKebabCase,
|
|
270
373
|
toPascalCase,
|
|
271
374
|
requireOption,
|
|
@@ -277,7 +380,9 @@ export {
|
|
|
277
380
|
appendBlockIfMarkerMissing,
|
|
278
381
|
insertImportIfMissing,
|
|
279
382
|
insertBeforeClassDeclaration,
|
|
280
|
-
|
|
383
|
+
findScriptSetupBlock,
|
|
384
|
+
insertScriptSetupBlock,
|
|
385
|
+
appendTopologyBlockIfPlacementMissing,
|
|
281
386
|
parseTagAttributes,
|
|
282
387
|
indentBlock
|
|
283
388
|
};
|