@jskit-ai/jskit-cli 0.2.75 → 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/mutations/textMutations.js +8 -2
- package/src/server/commandHandlers/health.js +10 -6
- package/src/server/commandHandlers/list.js +364 -22
- package/src/server/commandHandlers/mobile.js +90 -146
- package/src/server/commandHandlers/mobileShellSupport.js +24 -1
- package/src/server/core/buildCommandDeps.js +1 -0
- package/src/server/core/commandCatalog.js +5 -3
- package/src/server/core/createCliRunner.js +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/jskit-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.76",
|
|
4
4
|
"description": "Bundle and package orchestration CLI for JSKIT apps.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"test": "node --test"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@jskit-ai/jskit-catalog": "0.1.
|
|
24
|
-
"@jskit-ai/kernel": "0.1.
|
|
25
|
-
"@jskit-ai/shell-web": "0.1.
|
|
23
|
+
"@jskit-ai/jskit-catalog": "0.1.75",
|
|
24
|
+
"@jskit-ai/kernel": "0.1.67",
|
|
25
|
+
"@jskit-ai/shell-web": "0.1.66"
|
|
26
26
|
},
|
|
27
27
|
"engines": {
|
|
28
28
|
"node": "20.x"
|
|
@@ -113,6 +113,13 @@ async function applyTextMutations(
|
|
|
113
113
|
const previous = await readFileBufferIfExists(absoluteFile);
|
|
114
114
|
const previousContent = previous.exists ? previous.buffer.toString("utf8") : "";
|
|
115
115
|
const mutationId = String(mutation?.id || "").trim() || "append-text";
|
|
116
|
+
const interpolatedSkipChecks = normalizeSkipChecks(mutation?.skipIfContains)
|
|
117
|
+
.map((entry) => interpolateOptionValue(entry, options, packageEntry.packageId, `${mutationId}.skipIfContains`))
|
|
118
|
+
.filter((entry) => String(entry || "").trim().length > 0);
|
|
119
|
+
if (interpolatedSkipChecks.some((pattern) => previousContent.includes(String(pattern)))) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
116
123
|
const resolvedSnippet = interpolateOptionValue(snippet, options, packageEntry.packageId, mutationId);
|
|
117
124
|
const templateContextReplacements = await resolveTemplateContextReplacementsForMutation({
|
|
118
125
|
packageEntry,
|
|
@@ -126,8 +133,7 @@ async function applyTextMutations(
|
|
|
126
133
|
const renderedSnippet = templateContextReplacements
|
|
127
134
|
? applyTemplateContextReplacements(resolvedSnippet, templateContextReplacements)
|
|
128
135
|
: resolvedSnippet;
|
|
129
|
-
const skipChecks =
|
|
130
|
-
.map((entry) => interpolateOptionValue(entry, options, packageEntry.packageId, `${mutationId}.skipIfContains`))
|
|
136
|
+
const skipChecks = interpolatedSkipChecks
|
|
131
137
|
.map((entry) => {
|
|
132
138
|
if (!templateContextReplacements) {
|
|
133
139
|
return entry;
|
|
@@ -1825,8 +1825,12 @@ function createHealthCommands(ctx = {}) {
|
|
|
1825
1825
|
return false;
|
|
1826
1826
|
}
|
|
1827
1827
|
|
|
1828
|
-
function
|
|
1829
|
-
|
|
1828
|
+
function isListFiltersImportSource(sourcePath = "") {
|
|
1829
|
+
const normalizedSource = String(sourcePath || "").trim();
|
|
1830
|
+
return (
|
|
1831
|
+
/(^|\/)shared\/[^/'"]*ListFilters(?:\.[A-Za-z0-9]+)?$/u.test(normalizedSource) ||
|
|
1832
|
+
/^\.\/listFilters\.[A-Za-z0-9]+$/u.test(normalizedSource)
|
|
1833
|
+
);
|
|
1830
1834
|
}
|
|
1831
1835
|
|
|
1832
1836
|
function findCallSites(sourceText = "", calleeName = "") {
|
|
@@ -1872,22 +1876,22 @@ function createHealthCommands(ctx = {}) {
|
|
|
1872
1876
|
|
|
1873
1877
|
if (!firstArgument || firstArgument.startsWith("{")) {
|
|
1874
1878
|
issues.push(
|
|
1875
|
-
`${relativePath}:${lineNumber}: [filters:shared-definition] do not inline structured filter definitions in ${calleeName}(...). Put them in packages/<crud>/src/shared/<crud>ListFilters.js and import that
|
|
1879
|
+
`${relativePath}:${lineNumber}: [filters:shared-definition] do not inline structured filter definitions in ${calleeName}(...). Put them in listFilters.js or packages/<crud>/src/shared/<crud>ListFilters.js and import that module.`
|
|
1876
1880
|
);
|
|
1877
1881
|
continue;
|
|
1878
1882
|
}
|
|
1879
1883
|
|
|
1880
1884
|
if (!/^[A-Za-z_$][\w$]*$/u.test(firstArgument)) {
|
|
1881
1885
|
issues.push(
|
|
1882
|
-
`${relativePath}:${lineNumber}: [filters:shared-definition] ${calleeName}(...) must receive a definitions symbol imported from a CRUD shared *ListFilters module, not an ad-hoc expression.`
|
|
1886
|
+
`${relativePath}:${lineNumber}: [filters:shared-definition] ${calleeName}(...) must receive a definitions symbol imported from listFilters.js or a CRUD shared *ListFilters module, not an ad-hoc expression.`
|
|
1883
1887
|
);
|
|
1884
1888
|
continue;
|
|
1885
1889
|
}
|
|
1886
1890
|
|
|
1887
1891
|
const importSource = importBindings.get(firstArgument) || "";
|
|
1888
|
-
if (!
|
|
1892
|
+
if (!isListFiltersImportSource(importSource)) {
|
|
1889
1893
|
issues.push(
|
|
1890
|
-
`${relativePath}:${lineNumber}: [filters:shared-definition] ${calleeName}(${firstArgument}, ...) must use definitions imported from a CRUD shared *ListFilters module. Found ${importSource ? `import source "${importSource}"` : "a local symbol"} instead.`
|
|
1894
|
+
`${relativePath}:${lineNumber}: [filters:shared-definition] ${calleeName}(${firstArgument}, ...) must use definitions imported from listFilters.js or a CRUD shared *ListFilters module. Found ${importSource ? `import source "${importSource}"` : "a local symbol"} instead.`
|
|
1891
1895
|
);
|
|
1892
1896
|
}
|
|
1893
1897
|
}
|
|
@@ -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");
|
|
@@ -178,6 +506,7 @@ function createListCommands(ctx = {}) {
|
|
|
178
506
|
loadAppLocalPackageRegistry,
|
|
179
507
|
resolveInstalledNodeModulePackageEntry,
|
|
180
508
|
discoverPlacementTopologyFromApp,
|
|
509
|
+
discoverShellOutletSourcePathsFromApp,
|
|
181
510
|
discoverShellOutletTargetsFromApp,
|
|
182
511
|
normalizePlacementContributions,
|
|
183
512
|
resolvePackageKind
|
|
@@ -439,6 +768,8 @@ function createListCommands(ctx = {}) {
|
|
|
439
768
|
const showConcreteOnly = options.concrete === true && options.all !== true;
|
|
440
769
|
const showConcrete = options.concrete === true || options.all === true;
|
|
441
770
|
const showSemantic = showConcreteOnly !== true;
|
|
771
|
+
const showLayoutDetails = options.details === true;
|
|
772
|
+
const shouldLookupHostPaths = showSemantic;
|
|
442
773
|
const discoveredTopology = await discoverPlacementTopologyFromApp({ appRoot });
|
|
443
774
|
const semanticPlacements = ensureArray(discoveredTopology.placements)
|
|
444
775
|
.map((entry) => ensureObject(entry))
|
|
@@ -450,16 +781,28 @@ function createListCommands(ctx = {}) {
|
|
|
450
781
|
}
|
|
451
782
|
return String(left.owner || "").localeCompare(String(right.owner || ""));
|
|
452
783
|
});
|
|
453
|
-
|
|
454
|
-
|
|
784
|
+
let discoveredConcrete = { targets: [] };
|
|
785
|
+
if (showConcrete) {
|
|
786
|
+
discoveredConcrete = await discoverShellOutletTargetsFromApp({
|
|
455
787
|
appRoot,
|
|
456
788
|
sourceRoot: "src"
|
|
457
|
-
})
|
|
458
|
-
|
|
789
|
+
});
|
|
790
|
+
} else if (shouldLookupHostPaths) {
|
|
791
|
+
discoveredConcrete = await discoverShellOutletSourcePathsFromApp({
|
|
792
|
+
appRoot,
|
|
793
|
+
sourceRoot: "src"
|
|
794
|
+
});
|
|
795
|
+
}
|
|
459
796
|
const concreteTargets = ensureArray(discoveredConcrete.targets)
|
|
460
797
|
.map((entry) => ensureObject(entry))
|
|
461
798
|
.filter((entry) => String(entry.id || "").trim())
|
|
462
799
|
.sort((left, right) => String(left.id || "").localeCompare(String(right.id || "")));
|
|
800
|
+
const unmappedConcreteTargets = showSemantic
|
|
801
|
+
? resolveUnmappedConcreteTargets({
|
|
802
|
+
semanticPlacements,
|
|
803
|
+
concreteTargets
|
|
804
|
+
})
|
|
805
|
+
: [];
|
|
463
806
|
|
|
464
807
|
if (options.json) {
|
|
465
808
|
const payload = {
|
|
@@ -480,7 +823,12 @@ function createListCommands(ctx = {}) {
|
|
|
480
823
|
default: placementTarget.default === true,
|
|
481
824
|
sourcePath: String(placementTarget.sourcePath || "").trim()
|
|
482
825
|
}))
|
|
483
|
-
: []
|
|
826
|
+
: [],
|
|
827
|
+
unmappedConcretePlacements: unmappedConcreteTargets.map((placementTarget) => ({
|
|
828
|
+
target: String(placementTarget.id || "").trim(),
|
|
829
|
+
default: placementTarget.default === true,
|
|
830
|
+
sourcePath: String(placementTarget.sourcePath || "").trim()
|
|
831
|
+
}))
|
|
484
832
|
};
|
|
485
833
|
stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
486
834
|
return 0;
|
|
@@ -490,27 +838,21 @@ function createListCommands(ctx = {}) {
|
|
|
490
838
|
const lines = [];
|
|
491
839
|
if (showSemantic) {
|
|
492
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.");
|
|
493
842
|
if (semanticPlacements.length < 1) {
|
|
494
843
|
lines.push("- none");
|
|
495
844
|
} else {
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
const descriptionSuffix = description ? `: ${description}` : "";
|
|
503
|
-
lines.push(`- ${color.item(placementId)}${ownerLabel}${defaultLabel}${descriptionSuffix}`);
|
|
504
|
-
const variants = ensureObject(placementTarget.variants);
|
|
505
|
-
for (const layoutClass of ["compact", "medium", "expanded"]) {
|
|
506
|
-
const variant = ensureObject(variants[layoutClass]);
|
|
507
|
-
const outlet = String(variant.outlet || "").trim();
|
|
508
|
-
if (outlet) {
|
|
509
|
-
lines.push(` ${layoutClass} -> ${color.dim(outlet)}`);
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
}
|
|
845
|
+
appendSemanticPlacementGroups(lines, {
|
|
846
|
+
color,
|
|
847
|
+
semanticPlacements,
|
|
848
|
+
concreteTargets,
|
|
849
|
+
showLayoutDetails
|
|
850
|
+
});
|
|
513
851
|
}
|
|
852
|
+
appendUnmappedConcreteTargetWarnings(lines, {
|
|
853
|
+
color,
|
|
854
|
+
concreteTargets: unmappedConcreteTargets
|
|
855
|
+
});
|
|
514
856
|
}
|
|
515
857
|
|
|
516
858
|
if (showConcrete) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
4
4
|
import {
|
|
5
5
|
createColorFormatter,
|
|
6
6
|
writeWrappedLines
|
|
@@ -23,6 +23,10 @@ import {
|
|
|
23
23
|
} from "./mobileShellSupport.js";
|
|
24
24
|
const CAPACITOR_RUNTIME_PACKAGE_ID = "@jskit-ai/mobile-capacitor";
|
|
25
25
|
const MOBILE_NOTES_RELATIVE_PATH = path.join(".jskit", "mobile-capacitor.md");
|
|
26
|
+
const MANAGED_MOBILE_FILE_RELATIVE_PATHS = Object.freeze([
|
|
27
|
+
"capacitor.config.json",
|
|
28
|
+
MOBILE_NOTES_RELATIVE_PATH
|
|
29
|
+
]);
|
|
26
30
|
|
|
27
31
|
async function collectManagedMobileFileDriftIssues({
|
|
28
32
|
ctx,
|
|
@@ -34,12 +38,7 @@ async function collectManagedMobileFileDriftIssues({
|
|
|
34
38
|
path: pathModule,
|
|
35
39
|
normalizeRelativePath
|
|
36
40
|
} = ctx;
|
|
37
|
-
const
|
|
38
|
-
"capacitor.config.json",
|
|
39
|
-
MOBILE_NOTES_RELATIVE_PATH
|
|
40
|
-
];
|
|
41
|
-
|
|
42
|
-
for (const relativePath of managedRelativePaths) {
|
|
41
|
+
for (const relativePath of MANAGED_MOBILE_FILE_RELATIVE_PATHS) {
|
|
43
42
|
const absolutePath = pathModule.join(appRoot, relativePath);
|
|
44
43
|
if (!(await fileExists(absolutePath))) {
|
|
45
44
|
continue;
|
|
@@ -66,46 +65,6 @@ async function collectManagedMobileFileDriftIssues({
|
|
|
66
65
|
}
|
|
67
66
|
}
|
|
68
67
|
|
|
69
|
-
async function collectMissingInstalledDependencyNames(ctx, appRoot = "", packageJson = {}) {
|
|
70
|
-
const {
|
|
71
|
-
fileExists,
|
|
72
|
-
path: pathModule
|
|
73
|
-
} = ctx;
|
|
74
|
-
const sections = [
|
|
75
|
-
packageJson?.dependencies,
|
|
76
|
-
packageJson?.devDependencies,
|
|
77
|
-
packageJson?.optionalDependencies
|
|
78
|
-
];
|
|
79
|
-
const missing = [];
|
|
80
|
-
const seen = new Set();
|
|
81
|
-
|
|
82
|
-
for (const section of sections) {
|
|
83
|
-
if (!section || typeof section !== "object" || Array.isArray(section)) {
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
for (const packageName of Object.keys(section).sort((left, right) => left.localeCompare(right))) {
|
|
88
|
-
const normalizedPackageName = String(packageName || "").trim();
|
|
89
|
-
if (!normalizedPackageName || seen.has(normalizedPackageName)) {
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
seen.add(normalizedPackageName);
|
|
93
|
-
|
|
94
|
-
const packageJsonPath = pathModule.join(
|
|
95
|
-
appRoot,
|
|
96
|
-
"node_modules",
|
|
97
|
-
...normalizedPackageName.split("/"),
|
|
98
|
-
"package.json"
|
|
99
|
-
);
|
|
100
|
-
if (!(await fileExists(packageJsonPath))) {
|
|
101
|
-
missing.push(normalizedPackageName);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return missing;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
68
|
function renderAndroidMobileCommandList(lines, color) {
|
|
110
69
|
for (const entry of listMobileCommandDefinitions()) {
|
|
111
70
|
if (entry.name === "dev") {
|
|
@@ -429,9 +388,13 @@ async function runLocalBinary(binaryName, args = [], {
|
|
|
429
388
|
|
|
430
389
|
child.on("error", (error) => {
|
|
431
390
|
if (error?.code === "ENOENT") {
|
|
391
|
+
const installHint =
|
|
392
|
+
binaryName === "cap"
|
|
393
|
+
? ` Run npm install after adding ${CAPACITOR_RUNTIME_PACKAGE_ID}, then rerun this command.`
|
|
394
|
+
: "";
|
|
432
395
|
reject(
|
|
433
396
|
createCliError(
|
|
434
|
-
`Could not find local "${binaryName}" in node_modules/.bin
|
|
397
|
+
`Could not find local "${binaryName}" in node_modules/.bin.${installHint}`
|
|
435
398
|
)
|
|
436
399
|
);
|
|
437
400
|
return;
|
|
@@ -451,100 +414,93 @@ async function runLocalBinary(binaryName, args = [], {
|
|
|
451
414
|
});
|
|
452
415
|
}
|
|
453
416
|
|
|
454
|
-
|
|
417
|
+
function hasPackageDependency(packageJson = {}, packageId = "") {
|
|
418
|
+
const sections = [
|
|
419
|
+
packageJson?.dependencies,
|
|
420
|
+
packageJson?.devDependencies,
|
|
421
|
+
packageJson?.optionalDependencies
|
|
422
|
+
];
|
|
423
|
+
return sections.some((section) => (
|
|
424
|
+
section &&
|
|
425
|
+
typeof section === "object" &&
|
|
426
|
+
!Array.isArray(section) &&
|
|
427
|
+
Object.prototype.hasOwnProperty.call(section, packageId)
|
|
428
|
+
));
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
async function readJsonFileForMobileCommand(filePath = "", label = "", createCliError) {
|
|
432
|
+
try {
|
|
433
|
+
return JSON.parse(await readFile(filePath, "utf8"));
|
|
434
|
+
} catch (error) {
|
|
435
|
+
const message = String(error?.message || error || "unknown error");
|
|
436
|
+
throw createCliError(`Could not read ${label}: ${message}`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
async function assertMobileRuntimePackageInstalled({
|
|
455
441
|
ctx,
|
|
456
|
-
appRoot
|
|
457
|
-
stdout,
|
|
458
|
-
stderr,
|
|
459
|
-
dryRun = false,
|
|
460
|
-
devlinks = false
|
|
442
|
+
appRoot
|
|
461
443
|
} = {}) {
|
|
462
444
|
const {
|
|
463
445
|
path: pathModule,
|
|
464
|
-
|
|
446
|
+
createCliError
|
|
465
447
|
} = ctx;
|
|
466
|
-
const
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
await
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
stdout,
|
|
473
|
-
pathModule,
|
|
474
|
-
createCliError: ctx.createCliError,
|
|
475
|
-
dryRun
|
|
476
|
-
});
|
|
448
|
+
const packageJsonPath = pathModule.join(appRoot, "package.json");
|
|
449
|
+
const lockPath = pathModule.join(appRoot, ".jskit", "lock.json");
|
|
450
|
+
const packageJson = await readJsonFileForMobileCommand(packageJsonPath, "package.json", createCliError);
|
|
451
|
+
const lock = await readJsonFileForMobileCommand(lockPath, ".jskit/lock.json", createCliError);
|
|
452
|
+
const hasDependency = hasPackageDependency(packageJson, CAPACITOR_RUNTIME_PACKAGE_ID);
|
|
453
|
+
const hasLockRecord = Boolean(lock?.installedPackages?.[CAPACITOR_RUNTIME_PACKAGE_ID]);
|
|
477
454
|
|
|
478
|
-
if (
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
stdout,
|
|
483
|
-
pathModule,
|
|
484
|
-
createCliError: ctx.createCliError,
|
|
485
|
-
dryRun
|
|
486
|
-
});
|
|
455
|
+
if (!hasDependency || !hasLockRecord) {
|
|
456
|
+
throw createCliError(
|
|
457
|
+
`Mobile Capacitor runtime package is not installed for this app. Run jskit add package ${CAPACITOR_RUNTIME_PACKAGE_ID} first.`
|
|
458
|
+
);
|
|
487
459
|
}
|
|
488
460
|
}
|
|
489
461
|
|
|
490
462
|
async function refreshManagedMobileFiles({
|
|
491
463
|
ctx,
|
|
492
|
-
commandAdd,
|
|
493
464
|
appRoot,
|
|
494
465
|
options = {},
|
|
495
|
-
stdout
|
|
496
|
-
stderr
|
|
466
|
+
stdout
|
|
497
467
|
} = {}) {
|
|
498
468
|
const {
|
|
499
|
-
|
|
469
|
+
fileExists,
|
|
470
|
+
path: pathModule,
|
|
471
|
+
normalizeRelativePath,
|
|
472
|
+
createCliError
|
|
500
473
|
} = ctx;
|
|
501
|
-
const packageJsonPath = pathModule.join(appRoot, "package.json");
|
|
502
|
-
const packageJsonBefore = await readFile(packageJsonPath, "utf8");
|
|
503
|
-
let capturedStdout = "";
|
|
504
|
-
await commandAdd({
|
|
505
|
-
positional: ["package", CAPACITOR_RUNTIME_PACKAGE_ID],
|
|
506
|
-
options: {
|
|
507
|
-
...options,
|
|
508
|
-
forceReapplyTarget: true,
|
|
509
|
-
runNpmInstall: false,
|
|
510
|
-
inlineOptions: {}
|
|
511
|
-
},
|
|
512
|
-
cwd: appRoot,
|
|
513
|
-
io: {
|
|
514
|
-
stdout: {
|
|
515
|
-
write(chunk) {
|
|
516
|
-
capturedStdout += String(chunk || "");
|
|
517
|
-
}
|
|
518
|
-
},
|
|
519
|
-
stderr
|
|
520
|
-
}
|
|
521
|
-
});
|
|
522
|
-
const packageJsonAfter = await readFile(packageJsonPath, "utf8");
|
|
523
|
-
const parsedPackageJsonAfter = JSON.parse(packageJsonAfter);
|
|
524
|
-
const missingInstalledDependencies = await collectMissingInstalledDependencyNames(ctx, appRoot, parsedPackageJsonAfter);
|
|
525
474
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
475
|
+
for (const relativePath of MANAGED_MOBILE_FILE_RELATIVE_PATHS) {
|
|
476
|
+
const absolutePath = pathModule.join(appRoot, relativePath);
|
|
477
|
+
if (!(await fileExists(absolutePath))) {
|
|
478
|
+
throw createCliError(
|
|
479
|
+
`Managed mobile file is missing: ${normalizeRelativePath(appRoot, absolutePath)}. Run jskit add package ${CAPACITOR_RUNTIME_PACKAGE_ID} first.`
|
|
480
|
+
);
|
|
481
|
+
}
|
|
529
482
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
(packageJsonAfter !== packageJsonBefore || missingInstalledDependencies.length > 0)
|
|
533
|
-
) {
|
|
534
|
-
await runMobileAppInstall({
|
|
535
|
-
ctx,
|
|
483
|
+
const currentSource = await readFile(absolutePath, "utf8");
|
|
484
|
+
const nextSource = await renderManagedMobileFile({
|
|
536
485
|
appRoot,
|
|
537
|
-
|
|
538
|
-
stderr,
|
|
539
|
-
dryRun: false,
|
|
540
|
-
devlinks: options?.devlinks === true
|
|
486
|
+
relativeTargetPath: relativePath
|
|
541
487
|
});
|
|
488
|
+
if (nextSource === currentSource) {
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (options?.dryRun === true) {
|
|
493
|
+
stdout?.write(`[dry-run] refresh ${normalizeRelativePath(appRoot, absolutePath)}\n`);
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
await writeFile(absolutePath, nextSource, "utf8");
|
|
498
|
+
stdout?.write(`[mobile] Refreshed ${normalizeRelativePath(appRoot, absolutePath)}.\n`);
|
|
542
499
|
}
|
|
543
500
|
}
|
|
544
501
|
|
|
545
502
|
async function runMobileSyncAndroidCommand({
|
|
546
503
|
ctx,
|
|
547
|
-
commandAdd,
|
|
548
504
|
appRoot,
|
|
549
505
|
options = {},
|
|
550
506
|
stdout,
|
|
@@ -554,19 +510,20 @@ async function runMobileSyncAndroidCommand({
|
|
|
554
510
|
path: pathModule
|
|
555
511
|
} = ctx;
|
|
556
512
|
|
|
557
|
-
await
|
|
513
|
+
await assertMobileRuntimePackageInstalled({
|
|
558
514
|
ctx,
|
|
559
|
-
|
|
560
|
-
appRoot,
|
|
561
|
-
options,
|
|
562
|
-
stdout,
|
|
563
|
-
stderr
|
|
515
|
+
appRoot
|
|
564
516
|
});
|
|
565
|
-
|
|
566
517
|
await assertCapacitorShellInstalled({
|
|
567
518
|
ctx,
|
|
568
519
|
appRoot
|
|
569
520
|
});
|
|
521
|
+
await refreshManagedMobileFiles({
|
|
522
|
+
ctx,
|
|
523
|
+
appRoot,
|
|
524
|
+
options,
|
|
525
|
+
stdout
|
|
526
|
+
});
|
|
570
527
|
await ensureAndroidNativeShellIdentity({
|
|
571
528
|
ctx,
|
|
572
529
|
appRoot,
|
|
@@ -607,7 +564,6 @@ async function runMobileSyncAndroidCommand({
|
|
|
607
564
|
|
|
608
565
|
async function runMobileRunAndroidCommand({
|
|
609
566
|
ctx,
|
|
610
|
-
commandAdd,
|
|
611
567
|
appRoot,
|
|
612
568
|
options = {},
|
|
613
569
|
stdout,
|
|
@@ -632,26 +588,26 @@ async function runMobileRunAndroidCommand({
|
|
|
632
588
|
if (mobileConfig.assetMode === "bundled") {
|
|
633
589
|
await runMobileSyncAndroidCommand({
|
|
634
590
|
ctx,
|
|
635
|
-
commandAdd,
|
|
636
591
|
appRoot,
|
|
637
592
|
options,
|
|
638
593
|
stdout,
|
|
639
594
|
stderr
|
|
640
595
|
});
|
|
641
596
|
} else {
|
|
642
|
-
await
|
|
597
|
+
await assertMobileRuntimePackageInstalled({
|
|
643
598
|
ctx,
|
|
644
|
-
|
|
645
|
-
appRoot,
|
|
646
|
-
options,
|
|
647
|
-
stdout,
|
|
648
|
-
stderr
|
|
599
|
+
appRoot
|
|
649
600
|
});
|
|
650
|
-
|
|
651
601
|
await assertCapacitorShellInstalled({
|
|
652
602
|
ctx,
|
|
653
603
|
appRoot
|
|
654
604
|
});
|
|
605
|
+
await refreshManagedMobileFiles({
|
|
606
|
+
ctx,
|
|
607
|
+
appRoot,
|
|
608
|
+
options,
|
|
609
|
+
stdout
|
|
610
|
+
});
|
|
655
611
|
await ensureAndroidNativeShellIdentity({
|
|
656
612
|
ctx,
|
|
657
613
|
appRoot,
|
|
@@ -722,7 +678,6 @@ async function runCapRunAndroidCommand({
|
|
|
722
678
|
|
|
723
679
|
async function runMobileBuildAndroidCommand({
|
|
724
680
|
ctx,
|
|
725
|
-
commandAdd,
|
|
726
681
|
appRoot,
|
|
727
682
|
options = {},
|
|
728
683
|
stdout,
|
|
@@ -751,7 +706,6 @@ async function runMobileBuildAndroidCommand({
|
|
|
751
706
|
|
|
752
707
|
await runMobileSyncAndroidCommand({
|
|
753
708
|
ctx,
|
|
754
|
-
commandAdd,
|
|
755
709
|
appRoot,
|
|
756
710
|
options,
|
|
757
711
|
stdout,
|
|
@@ -1005,7 +959,6 @@ async function runMobileRestartAndroidCommand({
|
|
|
1005
959
|
|
|
1006
960
|
async function runMobileDevAndroidCommand({
|
|
1007
961
|
ctx,
|
|
1008
|
-
commandAdd,
|
|
1009
962
|
appRoot,
|
|
1010
963
|
options = {},
|
|
1011
964
|
stdout,
|
|
@@ -1024,7 +977,6 @@ async function runMobileDevAndroidCommand({
|
|
|
1024
977
|
stdout.write("[mobile] npx jskit mobile android sync\n");
|
|
1025
978
|
await runMobileSyncAndroidCommand({
|
|
1026
979
|
ctx,
|
|
1027
|
-
commandAdd,
|
|
1028
980
|
appRoot,
|
|
1029
981
|
options,
|
|
1030
982
|
stdout,
|
|
@@ -1060,16 +1012,12 @@ async function runMobileDevAndroidCommand({
|
|
|
1060
1012
|
return 0;
|
|
1061
1013
|
}
|
|
1062
1014
|
|
|
1063
|
-
function createMobileCommands(ctx = {}
|
|
1015
|
+
function createMobileCommands(ctx = {}) {
|
|
1064
1016
|
const {
|
|
1065
1017
|
createCliError,
|
|
1066
1018
|
resolveAppRootFromCwd
|
|
1067
1019
|
} = ctx;
|
|
1068
1020
|
|
|
1069
|
-
if (typeof commandAdd !== "function") {
|
|
1070
|
-
throw new TypeError("createMobileCommands requires commandAdd().");
|
|
1071
|
-
}
|
|
1072
|
-
|
|
1073
1021
|
async function commandMobile({ positional = [], options = {}, cwd = "", stdout, stderr }) {
|
|
1074
1022
|
const firstToken = String(positional[0] || "").trim();
|
|
1075
1023
|
const secondToken = String(positional[1] || "").trim();
|
|
@@ -1154,7 +1102,6 @@ function createMobileCommands(ctx = {}, { commandAdd } = {}) {
|
|
|
1154
1102
|
|
|
1155
1103
|
return runMobileDevAndroidCommand({
|
|
1156
1104
|
ctx,
|
|
1157
|
-
commandAdd,
|
|
1158
1105
|
appRoot,
|
|
1159
1106
|
options,
|
|
1160
1107
|
stdout,
|
|
@@ -1203,7 +1150,6 @@ function createMobileCommands(ctx = {}, { commandAdd } = {}) {
|
|
|
1203
1150
|
|
|
1204
1151
|
return runMobileSyncAndroidCommand({
|
|
1205
1152
|
ctx,
|
|
1206
|
-
commandAdd,
|
|
1207
1153
|
appRoot,
|
|
1208
1154
|
options,
|
|
1209
1155
|
stdout,
|
|
@@ -1220,7 +1166,6 @@ function createMobileCommands(ctx = {}, { commandAdd } = {}) {
|
|
|
1220
1166
|
|
|
1221
1167
|
return runMobileRunAndroidCommand({
|
|
1222
1168
|
ctx,
|
|
1223
|
-
commandAdd,
|
|
1224
1169
|
appRoot,
|
|
1225
1170
|
options,
|
|
1226
1171
|
stdout,
|
|
@@ -1237,7 +1182,6 @@ function createMobileCommands(ctx = {}, { commandAdd } = {}) {
|
|
|
1237
1182
|
|
|
1238
1183
|
return runMobileBuildAndroidCommand({
|
|
1239
1184
|
ctx,
|
|
1240
|
-
commandAdd,
|
|
1241
1185
|
appRoot,
|
|
1242
1186
|
options,
|
|
1243
1187
|
stdout,
|
|
@@ -158,6 +158,18 @@ function buildManagedMobileConfigStub({ packageJson = {} } = {}) {
|
|
|
158
158
|
].join("\n");
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
function isEmptyDisabledMobileConfigPlaceholder(mobileConfig = {}) {
|
|
162
|
+
return (
|
|
163
|
+
mobileConfig?.enabled !== true &&
|
|
164
|
+
!String(mobileConfig?.strategy || "").trim() &&
|
|
165
|
+
!String(mobileConfig?.appId || "").trim() &&
|
|
166
|
+
!String(mobileConfig?.appName || "").trim() &&
|
|
167
|
+
!String(mobileConfig?.apiBaseUrl || "").trim() &&
|
|
168
|
+
!String(mobileConfig?.auth?.customScheme || "").trim() &&
|
|
169
|
+
!String(mobileConfig?.android?.packageName || "").trim()
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
161
173
|
function parseAndroidSdkDirFromLocalProperties(source = "") {
|
|
162
174
|
const lines = String(source || "").split(/\r?\n/u);
|
|
163
175
|
for (const line of lines) {
|
|
@@ -489,9 +501,19 @@ async function ensureMobileConfigStub({
|
|
|
489
501
|
} = ctx;
|
|
490
502
|
const publicConfigPath = path.join(appRoot, PUBLIC_CONFIG_RELATIVE_PATH);
|
|
491
503
|
const currentSource = await readFile(publicConfigPath, "utf8");
|
|
492
|
-
if (
|
|
504
|
+
if (
|
|
505
|
+
currentSource.includes(MANAGED_MOBILE_CONFIG_START_MARKER) &&
|
|
506
|
+
currentSource.includes(MANAGED_MOBILE_CONFIG_END_MARKER)
|
|
507
|
+
) {
|
|
493
508
|
return false;
|
|
494
509
|
}
|
|
510
|
+
if (/\bconfig\.mobile\b|\bmobile\s*:/u.test(currentSource)) {
|
|
511
|
+
const mergedConfig = await loadAppConfigFromAppRoot({ appRoot });
|
|
512
|
+
const mobileConfig = resolveMobileConfig({ mobile: mergedConfig.mobile });
|
|
513
|
+
if (!isEmptyDisabledMobileConfigPlaceholder(mobileConfig)) {
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
495
517
|
|
|
496
518
|
const stubSource = buildManagedMobileConfigStub({
|
|
497
519
|
packageJson
|
|
@@ -913,6 +935,7 @@ export {
|
|
|
913
935
|
ANDROID_DIRECTORY_NAME,
|
|
914
936
|
ANDROID_MANIFEST_RELATIVE_PATH,
|
|
915
937
|
buildManagedMobileConfigStub,
|
|
938
|
+
isEmptyDisabledMobileConfigPlaceholder,
|
|
916
939
|
resolveInstalledMobileConfig,
|
|
917
940
|
resolveAndroidSdkDetails,
|
|
918
941
|
collectAndroidSdkComponentIssues,
|
|
@@ -54,6 +54,7 @@ function createCommandHandlerDeps(deps = {}) {
|
|
|
54
54
|
removeManagedViteProxyEntries: deps.removeManagedViteProxyEntries,
|
|
55
55
|
hashBuffer: deps.hashBuffer,
|
|
56
56
|
rm: deps.rm,
|
|
57
|
+
discoverShellOutletSourcePathsFromApp: deps.discoverShellOutletSourcePathsFromApp,
|
|
57
58
|
discoverShellOutletTargetsFromApp: deps.discoverShellOutletTargetsFromApp,
|
|
58
59
|
discoverPlacementTopologyFromApp: deps.discoverPlacementTopologyFromApp
|
|
59
60
|
};
|
|
@@ -317,14 +317,16 @@ const COMMAND_DESCRIPTORS = Object.freeze({
|
|
|
317
317
|
minimalUse: "jskit list-placements",
|
|
318
318
|
parameters: Object.freeze([]),
|
|
319
319
|
defaults: Object.freeze([
|
|
320
|
-
"Shows semantic placement targets from app placement topology by
|
|
320
|
+
"Shows semantic placement targets from app placement topology grouped by authoring model.",
|
|
321
|
+
"Default output includes the generator command pattern for adding to each group.",
|
|
322
|
+
"Use --details to include compact, medium, and expanded layout outlet mappings.",
|
|
321
323
|
"Use --concrete to inspect low-level ShellOutlet recipients.",
|
|
322
324
|
"Use --all to show both semantic placements and concrete recipients."
|
|
323
325
|
]),
|
|
324
|
-
fullUse: "jskit list-placements [--concrete] [--all] [--json]",
|
|
326
|
+
fullUse: "jskit list-placements [--details] [--concrete] [--all] [--json]",
|
|
325
327
|
showHelpOnBareInvocation: false,
|
|
326
328
|
handlerName: "commandListPlacements",
|
|
327
|
-
allowedFlagKeys: Object.freeze(["concrete", "all", "json"]),
|
|
329
|
+
allowedFlagKeys: Object.freeze(["details", "concrete", "all", "json"]),
|
|
328
330
|
inlineOptionMode: "none",
|
|
329
331
|
allowedValueOptionNames: Object.freeze([])
|
|
330
332
|
}),
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import {
|
|
9
9
|
discoverPlacementTopologyFromApp,
|
|
10
|
+
discoverShellOutletSourcePathsFromApp,
|
|
10
11
|
discoverShellOutletTargetsFromApp
|
|
11
12
|
} from "@jskit-ai/kernel/server/support";
|
|
12
13
|
import { createCliError } from "../shared/cliError.js";
|
|
@@ -149,6 +150,7 @@ const commandHandlers = createCommandHandlers(
|
|
|
149
150
|
removeManagedViteProxyEntries,
|
|
150
151
|
hashBuffer,
|
|
151
152
|
rm,
|
|
153
|
+
discoverShellOutletSourcePathsFromApp,
|
|
152
154
|
discoverShellOutletTargetsFromApp,
|
|
153
155
|
discoverPlacementTopologyFromApp
|
|
154
156
|
})
|