@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.
@@ -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 android [--dry-run] [--devlinks]",
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 mobile add capacitor."
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 android [--target <device-id>] [--dry-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: "Run the local Android phone workflow: sync, install/run, then create the adb reverse tunnel.",
66
- usage: "jskit mobile dev android [--target <device-id>]",
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
- "Runs jskit mobile sync android first.",
75
- "Runs jskit mobile run android --target <device-id> without re-syncing a second time.",
76
- "Runs jskit mobile tunnel android --target <device-id> last so local backend traffic reaches your laptop."
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 android",
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 android --target <device-id> [--port <port>]",
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: "Required adb device serial to tunnel to."
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 android --target <device-id>",
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: "Required adb device serial to restart."
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 android [--dry-run]",
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) => left.name.localeCompare(right.name));
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 === "add" || definition.name === "sync" || definition.name === "run" || definition.name === "build") {
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 (/\bconfig\.mobile\b|\bmobile\s*:/u.test(currentSource)) {
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 mobile add capacitor first.`
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 mobile add capacitor first.`
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 android to refresh the Android shell.`
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 android to refresh the Android shell.`
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 mobile add capacitor first.`
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 mobile add capacitor first.");
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
- discoverShellOutletTargetsFromApp: deps.discoverShellOutletTargetsFromApp
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 add capacitor",
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: "add (more mobile helpers will live here as Stage 1 expands)."
184
+ description: "dev | devices | sync | tunnel | restart | run | build | doctor."
181
185
  })
182
186
  ]),
183
187
  defaults: Object.freeze([
184
- "The first supported flow is jskit mobile add capacitor.",
185
- "Use jskit mobile <subcommand> help for subcommand-specific usage.",
186
- "--dry-run is accepted by jskit mobile add/sync/run/build.",
187
- "--devlinks runs npm run --if-present devlinks after install/sync maintenance for development-only relinking."
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
- "Discovers placement outlets from app Vue ShellOutlet tags and route meta.",
317
- "Includes placement outlets contributed by installed package metadata.",
318
- "Shows plain text by default; use --json for structured output."
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 { discoverShellOutletTargetsFromApp } from "@jskit-ai/kernel/server/support";
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
- discoverShellOutletTargetsFromApp
153
+ discoverShellOutletSourcePathsFromApp,
154
+ discoverShellOutletTargetsFromApp,
155
+ discoverPlacementTopologyFromApp
150
156
  })
151
157
  );
152
158