@akanjs/cli 2.3.6-rc.1 → 2.3.6-rc.3

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.
@@ -2490,6 +2490,7 @@ var PAGE_ROUTE_EXPORTS = new Set([
2490
2490
  ]);
2491
2491
  var ROOT_LAYOUT_EXPORTS = new Set([
2492
2492
  "default",
2493
+ "pageConfig",
2493
2494
  "head",
2494
2495
  "metadata",
2495
2496
  "generateHead",
@@ -2506,6 +2507,7 @@ var ROOT_LAYOUT_EXPORTS = new Set([
2506
2507
  ]);
2507
2508
  var LAYOUT_ROUTE_EXPORTS = new Set([
2508
2509
  "default",
2510
+ "pageConfig",
2509
2511
  "head",
2510
2512
  "metadata",
2511
2513
  "generateHead",
@@ -5195,6 +5197,7 @@ export async function generateMetadata(props: PageProps) {
5195
5197
 
5196
5198
  export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
5197
5199
  export const Error = userLayout.Error ?? inheritedLayout.Error;
5200
+ export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
5198
5201
 
5199
5202
  export default function GeneratedLayout({ children, params, searchParams }: LayoutProps) {
5200
5203
  return (
@@ -5236,6 +5239,7 @@ export async function generateMetadata(props: PageProps) {
5236
5239
 
5237
5240
  export const NotFound = userLayout.NotFound ?? inheritedLayout.NotFound;
5238
5241
  export const Error = userLayout.Error ?? inheritedLayout.Error;
5242
+ export const pageConfig = userLayout.pageConfig ?? inheritedLayout.pageConfig;
5239
5243
 
5240
5244
  export default function GeneratedLayout({ children, params, searchParams }: LayoutProps) {
5241
5245
  return <UserLayout params={params} searchParams={searchParams}>{children}</UserLayout>;
@@ -9484,7 +9488,9 @@ class Builder {
9484
9488
  }
9485
9489
  // pkgs/@akanjs/devkit/capacitorApp.ts
9486
9490
  import { cp as cp2, mkdir as mkdir11, rm as rm5 } from "fs/promises";
9491
+ import os from "os";
9487
9492
  import path38 from "path";
9493
+ import { select as select2 } from "@inquirer/prompts";
9488
9494
  import { MobileProject } from "@trapezedev/project";
9489
9495
  import { capitalize as capitalize4 } from "akanjs/common";
9490
9496
 
@@ -9663,6 +9669,407 @@ var resolveMobilePath = (target, pathname) => {
9663
9669
  };
9664
9670
  var targetHtmlFilename = (target) => target.basePath?.replace(/^\/+|\/+$/g, "") ? `${target.basePath.replace(/^\/+|\/+$/g, "")}.html` : "index.html";
9665
9671
  // pkgs/@akanjs/devkit/capacitorApp.ts
9672
+ var iosNativeBlockedEnvKeys = new Set([
9673
+ "AR",
9674
+ "AS",
9675
+ "CC",
9676
+ "CFLAGS",
9677
+ "CONDA_BUILD_SYSROOT",
9678
+ "CONDA_PREFIX",
9679
+ "CPP",
9680
+ "CPPFLAGS",
9681
+ "CPATH",
9682
+ "CXX",
9683
+ "CXXFLAGS",
9684
+ "LD",
9685
+ "LDFLAGS",
9686
+ "LIBRARY_PATH",
9687
+ "MACOSX_DEPLOYMENT_TARGET",
9688
+ "NM",
9689
+ "OBJC",
9690
+ "OBJCXX",
9691
+ "PREFIX",
9692
+ "RANLIB",
9693
+ "SDKROOT",
9694
+ "STRIP"
9695
+ ]);
9696
+ var rootCapacitorConfigFilenames = [
9697
+ "capacitor.config.ts",
9698
+ "capacitor.config.js",
9699
+ "capacitor.config.json"
9700
+ ];
9701
+ var rootCapacitorConfigPaths = (appRoot) => rootCapacitorConfigFilenames.map((file) => path38.join(appRoot, file));
9702
+ async function clearRootCapacitorConfigs(appRoot) {
9703
+ await Promise.all(rootCapacitorConfigPaths(appRoot).map((file) => rm5(file, { force: true })));
9704
+ }
9705
+ async function writeRootCapacitorConfig(appRoot, content) {
9706
+ await clearRootCapacitorConfigs(appRoot);
9707
+ await Bun.write(path38.join(appRoot, "capacitor.config.json"), content);
9708
+ }
9709
+ var getLocalIP = () => {
9710
+ const interfaces = os.networkInterfaces();
9711
+ for (const iface of Object.values(interfaces)) {
9712
+ if (!iface)
9713
+ continue;
9714
+ for (const alias of iface) {
9715
+ if (alias.family === "IPv4" && !alias.internal)
9716
+ return alias.address;
9717
+ }
9718
+ }
9719
+ return "127.0.0.1";
9720
+ };
9721
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
9722
+ var asString = (value) => typeof value === "string" ? value : undefined;
9723
+ var firstString = (...values) => values.find((value) => typeof value === "string");
9724
+ var scoreIosDeviceTarget = (target) => {
9725
+ const state = target.state?.toLowerCase() ?? "";
9726
+ return (state.includes("available") ? 4 : 0) + (state.includes("wired") ? 2 : 0) + (state.includes("paired") ? 1 : 0);
9727
+ };
9728
+ var dedupeIosRunTargets = (targets) => {
9729
+ const byKey = new Map;
9730
+ const runnableTargets = targets.filter((target) => !target.state?.toLowerCase().includes("unavailable"));
9731
+ for (const target of runnableTargets) {
9732
+ const key = target.xcodebuildId ?? target.id;
9733
+ const current = byKey.get(key);
9734
+ if (!current || scoreIosDeviceTarget(target) > scoreIosDeviceTarget(current))
9735
+ byKey.set(key, target);
9736
+ }
9737
+ return [...byKey.values()];
9738
+ };
9739
+ function walkRecords(value, visit) {
9740
+ if (Array.isArray(value)) {
9741
+ for (const item of value)
9742
+ walkRecords(item, visit);
9743
+ return;
9744
+ }
9745
+ if (!isRecord(value))
9746
+ return;
9747
+ visit(value);
9748
+ for (const item of Object.values(value))
9749
+ walkRecords(item, visit);
9750
+ }
9751
+ function parseDevicectlDevices(output) {
9752
+ try {
9753
+ const json = JSON.parse(output);
9754
+ const targets = new Map;
9755
+ walkRecords(json, (record) => {
9756
+ const deviceProperties = isRecord(record.deviceProperties) ? record.deviceProperties : {};
9757
+ const hardwareProperties = isRecord(record.hardwareProperties) ? record.hardwareProperties : {};
9758
+ const connectionProperties = isRecord(record.connectionProperties) ? record.connectionProperties : {};
9759
+ const devicectlId = firstString(record.identifier, record.deviceIdentifier);
9760
+ const potentialHostnames = Array.isArray(connectionProperties.potentialHostnames) ? connectionProperties.potentialHostnames.filter((value) => typeof value === "string") : [];
9761
+ const hostnameUdid = potentialHostnames.map((hostname) => hostname.match(/([0-9A-Fa-f]{8}-[0-9A-Fa-f]{16})\.coredevice\.local/)?.[1]).find((value) => Boolean(value));
9762
+ const udid = firstString(hardwareProperties.udid, hostnameUdid, record.udid, record.UDID);
9763
+ const id = udid ?? devicectlId;
9764
+ const name = firstString(deviceProperties.name, record.name, record.deviceName, record.displayName);
9765
+ if (!id || !name)
9766
+ return;
9767
+ const state = [
9768
+ firstString(record.state, record.connectionState, record.availability, connectionProperties.tunnelState),
9769
+ firstString(connectionProperties.transportType),
9770
+ firstString(connectionProperties.pairingState)
9771
+ ].filter(Boolean).join(" ");
9772
+ targets.set(id, { id, name, kind: "device", state, devicectlId, xcodebuildId: udid ?? id });
9773
+ });
9774
+ return dedupeIosRunTargets([...targets.values()]);
9775
+ } catch {
9776
+ const targets = [];
9777
+ for (const line of output.split(/\r?\n/)) {
9778
+ const id = line.match(/[0-9A-Fa-f]{8}-[0-9A-Fa-f]{16}|[0-9A-Fa-f-]{25,}/)?.[0];
9779
+ if (!id)
9780
+ continue;
9781
+ const name = line.replace(id, "").replace(/[()]/g, " ").trim().replace(/\s+/g, " ") || id;
9782
+ targets.push({ id, name, kind: "device" });
9783
+ }
9784
+ return dedupeIosRunTargets(targets);
9785
+ }
9786
+ }
9787
+ function parseSimctlDevices(output) {
9788
+ try {
9789
+ const json = JSON.parse(output);
9790
+ const devices = json.devices ?? {};
9791
+ return Object.values(devices).flatMap((runtimeDevices) => runtimeDevices).filter(isRecord).flatMap((device) => {
9792
+ const id = firstString(device.udid, device.UDID, device.identifier);
9793
+ const name = firstString(device.name, device.displayName);
9794
+ const isAvailable = device.isAvailable !== false && device.availabilityError === undefined;
9795
+ if (!id || !name || !isAvailable)
9796
+ return [];
9797
+ return [{ id, name, kind: "simulator", state: asString(device.state) }];
9798
+ });
9799
+ } catch {
9800
+ const targets = [];
9801
+ for (const line of output.split(/\r?\n/)) {
9802
+ const match = line.match(/^\s*(.+?)\s+\(([0-9A-Fa-f-]{20,})\)\s+\(([^)]+)\)/);
9803
+ if (!match)
9804
+ continue;
9805
+ targets.push({ id: match[2], name: match[1].trim(), kind: "simulator", state: match[3] });
9806
+ }
9807
+ return targets;
9808
+ }
9809
+ }
9810
+ function buildIosNativeRunCommand({
9811
+ appRoot,
9812
+ device,
9813
+ scheme = "App",
9814
+ configuration = "Debug"
9815
+ }) {
9816
+ const derivedDataPath = path38.join(appRoot, "ios/DerivedData", device.id);
9817
+ const productPlatform = device.kind === "device" ? "iphoneos" : "iphonesimulator";
9818
+ const destination = device.kind === "device" ? `id=${device.xcodebuildId ?? device.id}` : `platform=iOS Simulator,id=${device.id}`;
9819
+ return {
9820
+ configuration,
9821
+ derivedDataPath,
9822
+ appPath: path38.join(derivedDataPath, "Build/Products", `${configuration}-${productPlatform}`, "App.app"),
9823
+ xcodebuildArgs: [
9824
+ "-project",
9825
+ "App.xcodeproj",
9826
+ "-scheme",
9827
+ scheme,
9828
+ "-configuration",
9829
+ configuration,
9830
+ "-destination",
9831
+ destination,
9832
+ "-derivedDataPath",
9833
+ derivedDataPath,
9834
+ "build"
9835
+ ]
9836
+ };
9837
+ }
9838
+ function classifyIosRunFailure(log) {
9839
+ const lower = log.toLowerCase();
9840
+ if (lower.includes("unknown argument: '-index-store-path'") || lower.includes("compiler was not recognized")) {
9841
+ return {
9842
+ kind: "compiler-toolchain",
9843
+ title: "iOS build is using a non-Xcode compiler from the shell environment.",
9844
+ detail: "Akan removes common Conda/compiler environment variables for native iOS runs. If this persists, run outside the activated toolchain environment."
9845
+ };
9846
+ }
9847
+ if (lower.includes("developer mode") && lower.includes("disabled")) {
9848
+ return {
9849
+ kind: "device-state",
9850
+ title: "iOS device Developer Mode is disabled.",
9851
+ detail: "Enable Developer Mode on the iPhone, then reconnect and run the command again."
9852
+ };
9853
+ }
9854
+ if (lower.includes("untrusted") || lower.includes("not paired") || lower.includes("locked")) {
9855
+ return {
9856
+ kind: "device-state",
9857
+ title: "iOS device is not ready for installation.",
9858
+ detail: "Unlock the iPhone, trust this computer, and make sure the device is paired before retrying."
9859
+ };
9860
+ }
9861
+ if (lower.includes('unable to find utility "devicectl"') || lower.includes("devicectl") && lower.includes("not found")) {
9862
+ return {
9863
+ kind: "devicectl-unavailable",
9864
+ title: "Xcode devicectl is not available.",
9865
+ detail: "Install a recent Xcode version and verify xcode-select points to that Xcode installation."
9866
+ };
9867
+ }
9868
+ if (lower.includes("there are no accounts registered with xcode") || lower.includes("unable to log in with account")) {
9869
+ return {
9870
+ kind: "apple-account",
9871
+ title: "Xcode Apple ID is not available.",
9872
+ detail: "Sign in to an Apple ID in Xcode Settings > Accounts, then retry the Akan iOS command."
9873
+ };
9874
+ }
9875
+ if (lower.includes("failed registering bundle identifier") || lower.includes("cannot be registered to your development team")) {
9876
+ return {
9877
+ kind: "bundle-identifier",
9878
+ title: "iOS bundle identifier is not available for this Apple Developer Team.",
9879
+ detail: "Change mobile appId to a globally unique bundle identifier that your team can register, then rerun the iOS command."
9880
+ };
9881
+ }
9882
+ if (lower.includes("does not have permission") || lower.includes("your account") && lower.includes("permission")) {
9883
+ return {
9884
+ kind: "team-permission",
9885
+ title: "Apple Developer Team permission is missing.",
9886
+ detail: "Check that the signed-in Apple ID has permission for the selected DEVELOPMENT_TEAM."
9887
+ };
9888
+ }
9889
+ if (lower.includes("license agreement") || lower.includes("program license agreement")) {
9890
+ return {
9891
+ kind: "license-agreement",
9892
+ title: "Apple Developer Program license agreement is not accepted.",
9893
+ detail: "Accept the latest Apple Developer Program license agreement, then retry."
9894
+ };
9895
+ }
9896
+ if (lower.includes("no signing certificate") || lower.includes("doesn't include signing certificate")) {
9897
+ return {
9898
+ kind: "certificate",
9899
+ title: "iOS development signing certificate is missing.",
9900
+ detail: "Create or download an Apple Development certificate for the selected team."
9901
+ };
9902
+ }
9903
+ if (lower.includes("device") && lower.includes("not") && lower.includes("registered")) {
9904
+ return {
9905
+ kind: "device-registration",
9906
+ title: "iPhone is not registered in the provisioning profile.",
9907
+ detail: "Allow provisioning updates with a team that can register this device, or register the device manually."
9908
+ };
9909
+ }
9910
+ if (lower.includes("no profiles for") || lower.includes("requires a provisioning profile")) {
9911
+ return {
9912
+ kind: "provisioning-profile",
9913
+ title: "Matching iOS provisioning profile was not found.",
9914
+ detail: "Akan can request Xcode provisioning updates for physical devices, but the Apple account and team must be valid."
9915
+ };
9916
+ }
9917
+ return {
9918
+ kind: "unknown",
9919
+ title: "iOS native run failed.",
9920
+ detail: "Review the xcodebuild/devicectl output above. You can retry with --noAllowProvisioningUpdates to use the conservative path."
9921
+ };
9922
+ }
9923
+ function formatIosRunFailureMessage(input2) {
9924
+ return [
9925
+ input2.classification.title,
9926
+ input2.classification.detail,
9927
+ `Mobile target: ${input2.targetName}`,
9928
+ `Bundle ID: ${input2.appId}`,
9929
+ input2.teamId ? `Development Team: ${input2.teamId}` : null,
9930
+ "Capacitor is still used for native project generation and sync; Akan only runs the native build/install step directly."
9931
+ ].filter((line) => Boolean(line)).join(`
9932
+ `);
9933
+ }
9934
+ function sanitizeIosNativeRunEnv(env) {
9935
+ return Object.fromEntries(Object.entries(env).filter(([key]) => !iosNativeBlockedEnvKeys.has(key)));
9936
+ }
9937
+ var androidReleaseSigningKeys = [
9938
+ "MYAPP_RELEASE_STORE_FILE",
9939
+ "MYAPP_RELEASE_STORE_PASSWORD",
9940
+ "MYAPP_RELEASE_KEY_ALIAS",
9941
+ "MYAPP_RELEASE_KEY_PASSWORD"
9942
+ ];
9943
+ function getMissingAndroidReleaseSigningKeys({
9944
+ env = process.env,
9945
+ gradleProperties = ""
9946
+ } = {}) {
9947
+ return androidReleaseSigningKeys.filter((key) => {
9948
+ const gradleEnvKey = `ORG_GRADLE_PROJECT_${key}`;
9949
+ return env[key] === undefined && env[gradleEnvKey] === undefined && !new RegExp(`^\\s*${key}\\s*=`, "m").test(gradleProperties);
9950
+ });
9951
+ }
9952
+ function formatAndroidReleaseSigningError(missingKeys) {
9953
+ return [
9954
+ "Android release signing configuration is incomplete.",
9955
+ `Missing: ${missingKeys.join(", ")}`,
9956
+ "Set these values in android/gradle.properties or ORG_GRADLE_PROJECT_* environment variables before building a release artifact."
9957
+ ].join(`
9958
+ `);
9959
+ }
9960
+ function getAdbDeviceStateIssues(output) {
9961
+ return output.split(/\r?\n/).map((line) => line.trim().split(/\s+/)).filter(([id, state]) => id && state && id !== "List").flatMap(([id, state]) => {
9962
+ if (state === "unauthorized")
9963
+ return [`Android device ${id} is unauthorized. Confirm USB debugging authorization on the device.`];
9964
+ if (state === "offline")
9965
+ return [`Android device ${id} is offline. Reconnect the device or restart adb.`];
9966
+ return [];
9967
+ });
9968
+ }
9969
+ var mergeAllowNavigation = (configured, localIp) => {
9970
+ const values = Array.isArray(configured) ? configured.filter((value) => typeof value === "string") : [];
9971
+ if (localIp)
9972
+ values.push(localIp);
9973
+ values.push("localhost");
9974
+ return [...new Set(values)];
9975
+ };
9976
+ function assertJsonSerializable(value, label = "capacitor.config", seen = new WeakSet) {
9977
+ if (value === null)
9978
+ return;
9979
+ const valueType = typeof value;
9980
+ if (valueType === "function" || valueType === "symbol" || valueType === "bigint" || valueType === "undefined") {
9981
+ throw new Error(`${label} must be JSON serializable. Found ${valueType}.`);
9982
+ }
9983
+ if (valueType === "number" && !Number.isFinite(value)) {
9984
+ throw new Error(`${label} must be JSON serializable. Found non-finite number.`);
9985
+ }
9986
+ if (valueType !== "object")
9987
+ return;
9988
+ const objectValue = value;
9989
+ if (seen.has(objectValue))
9990
+ throw new Error(`${label} must be JSON serializable. Found circular reference.`);
9991
+ seen.add(objectValue);
9992
+ if (Array.isArray(value)) {
9993
+ value.forEach((item, index) => {
9994
+ assertJsonSerializable(item, `${label}[${index}]`, seen);
9995
+ });
9996
+ return;
9997
+ }
9998
+ for (const [key, item] of Object.entries(value)) {
9999
+ assertJsonSerializable(item, `${label}.${key}`, seen);
10000
+ }
10001
+ }
10002
+ function materializeCapacitorConfig(target, { operation, localServerUrl, localIp }) {
10003
+ const {
10004
+ name,
10005
+ basePath: _basePath,
10006
+ version: _version,
10007
+ buildNum: _buildNum,
10008
+ assets: _assets,
10009
+ permissions: _permissions,
10010
+ links: _links,
10011
+ files: _files,
10012
+ appId,
10013
+ appName,
10014
+ webDir: _webDir,
10015
+ plugins,
10016
+ server,
10017
+ android,
10018
+ ios,
10019
+ cordova,
10020
+ experimental,
10021
+ ...capacitorConfig
10022
+ } = target;
10023
+ const serverConfig = isRecord(server) ? server : undefined;
10024
+ const cordovaConfig = isRecord(cordova) ? cordova : undefined;
10025
+ const experimentalConfig = isRecord(experimental) ? experimental : undefined;
10026
+ const pluginsConfig = isRecord(plugins) ? plugins : {};
10027
+ const keyboardPluginConfig = isRecord(pluginsConfig.Keyboard) ? pluginsConfig.Keyboard : {};
10028
+ const config = {
10029
+ ...capacitorConfig,
10030
+ appId,
10031
+ appName,
10032
+ webDir: path38.posix.join(".akan", "mobile", name, "www"),
10033
+ plugins: {
10034
+ CapacitorCookies: { enabled: true },
10035
+ ...pluginsConfig,
10036
+ Keyboard: {
10037
+ resize: "none",
10038
+ ...keyboardPluginConfig
10039
+ }
10040
+ },
10041
+ android: {
10042
+ ...isRecord(android) ? android : {},
10043
+ path: "android"
10044
+ },
10045
+ ios: {
10046
+ ...isRecord(ios) ? ios : {},
10047
+ path: "ios"
10048
+ }
10049
+ };
10050
+ if (operation === "local") {
10051
+ if (!localServerUrl)
10052
+ throw new Error(`Local server URL is required for mobile target '${name}'.`);
10053
+ config.server = {
10054
+ ...serverConfig,
10055
+ androidScheme: "http",
10056
+ url: localServerUrl,
10057
+ cleartext: true,
10058
+ allowNavigation: mergeAllowNavigation(serverConfig?.allowNavigation, localIp)
10059
+ };
10060
+ } else if (serverConfig && Object.keys(serverConfig).length > 0) {
10061
+ config.server = serverConfig;
10062
+ }
10063
+ if (cordovaConfig && Object.keys(cordovaConfig).length > 0) {
10064
+ config.cordova = cordovaConfig;
10065
+ }
10066
+ if (experimentalConfig && Object.keys(experimentalConfig).length > 0) {
10067
+ config.experimental = experimentalConfig;
10068
+ }
10069
+ assertJsonSerializable(config);
10070
+ return config;
10071
+ }
10072
+
9666
10073
  class CapacitorApp {
9667
10074
  app;
9668
10075
  target;
@@ -9695,7 +10102,6 @@ class CapacitorApp {
9695
10102
  regenerate = false
9696
10103
  } = {}) {
9697
10104
  await mkdir11(this.targetRoot, { recursive: true });
9698
- await this.#writeCapacitorConfig();
9699
10105
  if (regenerate) {
9700
10106
  if (!platform || platform === "ios")
9701
10107
  await rm5(path38.join(this.app.cwdPath, this.iosRootPath), { recursive: true, force: true });
@@ -9743,12 +10149,122 @@ class CapacitorApp {
9743
10149
  async openIos() {
9744
10150
  await this.#spawnMobile("npx", ["cap", "open", "ios"], { operation: "local", env: "local" });
9745
10151
  }
9746
- async runIos({ operation, env, regenerate = false }) {
10152
+ async runIos({ operation, env, regenerate = false, noAllowProvisioningUpdates = false, iosDeviceId }) {
9747
10153
  if (operation === "release")
9748
10154
  await this.prepareWww();
9749
10155
  await this.#prepareIos({ operation, env, regenerate });
9750
- const args = ["cap", "run", "ios"];
9751
- await this.#spawnMobile("npx", args, { operation, env }, { stdio: "inherit" });
10156
+ const runTarget = await this.#selectIosRunTarget(iosDeviceId);
10157
+ if (runTarget.kind === "simulator") {
10158
+ await this.#spawnMobile("npx", ["cap", "run", "ios", "--target", runTarget.id], { operation, env }, { stdio: "inherit" });
10159
+ return;
10160
+ }
10161
+ await this.#runIosPhysicalDevice({ operation, env, runTarget, noAllowProvisioningUpdates });
10162
+ }
10163
+ async#selectIosRunTarget(deviceId) {
10164
+ const targets = await this.#loadIosRunTargets();
10165
+ if (deviceId) {
10166
+ const found = targets.find((target) => target.id === deviceId);
10167
+ if (!found)
10168
+ throw new Error(`iOS run target '${deviceId}' was not found.`);
10169
+ return found;
10170
+ }
10171
+ if (targets.length === 0) {
10172
+ throw new Error("No iOS run targets found. Open Simulator or connect an iPhone, then retry.");
10173
+ }
10174
+ return await select2({
10175
+ message: "Select iOS run target",
10176
+ choices: targets.map((target) => ({
10177
+ name: `[${target.kind}] ${target.name}${target.state ? ` (${target.state})` : ""}`,
10178
+ value: target
10179
+ }))
10180
+ });
10181
+ }
10182
+ async#loadIosRunTargets() {
10183
+ const devices = await this.#loadPhysicalIosDevices();
10184
+ const simulators = await this.#loadIosSimulators();
10185
+ return [...devices, ...simulators];
10186
+ }
10187
+ async#loadPhysicalIosDevices() {
10188
+ try {
10189
+ return parseDevicectlDevices(await this.#spawn("xcrun", ["devicectl", "list", "devices", "--json-output", "-"]));
10190
+ } catch (jsonError) {
10191
+ try {
10192
+ return parseDevicectlDevices(await this.#spawn("xcrun", ["devicectl", "list", "devices"]));
10193
+ } catch (textError) {
10194
+ const classification = classifyIosRunFailure(`${jsonError instanceof Error ? jsonError.message : ""}
10195
+ ${textError instanceof Error ? textError.message : ""}`);
10196
+ if (classification.kind === "devicectl-unavailable")
10197
+ this.app.logger.warn(classification.detail);
10198
+ return [];
10199
+ }
10200
+ }
10201
+ }
10202
+ async#loadIosSimulators() {
10203
+ try {
10204
+ return parseSimctlDevices(await this.#spawn("xcrun", ["simctl", "list", "devices", "available", "--json"]));
10205
+ } catch {
10206
+ try {
10207
+ return parseSimctlDevices(await this.#spawn("xcrun", ["simctl", "list", "devices", "available"]));
10208
+ } catch {
10209
+ return [];
10210
+ }
10211
+ }
10212
+ }
10213
+ async#runIosPhysicalDevice({
10214
+ operation,
10215
+ env,
10216
+ runTarget,
10217
+ noAllowProvisioningUpdates
10218
+ }) {
10219
+ const mobileEnv = sanitizeIosNativeRunEnv(await this.#commandEnv(operation, env));
10220
+ const configContent = await this.#writeCapacitorConfig({ operation }, mobileEnv);
10221
+ await this.#writeRootCapacitorConfig(configContent);
10222
+ const scheme = this.#iosScheme();
10223
+ const command = buildIosNativeRunCommand({
10224
+ appRoot: this.app.cwdPath,
10225
+ device: runTarget,
10226
+ scheme,
10227
+ configuration: operation === "release" ? "Release" : "Debug"
10228
+ });
10229
+ const xcodebuildArgs = noAllowProvisioningUpdates ? command.xcodebuildArgs : [...command.xcodebuildArgs.slice(0, -1), "-allowProvisioningUpdates", ...command.xcodebuildArgs.slice(-1)];
10230
+ try {
10231
+ await this.#spawn("xcodebuild", xcodebuildArgs, {
10232
+ cwd: path38.join(this.app.cwdPath, this.iosProjectPath),
10233
+ env: mobileEnv
10234
+ });
10235
+ const devicectlId = runTarget.devicectlId ?? runTarget.id;
10236
+ await this.#spawn("xcrun", ["devicectl", "device", "install", "app", "--device", devicectlId, command.appPath], {
10237
+ env: mobileEnv
10238
+ });
10239
+ await this.#spawn("xcrun", ["devicectl", "device", "process", "launch", "--device", devicectlId, this.target.appId], {
10240
+ env: mobileEnv
10241
+ });
10242
+ } catch (error) {
10243
+ throw new Error(formatIosRunFailureMessage({
10244
+ classification: classifyIosRunFailure(this.#errorOutput(error)),
10245
+ appId: this.target.appId,
10246
+ targetName: this.target.name,
10247
+ teamId: await this.#getIosDevelopmentTeam()
10248
+ }));
10249
+ } finally {
10250
+ await this.#clearRootCapacitorConfigs();
10251
+ }
10252
+ }
10253
+ #iosScheme() {
10254
+ return isRecord(this.target.ios) && typeof this.target.ios.scheme === "string" ? this.target.ios.scheme : "App";
10255
+ }
10256
+ async#getIosDevelopmentTeam() {
10257
+ const pbxprojPath = path38.join(this.app.cwdPath, this.iosProjectPath, "App.xcodeproj/project.pbxproj");
10258
+ if (!await Bun.file(pbxprojPath).exists())
10259
+ return;
10260
+ return (await Bun.file(pbxprojPath).text()).match(/DEVELOPMENT_TEAM = ([^;]+);/)?.[1]?.trim();
10261
+ }
10262
+ #errorOutput(error) {
10263
+ if (error instanceof CommandExecutionError)
10264
+ return `${error.stdout}
10265
+ ${error.stderr}
10266
+ ${error.message}`;
10267
+ return error instanceof Error ? error.message : String(error);
9752
10268
  }
9753
10269
  async#prepareAndroid({ operation, env, regenerate = false }) {
9754
10270
  await this.init({ platform: "android", operation, env, regenerate });
@@ -9802,6 +10318,7 @@ class CapacitorApp {
9802
10318
  async buildAndroid(assembleType, { env = "debug", regenerate = false } = {}) {
9803
10319
  await this.prepareWww();
9804
10320
  await this.#prepareAndroid({ operation: "release", env, regenerate });
10321
+ await this.#assertAndroidReleaseSigningConfig();
9805
10322
  await this.#updateAndroidBuildTypes();
9806
10323
  const isWindows = process.platform === "win32";
9807
10324
  const gradleCommand = isWindows ? "gradlew.bat" : "./gradlew";
@@ -9851,10 +10368,30 @@ class CapacitorApp {
9851
10368
  if (operation === "release")
9852
10369
  await this.prepareWww();
9853
10370
  await this.#prepareAndroid({ operation, env, regenerate });
10371
+ await this.#assertAndroidAdbReady();
9854
10372
  this.app.logger.info(`Running Android in ${operation} mode on ${env} env`);
9855
10373
  const args = ["cap", "run", "android"];
9856
10374
  await this.#spawnMobile("npx", args, { operation, env }, { stdio: "inherit" });
9857
10375
  }
10376
+ async#assertAndroidReleaseSigningConfig() {
10377
+ const gradlePropertiesPath = path38.join(this.app.cwdPath, this.androidRootPath, "gradle.properties");
10378
+ const gradleProperties = await Bun.file(gradlePropertiesPath).exists() ? await Bun.file(gradlePropertiesPath).text() : "";
10379
+ const missingKeys = getMissingAndroidReleaseSigningKeys({ gradleProperties });
10380
+ if (missingKeys.length > 0)
10381
+ throw new Error(formatAndroidReleaseSigningError(missingKeys));
10382
+ }
10383
+ async#assertAndroidAdbReady() {
10384
+ try {
10385
+ const issues = getAdbDeviceStateIssues(await this.#spawn("adb", ["devices"]));
10386
+ if (issues.length > 0)
10387
+ throw new Error(issues.join(`
10388
+ `));
10389
+ } catch (error) {
10390
+ if (error instanceof CommandExecutionError)
10391
+ return;
10392
+ throw error;
10393
+ }
10394
+ }
9858
10395
  async releaseIos() {
9859
10396
  await this.prepareWww();
9860
10397
  await this.#prepareIos({ operation: "release", env: "main" });
@@ -9879,30 +10416,18 @@ class CapacitorApp {
9879
10416
  return html.replace(/<\/head\s*>/i, `${script}
9880
10417
  </head>`);
9881
10418
  }
9882
- async#writeCapacitorConfig() {
10419
+ async#writeCapacitorConfig({ operation }, commandEnv) {
9883
10420
  await mkdir11(this.targetRoot, { recursive: true });
9884
- const appInfoPath = path38.relative(this.app.cwdPath, path38.join(this.app.cwdPath, "akan.app.json")).split(path38.sep).join("/");
9885
- const content = `import type { AppScanResult } from "akanjs";
9886
- import { withBase } from "${process.env.USE_AKANJS_PKGS === "true" ? "../../pkgs/" : ""}akanjs/capacitor.base.config";
9887
- import appInfo from "${appInfoPath.startsWith(".") ? appInfoPath : `./${appInfoPath}`}";
9888
-
9889
- export default withBase(
9890
- (config, target) => ({
9891
- ...config,
9892
- webDir: \`.akan/mobile/\${target.name}/www\`,
9893
- android: {
9894
- ...config.android,
9895
- path: "android",
9896
- },
9897
- ios: {
9898
- ...config.ios,
9899
- path: "ios",
9900
- },
9901
- }),
9902
- appInfo as AppScanResult,
9903
- );
10421
+ const localIp = operation === "local" ? getLocalIP() : undefined;
10422
+ const config = materializeCapacitorConfig(this.target, {
10423
+ operation,
10424
+ localIp,
10425
+ localServerUrl: localIp ? this.#localCsrUrl(localIp, commandEnv) : undefined
10426
+ });
10427
+ const content = `${JSON.stringify(config, null, 2)}
9904
10428
  `;
9905
- await Bun.write(path38.join(this.app.cwdPath, "capacitor.config.ts"), content);
10429
+ await Bun.write(path38.join(this.targetRoot, "capacitor.config.json"), content);
10430
+ return content;
9906
10431
  }
9907
10432
  async#prepareTargetAssets() {
9908
10433
  if (!this.target.assets)
@@ -9999,14 +10524,37 @@ export default withBase(
9999
10524
  ...devPort ? { PORT: devPort, AKAN_PUBLIC_CLIENT_PORT: devPort, AKAN_PUBLIC_SERVER_PORT: devPort } : {}
10000
10525
  });
10001
10526
  }
10527
+ #localCsrUrl(ip, commandEnv) {
10528
+ const basePath2 = this.target.basePath?.replace(/^\/+|\/+$/g, "");
10529
+ const locale = commandEnv.AKAN_PUBLIC_DEFAULT_LOCALE ?? "en";
10530
+ const pathname = basePath2 ? `${locale}/${basePath2}` : `${locale}/`;
10531
+ const port = commandEnv.AKAN_PUBLIC_CLIENT_PORT ?? commandEnv.PORT ?? "8282";
10532
+ const params = new URLSearchParams({ csr: "true", akanMobileTarget: this.target.name });
10533
+ if (basePath2)
10534
+ params.set("akanMobileBasePath", basePath2);
10535
+ return `http://${ip}:${port}/${pathname}?${params}`;
10536
+ }
10537
+ async#clearRootCapacitorConfigs() {
10538
+ await clearRootCapacitorConfigs(this.app.cwdPath);
10539
+ }
10540
+ async#writeRootCapacitorConfig(content) {
10541
+ await writeRootCapacitorConfig(this.app.cwdPath, content);
10542
+ }
10002
10543
  async#spawn(command, args = [], options = {}) {
10003
10544
  return await this.app.spawn(command, args, { cwd: this.app.cwdPath, ...options });
10004
10545
  }
10005
10546
  async#spawnMobile(command, args = [], { operation, env }, options = {}) {
10006
- return await this.#spawn(command, args, {
10007
- ...options,
10008
- env: { ...await this.#commandEnv(operation, env), ...options.env }
10009
- });
10547
+ const mobileEnv = { ...await this.#commandEnv(operation, env), ...options.env };
10548
+ const configContent = await this.#writeCapacitorConfig({ operation }, mobileEnv);
10549
+ await this.#writeRootCapacitorConfig(configContent);
10550
+ try {
10551
+ return await this.#spawn(command, args, {
10552
+ ...options,
10553
+ env: mobileEnv
10554
+ });
10555
+ } finally {
10556
+ await this.#clearRootCapacitorConfigs();
10557
+ }
10010
10558
  }
10011
10559
  async addCamera() {
10012
10560
  await this.#setPermissionInIos({
@@ -10123,7 +10671,7 @@ var Module = createInternalArgToken("Module");
10123
10671
  var Workspace = createInternalArgToken("Workspace");
10124
10672
  // pkgs/@akanjs/devkit/commandDecorators/command.ts
10125
10673
  import path39 from "path";
10126
- import { confirm, input as input2, select as select2 } from "@inquirer/prompts";
10674
+ import { confirm, input as input2, select as select3 } from "@inquirer/prompts";
10127
10675
  import { Logger as Logger10 } from "akanjs/common";
10128
10676
  import chalk6 from "chalk";
10129
10677
  import { program } from "commander";
@@ -10459,7 +11007,7 @@ var getOptionValue = async (argMeta, opt, context) => {
10459
11007
  return defaultValue;
10460
11008
  if (enumChoices) {
10461
11009
  const choices = normalizeEnumChoices(await resolveEnumChoices(argMeta, context) ?? []);
10462
- const choice = await select2({ message: ask ?? desc ?? `Select the ${name} value`, choices });
11010
+ const choice = await select3({ message: ask ?? desc ?? `Select the ${name} value`, choices });
10463
11011
  return choice;
10464
11012
  } else if (nullable)
10465
11013
  return null;
@@ -10531,7 +11079,7 @@ var getInternalArgumentValue = async (argMeta, value, workspace) => {
10531
11079
  else if (value && libNames.includes(value))
10532
11080
  return LibExecutor.from(workspace, value);
10533
11081
  else {
10534
- const sysName = await select2({
11082
+ const sysName = await select3({
10535
11083
  message: `Select the App or Lib name`,
10536
11084
  choices: [...appNames, ...libNames]
10537
11085
  });
@@ -10550,7 +11098,7 @@ var getInternalArgumentValue = async (argMeta, value, workspace) => {
10550
11098
  else if (value && pkgNames.includes(value))
10551
11099
  return PkgExecutor.from(workspace, value);
10552
11100
  else {
10553
- const execName = await select2({
11101
+ const execName = await select3({
10554
11102
  message: `Select the App or Lib or Pkg name`,
10555
11103
  choices: [...appNames, ...libNames, ...pkgNames]
10556
11104
  });
@@ -10566,18 +11114,18 @@ var getInternalArgumentValue = async (argMeta, value, workspace) => {
10566
11114
  } else if (sysType === "app") {
10567
11115
  if (value && appNames.includes(value))
10568
11116
  return AppExecutor.from(workspace, value);
10569
- const appName = await select2({ message: `Select the ${sysType} name`, choices: appNames });
11117
+ const appName = await select3({ message: `Select the ${sysType} name`, choices: appNames });
10570
11118
  return AppExecutor.from(workspace, appName);
10571
11119
  } else if (sysType === "lib") {
10572
11120
  if (value && libNames.includes(value))
10573
11121
  return LibExecutor.from(workspace, value);
10574
- const libName = await select2({ message: `Select the ${sysType} name`, choices: libNames });
11122
+ const libName = await select3({ message: `Select the ${sysType} name`, choices: libNames });
10575
11123
  return LibExecutor.from(workspace, libName);
10576
11124
  } else if (sysType === "pkg") {
10577
11125
  const pkgs = await workspace.getPkgs();
10578
11126
  if (value && pkgs.includes(value))
10579
11127
  return PkgExecutor.from(workspace, value);
10580
- const pkgName = await select2({ message: `Select the ${sysType} name`, choices: pkgs });
11128
+ const pkgName = await select3({ message: `Select the ${sysType} name`, choices: pkgs });
10581
11129
  return PkgExecutor.from(workspace, pkgName);
10582
11130
  } else if (sysType === "module") {
10583
11131
  if (value) {
@@ -10599,7 +11147,7 @@ var getInternalArgumentValue = async (argMeta, value, workspace) => {
10599
11147
  } else
10600
11148
  throw new Error(`Invalid system name: ${sysName}`);
10601
11149
  }
10602
- const { type, name } = await select2({
11150
+ const { type, name } = await select3({
10603
11151
  message: `select the App or Lib name`,
10604
11152
  choices: [
10605
11153
  ...appNames.map((name2) => ({ name: name2, value: { type: "app", name: name2 } })),
@@ -10608,7 +11156,7 @@ var getInternalArgumentValue = async (argMeta, value, workspace) => {
10608
11156
  });
10609
11157
  const executor = type === "app" ? AppExecutor.from(workspace, name) : LibExecutor.from(workspace, name);
10610
11158
  const modules = await executor.getModules();
10611
- const moduleName = await select2({
11159
+ const moduleName = await select3({
10612
11160
  message: `Select the module name`,
10613
11161
  choices: modules.map((name2) => ({ name: `${executor.name}:${name2}`, value: name2 }))
10614
11162
  });
@@ -11046,7 +11594,7 @@ var getRelatedCnsts = (constantFilePath) => {
11046
11594
  };
11047
11595
  // pkgs/@akanjs/devkit/prompter.ts
11048
11596
  import fsPromise from "fs/promises";
11049
- import { input as input3, select as select3 } from "@inquirer/prompts";
11597
+ import { input as input3, select as select4 } from "@inquirer/prompts";
11050
11598
  class Prompter {
11051
11599
  static async#getGuidelineRoot() {
11052
11600
  const dirname2 = getDirname(import.meta.url);
@@ -11062,7 +11610,7 @@ class Prompter {
11062
11610
  static async selectGuideline() {
11063
11611
  const guidelineRoot = await Prompter.#getGuidelineRoot();
11064
11612
  const guideNames = await Prompter.listGuidelines();
11065
- return await select3({
11613
+ return await select4({
11066
11614
  message: "Select a guideline",
11067
11615
  choices: guideNames.map((name) => ({ name, value: name }))
11068
11616
  });
@@ -11118,7 +11666,7 @@ ${document}
11118
11666
  }
11119
11667
  }
11120
11668
  // pkgs/@akanjs/devkit/selectModel.ts
11121
- import { select as select4 } from "@inquirer/prompts";
11669
+ import { select as select5 } from "@inquirer/prompts";
11122
11670
  // pkgs/@akanjs/devkit/streamAi.ts
11123
11671
  import { PromptTemplate } from "@langchain/core/prompts";
11124
11672
  import { RunnableSequence } from "@langchain/core/runnables";