@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
|
@@ -1,30 +1,8 @@
|
|
|
1
1
|
const MOBILE_COMMAND_DEFINITIONS = Object.freeze({
|
|
2
|
-
add: Object.freeze({
|
|
3
|
-
name: "add",
|
|
4
|
-
summary: "Install Capacitor mobile-shell support into the current app.",
|
|
5
|
-
usage: "jskit mobile add capacitor [--dry-run] [--devlinks]",
|
|
6
|
-
options: Object.freeze([
|
|
7
|
-
Object.freeze({
|
|
8
|
-
label: "--devlinks",
|
|
9
|
-
description: "Run npm run --if-present devlinks after install for local development relinking."
|
|
10
|
-
}),
|
|
11
|
-
Object.freeze({
|
|
12
|
-
label: "--dry-run",
|
|
13
|
-
description: "Preview the package install and generated files without mutating package.json, lockfiles, or app files."
|
|
14
|
-
})
|
|
15
|
-
]),
|
|
16
|
-
defaults: Object.freeze([
|
|
17
|
-
"Installs @jskit-ai/mobile-capacitor plus the required Capacitor packages.",
|
|
18
|
-
"Renders capacitor.config.json and .jskit/mobile-capacitor.md from config.mobile.",
|
|
19
|
-
"Runs npm install and then cap add android unless --dry-run is used.",
|
|
20
|
-
"Use --devlinks only when you want npm run devlinks to relink the app after install.",
|
|
21
|
-
"If android/ already exists, the Capacitor CLI add step is skipped."
|
|
22
|
-
])
|
|
23
|
-
}),
|
|
24
2
|
sync: Object.freeze({
|
|
25
3
|
name: "sync",
|
|
26
4
|
summary: "Build the JSKIT web client and sync the Android Capacitor shell.",
|
|
27
|
-
usage: "jskit mobile sync
|
|
5
|
+
usage: "jskit mobile android sync [--dry-run] [--devlinks]",
|
|
28
6
|
options: Object.freeze([
|
|
29
7
|
Object.freeze({
|
|
30
8
|
label: "--devlinks",
|
|
@@ -38,13 +16,13 @@ const MOBILE_COMMAND_DEFINITIONS = Object.freeze({
|
|
|
38
16
|
defaults: Object.freeze([
|
|
39
17
|
"Runs npm run build so dist/ matches the current JSKIT web client.",
|
|
40
18
|
"Runs cap sync android after the frontend build succeeds.",
|
|
41
|
-
"Requires capacitor.config.json and android/ from jskit
|
|
19
|
+
"Requires capacitor.config.json and android/ from jskit add package @jskit-ai/mobile-capacitor."
|
|
42
20
|
])
|
|
43
21
|
}),
|
|
44
22
|
run: Object.freeze({
|
|
45
23
|
name: "run",
|
|
46
24
|
summary: "Launch the Android Capacitor shell for the current app.",
|
|
47
|
-
usage: "jskit mobile run
|
|
25
|
+
usage: "jskit mobile android run [--target <device-id>] [--dry-run]",
|
|
48
26
|
options: Object.freeze([
|
|
49
27
|
Object.freeze({
|
|
50
28
|
label: "--target <device-id>",
|
|
@@ -62,8 +40,8 @@ const MOBILE_COMMAND_DEFINITIONS = Object.freeze({
|
|
|
62
40
|
}),
|
|
63
41
|
dev: Object.freeze({
|
|
64
42
|
name: "dev",
|
|
65
|
-
summary: "
|
|
66
|
-
usage: "jskit mobile dev
|
|
43
|
+
summary: "Shortcut to run sync, tunnel, run in this order.",
|
|
44
|
+
usage: "jskit mobile android dev [--target <device-id>]",
|
|
67
45
|
options: Object.freeze([
|
|
68
46
|
Object.freeze({
|
|
69
47
|
label: "--target <device-id>",
|
|
@@ -71,15 +49,15 @@ const MOBILE_COMMAND_DEFINITIONS = Object.freeze({
|
|
|
71
49
|
})
|
|
72
50
|
]),
|
|
73
51
|
defaults: Object.freeze([
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"
|
|
52
|
+
"jskit mobile android sync",
|
|
53
|
+
"jskit mobile android tunnel",
|
|
54
|
+
"jskit mobile android run"
|
|
77
55
|
])
|
|
78
56
|
}),
|
|
79
57
|
devices: Object.freeze({
|
|
80
58
|
name: "devices",
|
|
81
59
|
summary: "List Android devices currently visible to adb.",
|
|
82
|
-
usage: "jskit mobile devices
|
|
60
|
+
usage: "jskit mobile android devices",
|
|
83
61
|
options: Object.freeze([]),
|
|
84
62
|
defaults: Object.freeze([
|
|
85
63
|
"Runs adb devices -l and prints the currently connected Android targets."
|
|
@@ -88,11 +66,11 @@ const MOBILE_COMMAND_DEFINITIONS = Object.freeze({
|
|
|
88
66
|
tunnel: Object.freeze({
|
|
89
67
|
name: "tunnel",
|
|
90
68
|
summary: "Create and verify an adb reverse tunnel for local Android testing.",
|
|
91
|
-
usage: "jskit mobile tunnel
|
|
69
|
+
usage: "jskit mobile android tunnel [--target <device-id>] [--port <port>]",
|
|
92
70
|
options: Object.freeze([
|
|
93
71
|
Object.freeze({
|
|
94
72
|
label: "--target <device-id>",
|
|
95
|
-
description: "
|
|
73
|
+
description: "Optional adb device serial. If omitted, uses the first device from adb devices -l."
|
|
96
74
|
}),
|
|
97
75
|
Object.freeze({
|
|
98
76
|
label: "--port <port>",
|
|
@@ -107,11 +85,11 @@ const MOBILE_COMMAND_DEFINITIONS = Object.freeze({
|
|
|
107
85
|
restart: Object.freeze({
|
|
108
86
|
name: "restart",
|
|
109
87
|
summary: "Clear app data and cold-start the Android shell on a chosen device.",
|
|
110
|
-
usage: "jskit mobile restart
|
|
88
|
+
usage: "jskit mobile android restart [--target <device-id>]",
|
|
111
89
|
options: Object.freeze([
|
|
112
90
|
Object.freeze({
|
|
113
91
|
label: "--target <device-id>",
|
|
114
|
-
description: "
|
|
92
|
+
description: "Optional adb device serial. If omitted, uses the first device from adb devices -l."
|
|
115
93
|
})
|
|
116
94
|
]),
|
|
117
95
|
defaults: Object.freeze([
|
|
@@ -122,7 +100,7 @@ const MOBILE_COMMAND_DEFINITIONS = Object.freeze({
|
|
|
122
100
|
build: Object.freeze({
|
|
123
101
|
name: "build",
|
|
124
102
|
summary: "Build a release Android App Bundle for the current app.",
|
|
125
|
-
usage: "jskit mobile build
|
|
103
|
+
usage: "jskit mobile android build [--dry-run]",
|
|
126
104
|
options: Object.freeze([
|
|
127
105
|
Object.freeze({
|
|
128
106
|
label: "--dry-run",
|
|
@@ -137,7 +115,7 @@ const MOBILE_COMMAND_DEFINITIONS = Object.freeze({
|
|
|
137
115
|
doctor: Object.freeze({
|
|
138
116
|
name: "doctor",
|
|
139
117
|
summary: "Validate the Android Capacitor shell wiring for the current app.",
|
|
140
|
-
usage: "jskit mobile doctor",
|
|
118
|
+
usage: "jskit mobile android doctor",
|
|
141
119
|
options: Object.freeze([]),
|
|
142
120
|
defaults: Object.freeze([
|
|
143
121
|
"Checks config.mobile, capacitor.config.json, android/, and the managed AndroidManifest deep-link filter."
|
|
@@ -146,8 +124,25 @@ const MOBILE_COMMAND_DEFINITIONS = Object.freeze({
|
|
|
146
124
|
});
|
|
147
125
|
|
|
148
126
|
function listMobileCommandDefinitions() {
|
|
127
|
+
const order = new Map([
|
|
128
|
+
["dev", 0],
|
|
129
|
+
["sync", 1],
|
|
130
|
+
["tunnel", 2],
|
|
131
|
+
["run", 3],
|
|
132
|
+
["restart", 4],
|
|
133
|
+
["build", 5],
|
|
134
|
+
["devices", 6],
|
|
135
|
+
["doctor", 7]
|
|
136
|
+
]);
|
|
149
137
|
return Object.values(MOBILE_COMMAND_DEFINITIONS)
|
|
150
|
-
.sort((left, right) =>
|
|
138
|
+
.sort((left, right) => {
|
|
139
|
+
const leftOrder = order.get(left.name) ?? Number.MAX_SAFE_INTEGER;
|
|
140
|
+
const rightOrder = order.get(right.name) ?? Number.MAX_SAFE_INTEGER;
|
|
141
|
+
if (leftOrder !== rightOrder) {
|
|
142
|
+
return leftOrder - rightOrder;
|
|
143
|
+
}
|
|
144
|
+
return left.name.localeCompare(right.name);
|
|
145
|
+
});
|
|
151
146
|
}
|
|
152
147
|
|
|
153
148
|
function resolveMobileCommandDefinition(rawName = "") {
|
|
@@ -168,7 +163,7 @@ function buildMobileCommandOptionMeta(subcommandName = "") {
|
|
|
168
163
|
return optionMeta;
|
|
169
164
|
}
|
|
170
165
|
|
|
171
|
-
if (definition.name === "
|
|
166
|
+
if (definition.name === "sync" || definition.name === "run" || definition.name === "build") {
|
|
172
167
|
optionMeta["dry-run"] = { inputType: "flag" };
|
|
173
168
|
}
|
|
174
169
|
if (definition.name === "run") {
|
|
@@ -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
|
|
@@ -619,7 +641,7 @@ async function assertCapacitorShellInstalled({ ctx, appRoot }) {
|
|
|
619
641
|
|
|
620
642
|
if (missingPaths.length > 0) {
|
|
621
643
|
throw ctx.createCliError(
|
|
622
|
-
`Capacitor Android shell is not installed for this app. Missing: ${missingPaths.join(", ")}. Run jskit
|
|
644
|
+
`Capacitor Android shell is not installed for this app. Missing: ${missingPaths.join(", ")}. Run jskit add package @jskit-ai/mobile-capacitor first.`
|
|
623
645
|
);
|
|
624
646
|
}
|
|
625
647
|
}
|
|
@@ -678,7 +700,7 @@ async function ensureAndroidManifestDeepLinks({
|
|
|
678
700
|
const manifestPath = pathModule.join(appRoot, ANDROID_MANIFEST_RELATIVE_PATH);
|
|
679
701
|
if (!(await fileExists(manifestPath))) {
|
|
680
702
|
throw createCliError(
|
|
681
|
-
`Capacitor Android shell is missing ${normalizeRelativePath(appRoot, manifestPath)}. Run jskit
|
|
703
|
+
`Capacitor Android shell is missing ${normalizeRelativePath(appRoot, manifestPath)}. Run jskit add package @jskit-ai/mobile-capacitor first.`
|
|
682
704
|
);
|
|
683
705
|
}
|
|
684
706
|
|
|
@@ -722,7 +744,7 @@ async function collectAndroidNativeShellIdentityIssues({ ctx, appRoot } = {}) {
|
|
|
722
744
|
const expectedSource = renderer(currentSource, nativeConfig);
|
|
723
745
|
if (currentSource !== expectedSource) {
|
|
724
746
|
issues.push(
|
|
725
|
-
`${normalizeRelativePath(appRoot, absolutePath)} is stale and no longer matches config.mobile. Re-run jskit mobile sync
|
|
747
|
+
`${normalizeRelativePath(appRoot, absolutePath)} is stale and no longer matches config.mobile. Re-run jskit mobile android sync to refresh the Android shell.`
|
|
726
748
|
);
|
|
727
749
|
}
|
|
728
750
|
};
|
|
@@ -757,7 +779,7 @@ async function collectAndroidNativeShellIdentityIssues({ ctx, appRoot } = {}) {
|
|
|
757
779
|
currentMainActivitySource !== expectedMainActivitySource
|
|
758
780
|
) {
|
|
759
781
|
issues.push(
|
|
760
|
-
`${normalizeRelativePath(appRoot, mainActivityEntry.absolutePath)} is stale and no longer matches config.mobile. Re-run jskit mobile sync
|
|
782
|
+
`${normalizeRelativePath(appRoot, mainActivityEntry.absolutePath)} is stale and no longer matches config.mobile. Re-run jskit mobile android sync to refresh the Android shell.`
|
|
761
783
|
);
|
|
762
784
|
}
|
|
763
785
|
|
|
@@ -783,7 +805,7 @@ async function ensureAndroidNativeShellIdentity({
|
|
|
783
805
|
const absolutePath = pathModule.join(appRoot, relativePath);
|
|
784
806
|
if (!(await fileExists(absolutePath))) {
|
|
785
807
|
throw createCliError(
|
|
786
|
-
`Capacitor Android shell is missing ${normalizeRelativePath(appRoot, absolutePath)}. Run jskit
|
|
808
|
+
`Capacitor Android shell is missing ${normalizeRelativePath(appRoot, absolutePath)}. Run jskit add package @jskit-ai/mobile-capacitor first.`
|
|
787
809
|
);
|
|
788
810
|
}
|
|
789
811
|
const currentSource = await readFile(absolutePath, "utf8");
|
|
@@ -807,7 +829,7 @@ async function ensureAndroidNativeShellIdentity({
|
|
|
807
829
|
|
|
808
830
|
const mainActivityEntry = await resolveAndroidMainActivityEntry(appRoot);
|
|
809
831
|
if (!mainActivityEntry) {
|
|
810
|
-
throw createCliError("Capacitor Android shell is missing MainActivity.java or MainActivity.kt. Run jskit
|
|
832
|
+
throw createCliError("Capacitor Android shell is missing MainActivity.java or MainActivity.kt. Run jskit add package @jskit-ai/mobile-capacitor first.");
|
|
811
833
|
}
|
|
812
834
|
|
|
813
835
|
const currentMainActivitySource = await readFile(mainActivityEntry.absolutePath, "utf8");
|
|
@@ -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,
|
|
@@ -23,6 +23,7 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
23
23
|
verbose: false,
|
|
24
24
|
json: false,
|
|
25
25
|
all: false,
|
|
26
|
+
concrete: false,
|
|
26
27
|
help: true,
|
|
27
28
|
inlineOptions: {}
|
|
28
29
|
},
|
|
@@ -49,6 +50,7 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
49
50
|
verbose: false,
|
|
50
51
|
json: false,
|
|
51
52
|
all: false,
|
|
53
|
+
concrete: false,
|
|
52
54
|
help: false,
|
|
53
55
|
inlineOptions: {}
|
|
54
56
|
};
|
|
@@ -107,6 +109,10 @@ function parseArgs(argv, { createCliError } = {}) {
|
|
|
107
109
|
options.all = true;
|
|
108
110
|
continue;
|
|
109
111
|
}
|
|
112
|
+
if (token === "--concrete") {
|
|
113
|
+
options.concrete = true;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
110
116
|
if (token === "--help" || token === "-h") {
|
|
111
117
|
options.help = true;
|
|
112
118
|
continue;
|
|
@@ -54,7 +54,9 @@ function createCommandHandlerDeps(deps = {}) {
|
|
|
54
54
|
removeManagedViteProxyEntries: deps.removeManagedViteProxyEntries,
|
|
55
55
|
hashBuffer: deps.hashBuffer,
|
|
56
56
|
rm: deps.rm,
|
|
57
|
-
|
|
57
|
+
discoverShellOutletSourcePathsFromApp: deps.discoverShellOutletSourcePathsFromApp,
|
|
58
|
+
discoverShellOutletTargetsFromApp: deps.discoverShellOutletTargetsFromApp,
|
|
59
|
+
discoverPlacementTopologyFromApp: deps.discoverPlacementTopologyFromApp
|
|
58
60
|
};
|
|
59
61
|
}
|
|
60
62
|
|
|
@@ -173,20 +173,24 @@ const COMMAND_DESCRIPTORS = Object.freeze({
|
|
|
173
173
|
aliases: Object.freeze([]),
|
|
174
174
|
showInOverview: true,
|
|
175
175
|
summary: "Run JSKIT-managed mobile-shell helpers.",
|
|
176
|
-
minimalUse: "jskit mobile
|
|
176
|
+
minimalUse: "jskit mobile android dev",
|
|
177
177
|
parameters: Object.freeze([
|
|
178
|
+
Object.freeze({
|
|
179
|
+
name: "<platform>",
|
|
180
|
+
description: "Currently only android is supported."
|
|
181
|
+
}),
|
|
178
182
|
Object.freeze({
|
|
179
183
|
name: "<subcommand>",
|
|
180
|
-
description: "
|
|
184
|
+
description: "dev | devices | sync | tunnel | restart | run | build | doctor."
|
|
181
185
|
})
|
|
182
186
|
]),
|
|
183
187
|
defaults: Object.freeze([
|
|
184
|
-
"
|
|
185
|
-
"Use jskit mobile <
|
|
186
|
-
"--dry-run is accepted by jskit mobile
|
|
187
|
-
"--devlinks runs npm run --if-present devlinks after
|
|
188
|
+
"Install the shell first with jskit add package @jskit-ai/mobile-capacitor.",
|
|
189
|
+
"Use jskit mobile <platform> help for platform-specific usage.",
|
|
190
|
+
"--dry-run is accepted by jskit mobile android sync/run/build.",
|
|
191
|
+
"--devlinks runs npm run --if-present devlinks after jskit mobile android sync maintenance for development-only relinking."
|
|
188
192
|
]),
|
|
189
|
-
fullUse: "jskit mobile <subcommand> [help] [--dry-run] [--<option> <value>...]",
|
|
193
|
+
fullUse: "jskit mobile <platform> <subcommand> [help] [--dry-run] [--<option> <value>...]",
|
|
190
194
|
showHelpOnBareInvocation: true,
|
|
191
195
|
handlerName: "commandMobile",
|
|
192
196
|
allowedFlagKeys: Object.freeze(["dryRun", "devlinks"]),
|
|
@@ -313,14 +317,16 @@ const COMMAND_DESCRIPTORS = Object.freeze({
|
|
|
313
317
|
minimalUse: "jskit list-placements",
|
|
314
318
|
parameters: Object.freeze([]),
|
|
315
319
|
defaults: Object.freeze([
|
|
316
|
-
"
|
|
317
|
-
"
|
|
318
|
-
"
|
|
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.",
|
|
323
|
+
"Use --concrete to inspect low-level ShellOutlet recipients.",
|
|
324
|
+
"Use --all to show both semantic placements and concrete recipients."
|
|
319
325
|
]),
|
|
320
|
-
fullUse: "jskit list-placements [--json]",
|
|
326
|
+
fullUse: "jskit list-placements [--details] [--concrete] [--all] [--json]",
|
|
321
327
|
showHelpOnBareInvocation: false,
|
|
322
328
|
handlerName: "commandListPlacements",
|
|
323
|
-
allowedFlagKeys: Object.freeze(["json"]),
|
|
329
|
+
allowedFlagKeys: Object.freeze(["details", "concrete", "all", "json"]),
|
|
324
330
|
inlineOptionMode: "none",
|
|
325
331
|
allowedValueOptionNames: Object.freeze([])
|
|
326
332
|
}),
|
|
@@ -5,7 +5,11 @@ import {
|
|
|
5
5
|
writeFile
|
|
6
6
|
} from "node:fs/promises";
|
|
7
7
|
import path from "node:path";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
discoverPlacementTopologyFromApp,
|
|
10
|
+
discoverShellOutletSourcePathsFromApp,
|
|
11
|
+
discoverShellOutletTargetsFromApp
|
|
12
|
+
} from "@jskit-ai/kernel/server/support";
|
|
9
13
|
import { createCliError } from "../shared/cliError.js";
|
|
10
14
|
import {
|
|
11
15
|
createColorFormatter,
|
|
@@ -146,7 +150,9 @@ const commandHandlers = createCommandHandlers(
|
|
|
146
150
|
removeManagedViteProxyEntries,
|
|
147
151
|
hashBuffer,
|
|
148
152
|
rm,
|
|
149
|
-
|
|
153
|
+
discoverShellOutletSourcePathsFromApp,
|
|
154
|
+
discoverShellOutletTargetsFromApp,
|
|
155
|
+
discoverPlacementTopologyFromApp
|
|
150
156
|
})
|
|
151
157
|
);
|
|
152
158
|
|