@akanjs/devkit 3.0.0-beta.2 → 3.0.0-beta.20
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/README.md +1 -0
- package/abstractDoc.test.ts +6 -15
- package/abstractDoc.ts +6 -8
- package/agentsIndex.test.ts +4 -7
- package/agentsIndex.ts +6 -22
- package/akanApp/BackendImportGraph.test.ts +8 -22
- package/akanApp/BackendImportGraph.ts +1 -20
- package/akanApp/akanApp.host.test.ts +1 -3
- package/akanApp/akanApp.host.ts +82 -254
- package/akanApp/devHostPolicy.test.ts +10 -34
- package/akanApp/devHostPolicy.ts +12 -98
- package/akanConfig/akanConfig.test.ts +85 -69
- package/akanConfig/akanConfig.ts +69 -153
- package/akanConfig/types.ts +1 -0
- package/akanContext.ts +106 -159
- package/akanMcpContract.ts +28 -73
- package/appSelectionMemory.ts +1 -4
- package/applicationBuildReporter.ts +37 -5
- package/applicationBuildRunner.test.ts +5 -3
- package/applicationBuildRunner.ts +54 -61
- package/applicationReleasePackager.ts +24 -44
- package/applicationTestPreload.ts +3 -12
- package/artifact/implicitRootLayout.test.ts +9 -30
- package/artifact/implicitRootLayout.ts +34 -106
- package/artifact/routeSeedIndex.test.ts +7 -6
- package/artifact/routeSeedIndex.ts +11 -44
- package/biome.base.json +6 -1
- package/biomeBase.ts +2 -5
- package/capacitorApp.test.ts +32 -16
- package/capacitorApp.ts +110 -238
- package/cloud/cloudApi.test.ts +202 -0
- package/cloud/cloudApi.ts +77 -39
- package/cloud/configLock.test.ts +64 -0
- package/cloud/configLock.ts +96 -0
- package/cloud/constants.ts +26 -5
- package/cloud/globalConfig.ts +70 -32
- package/codeAgent/agent/AkanCodeServices.ts +97 -0
- package/codeAgent/agent/AkanContextFiles.ts +94 -0
- package/codeAgent/agent/CodeAgent.ts +551 -0
- package/codeAgent/agent/CodeAgentAsks.ts +66 -0
- package/codeAgent/agent/CodeAgentEventMapper.ts +225 -0
- package/codeAgent/agent/CodeAgentGate.ts +93 -0
- package/codeAgent/agent/CodeAgentProxy.ts +82 -0
- package/codeAgent/agent/CodeAgentRpcHost.ts +103 -0
- package/codeAgent/agent/CodeAgentRpcListener.ts +109 -0
- package/codeAgent/agent/CodeAgentStreamPrinter.ts +86 -0
- package/codeAgent/agent/CodeAgentSuspended.ts +85 -0
- package/codeAgent/agent/CodeAgentUi.ts +47 -0
- package/codeAgent/agent/CodeMailbox.ts +148 -0
- package/codeAgent/agent/CodeSessionFork.ts +56 -0
- package/codeAgent/agent/CodeSessionIndex.ts +78 -0
- package/codeAgent/agent/SubagentPool.ts +214 -0
- package/codeAgent/agent/akanCodeModel.ts +47 -0
- package/codeAgent/agent/akanCodePaths.ts +31 -0
- package/codeAgent/agent/akanContextFiles.test.ts +114 -0
- package/codeAgent/agent/akanSystemPrompt.ts +46 -0
- package/codeAgent/agent/codeAgent.test.ts +178 -0
- package/codeAgent/agent/codeAgentContract.test.ts +523 -0
- package/codeAgent/agent/codeAgentProxy.test.ts +74 -0
- package/codeAgent/agent/codeAgentRpcListener.test.ts +113 -0
- package/codeAgent/agent/codeAgentSuspended.test.ts +47 -0
- package/codeAgent/agent/codeMailbox.test.ts +76 -0
- package/codeAgent/agent/codeSessionFork.test.ts +58 -0
- package/codeAgent/agent/codeSessionIndex.test.ts +77 -0
- package/codeAgent/agent/consoleToStderr.ts +8 -0
- package/codeAgent/agent/jsonLines.ts +29 -0
- package/codeAgent/agent/stringArg.ts +5 -0
- package/codeAgent/feedback/DevLogFeedback.ts +82 -0
- package/codeAgent/feedback/PreviewChrome.ts +82 -0
- package/codeAgent/feedback/PreviewFeedback.ts +46 -0
- package/codeAgent/feedback/PreviewView.ts +95 -0
- package/codeAgent/feedback/TurnFeedback.ts +98 -0
- package/codeAgent/feedback/VerifyFeedback.ts +40 -0
- package/codeAgent/feedback/previewChrome.test.ts +73 -0
- package/codeAgent/index.ts +34 -0
- package/codeAgent/skills/akan-code/SKILL.md +130 -0
- package/codeAgent/skills/akan-debug/SKILL.md +42 -0
- package/codeAgent/skills/akan-deps/SKILL.md +50 -0
- package/codeAgent/skills/akan-err/SKILL.md +39 -0
- package/codeAgent/skills/akan-module/SKILL.md +45 -0
- package/codeAgent/skills/akan-scaffold/SKILL.md +76 -0
- package/codeAgent/skills/akan-scalar/SKILL.md +52 -0
- package/codeAgent/skills/akan-service/SKILL.md +34 -0
- package/codeAgent/skills/akan-store/SKILL.md +67 -0
- package/codeAgent/skills/akan-ui/SKILL.md +63 -0
- package/codeAgent/skills/akan-validate/SKILL.md +44 -0
- package/codeAgent/tools/AkanCli.ts +50 -0
- package/codeAgent/tools/AkanCodePlugins.ts +136 -0
- package/codeAgent/tools/AkanEditScope.ts +48 -0
- package/codeAgent/tools/AkanEnvKeys.ts +50 -0
- package/codeAgent/tools/AkanToolPack.ts +92 -0
- package/codeAgent/tools/AkanVerifier.ts +48 -0
- package/codeAgent/tools/AskToolPack.ts +99 -0
- package/codeAgent/tools/MailToolPack.ts +76 -0
- package/codeAgent/tools/McpClient.ts +187 -0
- package/codeAgent/tools/McpOAuth.ts +233 -0
- package/codeAgent/tools/McpOAuthLoopback.ts +64 -0
- package/codeAgent/tools/McpServerConfig.ts +129 -0
- package/codeAgent/tools/McpSignIn.ts +158 -0
- package/codeAgent/tools/McpTokenStore.ts +81 -0
- package/codeAgent/tools/McpToolPack.ts +199 -0
- package/codeAgent/tools/SessionToolPack.ts +91 -0
- package/codeAgent/tools/WebToolPack.ts +147 -0
- package/codeAgent/tools/akanCodePlugins.test.ts +677 -0
- package/codeAgent/tools/askToolPack.test.ts +116 -0
- package/codeAgent/tools/mcpOAuth.test.ts +612 -0
- package/codeAgent/tools/mcpServerConfig.test.ts +212 -0
- package/codegenLock.test.ts +4 -13
- package/codegenLock.ts +3 -17
- package/commandDecorators/argMeta.ts +1 -5
- package/commandDecorators/camelToKebabCase.ts +1 -0
- package/commandDecorators/command.ts +46 -102
- package/commandDecorators/commandBuilder.ts +30 -61
- package/commandDecorators/commandDecorators.test.ts +2 -10
- package/commandDecorators/dependencyBuilder.ts +10 -1
- package/commandDecorators/helpFormatter.ts +68 -145
- package/commandDecorators/index.ts +0 -2
- package/commandDecorators/targetMeta.ts +9 -7
- package/createTunnel.ts +7 -14
- package/dependencyScanner.test.ts +4 -16
- package/dependencyScanner.ts +31 -80
- package/devkitUtils.test.ts +48 -20
- package/executors.linkFallback.test.ts +103 -0
- package/executors.test.ts +132 -106
- package/executors.ts +194 -484
- package/fileEditor.ts +9 -37
- package/{getDirname.test.ts → fileSys.test.ts} +5 -3
- package/fileSys.ts +6 -8
- package/formSetterScanner.ts +3 -16
- package/frontendBuild/allRoutesBuilder.ts +7 -21
- package/frontendBuild/autoImportSync.test.ts +27 -38
- package/frontendBuild/autoImportSync.ts +31 -65
- package/frontendBuild/buildRouteClient.test.ts +62 -37
- package/frontendBuild/bundleDefine.ts +12 -0
- package/frontendBuild/clientBuildTypes.ts +5 -36
- package/frontendBuild/clientEntriesBundler.ts +9 -40
- package/frontendBuild/clientEntryDiscovery.ts +6 -41
- package/frontendBuild/csrArtifactBuilder.ts +6 -25
- package/frontendBuild/cssCandidateCache.test.ts +0 -6
- package/frontendBuild/cssCandidateCache.ts +6 -41
- package/frontendBuild/cssCompiler.ts +29 -78
- package/frontendBuild/cssImportResolver.ts +6 -19
- package/frontendBuild/devChangePlanner.ts +7 -10
- package/frontendBuild/devGeneratedIndexSync.test.ts +12 -27
- package/frontendBuild/devGeneratedIndexSync.ts +1 -4
- package/frontendBuild/fontOptimizer.test.ts +6 -14
- package/frontendBuild/fontOptimizer.ts +15 -72
- package/frontendBuild/fontPruner.test.ts +5 -10
- package/frontendBuild/fontPruner.ts +5 -20
- package/frontendBuild/frontendBuild.test.ts +46 -69
- package/frontendBuild/hmrChangeClassifier.ts +2 -2
- package/frontendBuild/hmrWatcher.test.ts +8 -32
- package/frontendBuild/hmrWatcher.ts +14 -71
- package/frontendBuild/pagesBundleBuilder.ts +4 -29
- package/frontendBuild/precompressArtifacts.ts +5 -18
- package/frontendBuild/routeClientBuilder.ts +23 -46
- package/frontendBuild/routesManifestArtifactSerializer.ts +5 -36
- package/frontendBuild/sourceMtimeIndex.test.ts +41 -97
- package/frontendBuild/sourceMtimeIndex.ts +19 -130
- package/frontendBuild/ssrBaseArtifactBuilder.ts +2 -8
- package/frontendBuild/styleContract.ts +1 -8
- package/frontendBuild/themeValidator.ts +4 -26
- package/frontendBuild/watchRootResolver.test.ts +23 -42
- package/frontendBuild/watchRootResolver.ts +5 -27
- package/getRelatedCnsts.ts +12 -92
- package/incrementalBuilder/buildBatch.proc.ts +22 -57
- package/incrementalBuilder/buildBatchProtocol.ts +8 -36
- package/incrementalBuilder/buildBatchRunner.test.ts +34 -0
- package/incrementalBuilder/buildBatchRunner.ts +10 -31
- package/incrementalBuilder/builderChannel.test.ts +24 -49
- package/incrementalBuilder/builderChannel.ts +6 -37
- package/incrementalBuilder/builderRequestRouter.test.ts +0 -8
- package/incrementalBuilder/builderRequestRouter.ts +5 -24
- package/incrementalBuilder/devWatchBatch.test.ts +21 -24
- package/incrementalBuilder/incrementalBuilder.host.test.ts +18 -55
- package/incrementalBuilder/incrementalBuilder.host.ts +18 -76
- package/incrementalBuilder/incrementalBuilder.proc.test.ts +81 -0
- package/incrementalBuilder/incrementalBuilder.proc.ts +49 -161
- package/index.ts +5 -17
- package/integration/devResourceProbe.ts +50 -56
- package/integration/devStability.integration.test.ts +47 -194
- package/integration/devStabilityHarness.test.ts +13 -28
- package/integration/devStabilityHarness.ts +65 -330
- package/integration/ssrMemoryProbe.ts +41 -167
- package/libSource.test.ts +4 -15
- package/libSource.ts +19 -31
- package/lint/gritRules.test.ts +8 -24
- package/lint/no-deep-internal-import.grit +2 -2
- package/lint/no-redeclare-predefined-endpoint.grit +2 -1
- package/linter.test.ts +1 -2
- package/linter.ts +26 -116
- package/mobile/mobileTarget.ts +14 -13
- package/package.json +10 -5
- package/packageExports.test.ts +3 -18
- package/packageExportsMap.ts +3 -19
- package/piDependencyPins.test.ts +46 -0
- package/platformTest/BunTestReport.test.ts +60 -0
- package/platformTest/BunTestReport.ts +65 -0
- package/platformTest/IdleWatchedProcess.test.ts +56 -0
- package/platformTest/IdleWatchedProcess.ts +73 -0
- package/platformTest/LinuxDockerTarget.ts +105 -0
- package/platformTest/PlatformTestRun.test.ts +64 -0
- package/platformTest/PlatformTestRun.ts +141 -0
- package/platformTest/PlatformTestTarget.ts +142 -0
- package/platformTest/TestSnapshot.test.ts +72 -0
- package/platformTest/TestSnapshot.ts +103 -0
- package/platformTest/WindowsSshTarget.ts +151 -0
- package/prompter.ts +3 -13
- package/qualityScanner.test.ts +9 -26
- package/qualityScanner.ts +197 -285
- package/recipeScanner.test.ts +7 -12
- package/recipeScanner.ts +9 -38
- package/repoIdentity.ts +2 -9
- package/routeSourceValidator.test.ts +0 -1
- package/routeSourceValidator.ts +7 -42
- package/scanInfo.ts +87 -216
- package/selectModel.ts +1 -2
- package/semver.ts +3 -5
- package/slicePlanner.test.ts +6 -24
- package/slicePlanner.ts +11 -32
- package/spinner.test.ts +3 -8
- package/spinner.ts +4 -16
- package/sshTunnel.ts +1 -6
- package/ssrScanner.test.ts +1 -6
- package/ssrScanner.ts +103 -113
- package/stripAnsi.ts +3 -0
- package/subspace.test.ts +30 -25
- package/subspace.ts +81 -115
- package/subspaceConfig.ts +11 -9
- package/testHelpers.ts +194 -0
- package/transforms/asyncDefaultExportDetector.ts +3 -11
- package/transforms/barrelAnalyzer.ts +43 -135
- package/transforms/barrelImportsPlugin.ts +38 -175
- package/transforms/externalizeFrameworkPlugin.test.ts +39 -118
- package/transforms/externalizeFrameworkPlugin.ts +13 -87
- package/transforms/moduleSyntax.ts +32 -0
- package/transforms/rscUseClientTransform.ts +35 -20
- package/transforms/transforms.test.ts +153 -44
- package/transforms/tsconfigPackageResolver.test.ts +6 -24
- package/transforms/useClientBundlePlugin.ts +2 -22
- package/typeChecker.ts +7 -63
- package/types.ts +1 -0
- package/uploadRelease.ts +10 -25
- package/workflow/artifacts.ts +18 -0
- package/workflow/executor.test.ts +1 -12
- package/workflow/executor.ts +101 -110
- package/workflow/index.ts +0 -1
- package/workflow/moduleIndex.test.ts +4 -15
- package/workflow/moduleIndex.ts +9 -49
- package/workflow/plan.ts +18 -25
- package/workflow/render.ts +80 -96
- package/workflow/rolloutGate.test.ts +1 -1
- package/workflow/source.ts +52 -69
- package/workflow/sourceAst.ts +50 -0
- package/workspaceLayout.ts +4 -19
- package/abstractCompactor.ts +0 -112
- package/aiEditor.test.ts +0 -60
- package/aiEditor.ts +0 -387
- package/commandDecorators/commandMeta.ts +0 -7
- package/commandDecorators/types.ts +0 -10
- package/getDirname.ts +0 -4
- package/guideline.ts +0 -19
- package/local/optimize-resource/ipcprobe/child.ts +0 -1
- package/local/optimize-resource/ipcprobe/parent.ts +0 -11
- package/workflow/primitive.ts +0 -59
package/capacitorApp.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { MobileProject } from "@trapezedev/project";
|
|
|
7
7
|
import type { AndroidProject } from "@trapezedev/project/dist/android/project";
|
|
8
8
|
import type { IosProject } from "@trapezedev/project/dist/ios/project";
|
|
9
9
|
import type { AkanNativeContext, AkanPlugin } from "akanjs";
|
|
10
|
-
import { capitalize } from "akanjs/common";
|
|
10
|
+
import { capitalize, isRecord } from "akanjs/common";
|
|
11
11
|
import type { AkanMobileTargetConfig, MobilePermission } from "./akanConfig";
|
|
12
12
|
import { type AppExecutor, CommandExecutionError } from "./executors";
|
|
13
13
|
import { FileEditor } from "./fileEditor";
|
|
@@ -63,8 +63,7 @@ export type IosRunFailureKind =
|
|
|
63
63
|
| "simulator-runtime"
|
|
64
64
|
| "unknown";
|
|
65
65
|
|
|
66
|
-
//
|
|
67
|
-
// runtimes, so an app built against them dyld-crashes at launch on an iOS <18 simulator/device.
|
|
66
|
+
// iOS 18+ SDKs split SwiftUI into a SwiftUICore dylib older runtimes lack, so such a build dyld-crashes below iOS 18.
|
|
68
67
|
export const SWIFTUICORE_MIN_IOS_MAJOR = 18;
|
|
69
68
|
|
|
70
69
|
export interface IosRunFailureClassification {
|
|
@@ -73,30 +72,11 @@ export interface IosRunFailureClassification {
|
|
|
73
72
|
detail: string;
|
|
74
73
|
}
|
|
75
74
|
|
|
76
|
-
const iosNativeBlockedEnvKeys = new Set(
|
|
77
|
-
"AR"
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
"CONDA_BUILD_SYSROOT",
|
|
82
|
-
"CONDA_PREFIX",
|
|
83
|
-
"CPP",
|
|
84
|
-
"CPPFLAGS",
|
|
85
|
-
"CPATH",
|
|
86
|
-
"CXX",
|
|
87
|
-
"CXXFLAGS",
|
|
88
|
-
"LD",
|
|
89
|
-
"LDFLAGS",
|
|
90
|
-
"LIBRARY_PATH",
|
|
91
|
-
"MACOSX_DEPLOYMENT_TARGET",
|
|
92
|
-
"NM",
|
|
93
|
-
"OBJC",
|
|
94
|
-
"OBJCXX",
|
|
95
|
-
"PREFIX",
|
|
96
|
-
"RANLIB",
|
|
97
|
-
"SDKROOT",
|
|
98
|
-
"STRIP",
|
|
99
|
-
]);
|
|
75
|
+
const iosNativeBlockedEnvKeys = new Set(
|
|
76
|
+
"AR AS CC CFLAGS CONDA_BUILD_SYSROOT CONDA_PREFIX CPP CPPFLAGS CPATH CXX CXXFLAGS LD LDFLAGS LIBRARY_PATH"
|
|
77
|
+
.split(" ")
|
|
78
|
+
.concat("MACOSX_DEPLOYMENT_TARGET NM OBJC OBJCXX PREFIX RANLIB SDKROOT STRIP".split(" ")),
|
|
79
|
+
);
|
|
100
80
|
|
|
101
81
|
export const rootCapacitorConfigFilenames = [
|
|
102
82
|
"capacitor.config.ts",
|
|
@@ -123,11 +103,6 @@ interface MaterializeCapacitorConfigOptions {
|
|
|
123
103
|
}
|
|
124
104
|
type MobilePlatform = "ios" | "android";
|
|
125
105
|
|
|
126
|
-
/**
|
|
127
|
-
* `AppExecutor.spawn`'s own options plus the two the Capacitor config is written from before the spawn:
|
|
128
|
-
* the platform the command targets, and — for iOS — whether it targets a device or a simulator, which
|
|
129
|
-
* decide the `capacitor.config.json` that command reads.
|
|
130
|
-
*/
|
|
131
106
|
type SpawnMobileOptions = Parameters<AppExecutor["spawn"]>[2] & {
|
|
132
107
|
platform?: MobilePlatform;
|
|
133
108
|
iosRunTargetKind?: IosRunTargetKind;
|
|
@@ -139,24 +114,8 @@ export interface LocalDevHostResolution {
|
|
|
139
114
|
candidates: { name: string; address: string }[];
|
|
140
115
|
}
|
|
141
116
|
|
|
142
|
-
//
|
|
143
|
-
|
|
144
|
-
const virtualInterfacePrefixes = [
|
|
145
|
-
"bridge",
|
|
146
|
-
"utun",
|
|
147
|
-
"llw",
|
|
148
|
-
"awdl",
|
|
149
|
-
"ap",
|
|
150
|
-
"vmnet",
|
|
151
|
-
"vnic",
|
|
152
|
-
"tap",
|
|
153
|
-
"tun",
|
|
154
|
-
"docker",
|
|
155
|
-
"veth",
|
|
156
|
-
"vboxnet",
|
|
157
|
-
"gif",
|
|
158
|
-
"stf",
|
|
159
|
-
];
|
|
117
|
+
// Almost never a LAN NIC a device can reach: bridges, tunnels, AirDrop/awdl, VM and container adapters.
|
|
118
|
+
const virtualInterfacePrefixes = "bridge utun llw awdl ap vmnet vnic tap tun docker veth vboxnet gif stf".split(" ");
|
|
160
119
|
const physicalInterfacePrefixes = ["en", "eth", "wlan", "wlp", "enp", "eno", "wlo"];
|
|
161
120
|
|
|
162
121
|
const isPrivateLanIpv4 = (address: string): boolean => {
|
|
@@ -165,8 +124,7 @@ const isPrivateLanIpv4 = (address: string): boolean => {
|
|
|
165
124
|
return Number.isFinite(secondOctet) && secondOctet >= 16 && secondOctet <= 31;
|
|
166
125
|
};
|
|
167
126
|
|
|
168
|
-
//
|
|
169
|
-
// virtual/bridge interfaces are demoted below real NICs; private-LAN ranges get a small boost.
|
|
127
|
+
// Link-local (169.254) is never routable.
|
|
170
128
|
const scoreDevHostCandidate = (name: string, address: string): number => {
|
|
171
129
|
const lowerName = name.toLowerCase();
|
|
172
130
|
let score = 0;
|
|
@@ -177,9 +135,7 @@ const scoreDevHostCandidate = (name: string, address: string): number => {
|
|
|
177
135
|
return score;
|
|
178
136
|
};
|
|
179
137
|
|
|
180
|
-
//
|
|
181
|
-
// otherwise rank non-internal IPv4 interfaces so a down/virtual interface (e.g. an inactive
|
|
182
|
-
// Thunderbolt bridge enumerated first) never shadows a real LAN NIC. Deterministic tie-break.
|
|
138
|
+
// Ranked, not first-found: an inactive Thunderbolt bridge is often enumerated before the real LAN NIC.
|
|
183
139
|
export const selectLocalDevHost = (
|
|
184
140
|
interfaces: NodeJS.Dict<os.NetworkInterfaceInfo[]>,
|
|
185
141
|
{ override }: { override?: string } = {},
|
|
@@ -204,9 +160,6 @@ export const selectLocalDevHost = (
|
|
|
204
160
|
: { host: "127.0.0.1", source: "loopback", candidates };
|
|
205
161
|
};
|
|
206
162
|
|
|
207
|
-
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
208
|
-
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
209
|
-
|
|
210
163
|
const asString = (value: unknown) => (typeof value === "string" ? value : undefined);
|
|
211
164
|
|
|
212
165
|
const firstString = (...values: unknown[]) => values.find((value): value is string => typeof value === "string");
|
|
@@ -227,8 +180,7 @@ const dedupeIosRunTargets = (targets: IosRunTarget[]) => {
|
|
|
227
180
|
return [...byKey.values()];
|
|
228
181
|
};
|
|
229
182
|
|
|
230
|
-
//
|
|
231
|
-
// or a per-device `runtimeIdentifier` / text header ("iOS 18.2") — into a "iOS 18.2" display string.
|
|
183
|
+
// simctl spells a runtime as a JSON key ("com.apple.CoreSimulator.SimRuntime.iOS-18-2") or as "iOS 18.2".
|
|
232
184
|
const formatSimctlRuntime = (key?: string): string | undefined => {
|
|
233
185
|
if (!key) return undefined;
|
|
234
186
|
if (/^(iOS|watchOS|tvOS|visionOS)\s+[\d.]+$/i.test(key)) return key;
|
|
@@ -237,18 +189,11 @@ const formatSimctlRuntime = (key?: string): string | undefined => {
|
|
|
237
189
|
return `${match[1]} ${match[2]}${match[3] ? `.${match[3]}` : ""}`;
|
|
238
190
|
};
|
|
239
191
|
|
|
240
|
-
// Extract the major OS version from a runtime display string ("iOS 18.2" → 18). Undefined for
|
|
241
|
-
// physical devices (no runtime) or unparseable values.
|
|
242
192
|
export const parseIosRuntimeMajor = (runtime?: string): number | undefined => {
|
|
243
|
-
const major = runtime?.match(
|
|
244
|
-
|
|
245
|
-
const parsed = Number.parseInt(major, 10);
|
|
246
|
-
return Number.isNaN(parsed) ? undefined : parsed;
|
|
193
|
+
const major = runtime?.match(/\d+/)?.[0];
|
|
194
|
+
return major === undefined ? undefined : Number.parseInt(major, 10);
|
|
247
195
|
};
|
|
248
196
|
|
|
249
|
-
// Rank for default-target ordering: ready targets (booted simulator / connected device) first, then
|
|
250
|
-
// physical devices, then newer simulator runtimes. Fixes the old ascending-runtime order that made a
|
|
251
|
-
// stale iOS 17.x simulator the default pick.
|
|
252
197
|
const iosRunTargetRank = (target: IosRunTarget): number => {
|
|
253
198
|
const state = target.state?.toLowerCase() ?? "";
|
|
254
199
|
const ready = state.includes("booted") || state.includes("connected") || state.includes("available") ? 1000 : 0;
|
|
@@ -489,7 +434,7 @@ export function classifyIosRunFailure(log: string): IosRunFailureClassification
|
|
|
489
434
|
kind: "unknown",
|
|
490
435
|
title: "iOS native run failed.",
|
|
491
436
|
detail:
|
|
492
|
-
"Review the xcodebuild/devicectl output above. You can retry with --
|
|
437
|
+
"Review the xcodebuild/devicectl output above. You can retry with --no-allow-provisioning-updates to use the conservative path.",
|
|
493
438
|
};
|
|
494
439
|
}
|
|
495
440
|
|
|
@@ -515,9 +460,8 @@ export function sanitizeIosNativeRunEnv(env: MobileCommandEnv): MobileCommandEnv
|
|
|
515
460
|
return Object.fromEntries(Object.entries(env).filter(([key]) => !iosNativeBlockedEnvKeys.has(key)));
|
|
516
461
|
}
|
|
517
462
|
|
|
518
|
-
//
|
|
519
|
-
//
|
|
520
|
-
// "cannot be registered to your development team". A unique reverse-DNS id fixes it.
|
|
463
|
+
// Placeholder bundle IDs are already claimed on Apple's portal, so device signing fails with "cannot be registered
|
|
464
|
+
// to your development team".
|
|
521
465
|
export const PLACEHOLDER_APP_IDS = [
|
|
522
466
|
"com.myapp.app",
|
|
523
467
|
"com.myorg.myapp",
|
|
@@ -597,6 +541,29 @@ export function raiseGradleMinSdkVersion(content: string, floor: number = ANDROI
|
|
|
597
541
|
return content.replace(/(minSdkVersion\s*=\s*)\d+/, `$1${floor}`);
|
|
598
542
|
}
|
|
599
543
|
|
|
544
|
+
//* Apple's Info.plist keys do not follow the description names (photo → NSPhotoLibrary…), so each one is spelled out.
|
|
545
|
+
const iosUsageDescriptionKeys = {
|
|
546
|
+
cameraUsageDescription: ["NSCameraUsageDescription"],
|
|
547
|
+
photoAddUsageDescription: ["NSPhotoLibraryAddUsageDescription"],
|
|
548
|
+
photoUsageDescription: ["NSPhotoLibraryUsageDescription"],
|
|
549
|
+
contactsUsageDescription: ["NSContactsUsageDescription"],
|
|
550
|
+
//? iOS 11+ reads the AlwaysAndWhenInUse key; the Always key only serves iOS 10 and below.
|
|
551
|
+
locationAlwaysUsageDescription: ["NSLocationAlwaysAndWhenInUseUsageDescription", "NSLocationAlwaysUsageDescription"],
|
|
552
|
+
locationWhenInUseUsageDescription: ["NSLocationWhenInUseUsageDescription"],
|
|
553
|
+
microphoneUsageDescription: ["NSMicrophoneUsageDescription"],
|
|
554
|
+
speechRecognitionUsageDescription: ["NSSpeechRecognitionUsageDescription"],
|
|
555
|
+
} as const;
|
|
556
|
+
|
|
557
|
+
export const toIosInfoPlistUsageDescriptions = (descriptions: { [key: string]: string }) =>
|
|
558
|
+
Object.fromEntries(
|
|
559
|
+
Object.entries(descriptions).flatMap(([key, value]) => {
|
|
560
|
+
const plistKeys: readonly string[] = Object.hasOwn(iosUsageDescriptionKeys, key)
|
|
561
|
+
? iosUsageDescriptionKeys[key as keyof typeof iosUsageDescriptionKeys]
|
|
562
|
+
: [`NS${capitalize(key)}`];
|
|
563
|
+
return plistKeys.map((plistKey) => [plistKey, value]);
|
|
564
|
+
}),
|
|
565
|
+
);
|
|
566
|
+
|
|
600
567
|
const mergeAllowNavigation = (configured: unknown, localIp: string | undefined) => {
|
|
601
568
|
const values = Array.isArray(configured)
|
|
602
569
|
? configured.filter((value): value is string => typeof value === "string")
|
|
@@ -620,9 +587,7 @@ export function assertJsonSerializable(value: unknown, label = "capacitor.config
|
|
|
620
587
|
if (seen.has(objectValue)) throw new Error(`${label} must be JSON serializable. Found circular reference.`);
|
|
621
588
|
seen.add(objectValue);
|
|
622
589
|
if (Array.isArray(value)) {
|
|
623
|
-
value.
|
|
624
|
-
assertJsonSerializable(item, `${label}[${index}]`, seen);
|
|
625
|
-
});
|
|
590
|
+
for (const [index, item] of value.entries()) assertJsonSerializable(item, `${label}[${index}]`, seen);
|
|
626
591
|
return;
|
|
627
592
|
}
|
|
628
593
|
for (const [key, item] of Object.entries(value as Record<string, unknown>)) {
|
|
@@ -727,8 +692,6 @@ export class CapacitorApp {
|
|
|
727
692
|
readonly iosProjectPath = "ios/App";
|
|
728
693
|
readonly androidRootPath = "android";
|
|
729
694
|
readonly androidAssetsPath = "android/app/src/main/assets";
|
|
730
|
-
//* Accumulates iOS entitlements contributed by deep links and plugins during a prepare,
|
|
731
|
-
//* then flushed to App/App.entitlements once (see #flushIosEntitlements).
|
|
732
695
|
#iosEntitlements: Record<string, string | string[]> = {};
|
|
733
696
|
constructor(
|
|
734
697
|
private readonly app: AppExecutor,
|
|
@@ -791,7 +754,6 @@ export class CapacitorApp {
|
|
|
791
754
|
await this.#prepareIos({ operation: "release", env, regenerate });
|
|
792
755
|
await this.#spawnMobile("npx", ["cap", "build", "ios"], { operation: "release", env }, { stdio: "inherit" });
|
|
793
756
|
this.app.verbose(`build completed iOS.`);
|
|
794
|
-
return;
|
|
795
757
|
}
|
|
796
758
|
async syncIos() {
|
|
797
759
|
await this.#spawnMobile("npx", ["cap", "sync", "ios"], { operation: "local", env: "local" });
|
|
@@ -816,7 +778,10 @@ export class CapacitorApp {
|
|
|
816
778
|
}
|
|
817
779
|
|
|
818
780
|
async #selectIosRunTarget(deviceId?: string) {
|
|
819
|
-
const targets = sortIosRunTargets(
|
|
781
|
+
const targets = sortIosRunTargets([
|
|
782
|
+
...(await this.#loadPhysicalIosDevices()),
|
|
783
|
+
...(await this.#loadIosSimulators()),
|
|
784
|
+
]);
|
|
820
785
|
if (deviceId) {
|
|
821
786
|
const needle = deviceId.toLowerCase();
|
|
822
787
|
const found =
|
|
@@ -858,12 +823,6 @@ export class CapacitorApp {
|
|
|
858
823
|
);
|
|
859
824
|
}
|
|
860
825
|
|
|
861
|
-
async #loadIosRunTargets() {
|
|
862
|
-
const devices = await this.#loadPhysicalIosDevices();
|
|
863
|
-
const simulators = await this.#loadIosSimulators();
|
|
864
|
-
return [...devices, ...simulators];
|
|
865
|
-
}
|
|
866
|
-
|
|
867
826
|
async #loadPhysicalIosDevices() {
|
|
868
827
|
try {
|
|
869
828
|
return parseDevicectlDevices(await this.#spawn("xcrun", ["devicectl", "list", "devices", "--json-output", "-"]));
|
|
@@ -906,12 +865,11 @@ export class CapacitorApp {
|
|
|
906
865
|
{ operation, platform: "ios", iosRunTargetKind: runTarget.kind },
|
|
907
866
|
mobileEnv,
|
|
908
867
|
);
|
|
909
|
-
await
|
|
910
|
-
const scheme = this.#iosScheme();
|
|
868
|
+
await writeRootCapacitorConfig(this.app.cwdPath, configContent);
|
|
911
869
|
const command = buildIosNativeRunCommand({
|
|
912
870
|
appRoot: this.app.cwdPath,
|
|
913
871
|
device: runTarget,
|
|
914
|
-
scheme,
|
|
872
|
+
scheme: isRecord(this.target.ios) && typeof this.target.ios.scheme === "string" ? this.target.ios.scheme : "App",
|
|
915
873
|
configuration: operation === "release" ? "Release" : "Debug",
|
|
916
874
|
});
|
|
917
875
|
const xcodebuildArgs = noAllowProvisioningUpdates
|
|
@@ -923,16 +881,10 @@ export class CapacitorApp {
|
|
|
923
881
|
env: mobileEnv,
|
|
924
882
|
});
|
|
925
883
|
const devicectlId = runTarget.devicectlId ?? runTarget.id;
|
|
926
|
-
|
|
927
|
-
env: mobileEnv
|
|
928
|
-
|
|
929
|
-
await this
|
|
930
|
-
"xcrun",
|
|
931
|
-
["devicectl", "device", "process", "launch", "--device", devicectlId, this.target.appId],
|
|
932
|
-
{
|
|
933
|
-
env: mobileEnv,
|
|
934
|
-
},
|
|
935
|
-
);
|
|
884
|
+
const devicectl = (verb: string[], subject: string) =>
|
|
885
|
+
this.#spawn("xcrun", ["devicectl", "device", ...verb, "--device", devicectlId, subject], { env: mobileEnv });
|
|
886
|
+
await devicectl(["install", "app"], command.appPath);
|
|
887
|
+
await devicectl(["process", "launch"], this.target.appId);
|
|
936
888
|
} catch (error) {
|
|
937
889
|
throw new Error(
|
|
938
890
|
formatIosRunFailureMessage({
|
|
@@ -943,14 +895,10 @@ export class CapacitorApp {
|
|
|
943
895
|
}),
|
|
944
896
|
);
|
|
945
897
|
} finally {
|
|
946
|
-
await
|
|
898
|
+
await clearRootCapacitorConfigs(this.app.cwdPath);
|
|
947
899
|
}
|
|
948
900
|
}
|
|
949
901
|
|
|
950
|
-
#iosScheme() {
|
|
951
|
-
return isRecord(this.target.ios) && typeof this.target.ios.scheme === "string" ? this.target.ios.scheme : "App";
|
|
952
|
-
}
|
|
953
|
-
|
|
954
902
|
async #getIosDevelopmentTeam() {
|
|
955
903
|
const pbxprojPath = path.join(this.app.cwdPath, this.iosProjectPath, "App.xcodeproj/project.pbxproj");
|
|
956
904
|
if (!(await Bun.file(pbxprojPath).exists())) return undefined;
|
|
@@ -973,15 +921,13 @@ export class CapacitorApp {
|
|
|
973
921
|
await this.#disableNativeKeyboardResizeInAndroid();
|
|
974
922
|
await this.project.commit();
|
|
975
923
|
await this.#generateAssets({ operation, env });
|
|
976
|
-
await this
|
|
924
|
+
await mkdir(path.join(this.app.cwdPath, this.androidAssetsPath), { recursive: true });
|
|
977
925
|
await this.#ensureAndroidDebugKeystore();
|
|
978
926
|
await this.#spawnMobile("npx", ["cap", "sync", "android"], { operation, env });
|
|
979
927
|
await this.#setDeepLinksInAndroid(this.target.deepLinks?.schemes ?? [], this.target.deepLinks?.domains ?? []);
|
|
980
928
|
}
|
|
981
929
|
|
|
982
930
|
async #updateAndroidBuildTypes() {
|
|
983
|
-
//keystore 기본 설정 및 debug, release 설정
|
|
984
|
-
|
|
985
931
|
const appGradle = await FileEditor.create(path.join(this.app.cwdPath, this.androidRootPath, "app/build.gradle"));
|
|
986
932
|
const buildTypesBlock = `
|
|
987
933
|
debug {
|
|
@@ -1025,10 +971,7 @@ export class CapacitorApp {
|
|
|
1025
971
|
await this.#prepareAndroid({ operation: "release", env, regenerate });
|
|
1026
972
|
await this.#assertAndroidReleaseSigningConfig();
|
|
1027
973
|
await this.#updateAndroidBuildTypes();
|
|
1028
|
-
|
|
1029
|
-
const isWindows = process.platform === "win32";
|
|
1030
|
-
const gradleCommand = isWindows ? "gradlew.bat" : "./gradlew";
|
|
1031
|
-
|
|
974
|
+
const gradleCommand = process.platform === "win32" ? "gradlew.bat" : "./gradlew";
|
|
1032
975
|
await this.app.spawn(gradleCommand, [assembleType === "apk" ? "assembleRelease" : "bundleRelease"], {
|
|
1033
976
|
stdio: "inherit",
|
|
1034
977
|
cwd: path.join(this.app.cwdPath, this.androidRootPath),
|
|
@@ -1056,30 +999,18 @@ export class CapacitorApp {
|
|
|
1056
999
|
});
|
|
1057
1000
|
if (changed) await writeFile(manifestPath, manifest);
|
|
1058
1001
|
}
|
|
1059
|
-
async #ensureAndroidAssetsDir() {
|
|
1060
|
-
await mkdir(path.join(this.app.cwdPath, this.androidAssetsPath), { recursive: true });
|
|
1061
|
-
}
|
|
1062
1002
|
async #ensureAndroidDebugKeystore() {
|
|
1063
1003
|
const keystorePath = path.join(this.app.cwdPath, this.androidRootPath, "app/debug.keystore");
|
|
1064
1004
|
if (await Bun.file(keystorePath).exists()) return;
|
|
1065
1005
|
|
|
1006
|
+
const options =
|
|
1007
|
+
"-storepass android -alias androiddebugkey -keypass android -keyalg RSA -keysize 2048 -validity 10000";
|
|
1066
1008
|
await this.#spawn("keytool", [
|
|
1067
1009
|
"-genkeypair",
|
|
1068
1010
|
"-v",
|
|
1069
1011
|
"-keystore",
|
|
1070
1012
|
keystorePath,
|
|
1071
|
-
"
|
|
1072
|
-
"android",
|
|
1073
|
-
"-alias",
|
|
1074
|
-
"androiddebugkey",
|
|
1075
|
-
"-keypass",
|
|
1076
|
-
"android",
|
|
1077
|
-
"-keyalg",
|
|
1078
|
-
"RSA",
|
|
1079
|
-
"-keysize",
|
|
1080
|
-
"2048",
|
|
1081
|
-
"-validity",
|
|
1082
|
-
"10000",
|
|
1013
|
+
...options.split(" "),
|
|
1083
1014
|
"-dname",
|
|
1084
1015
|
"CN=Android Debug,O=Android,C=US",
|
|
1085
1016
|
]);
|
|
@@ -1094,8 +1025,7 @@ export class CapacitorApp {
|
|
|
1094
1025
|
await this.#prepareAndroid({ operation, env, regenerate });
|
|
1095
1026
|
await this.#assertAndroidAdbReady();
|
|
1096
1027
|
this.app.logger.info(`Running Android in ${operation} mode on ${env} env`);
|
|
1097
|
-
|
|
1098
|
-
await this.#spawnMobile("npx", args, { operation, env }, { stdio: "inherit" });
|
|
1028
|
+
await this.#spawnMobile("npx", ["cap", "run", "android"], { operation, env }, { stdio: "inherit" });
|
|
1099
1029
|
}
|
|
1100
1030
|
|
|
1101
1031
|
async #assertAndroidReleaseSigningConfig() {
|
|
@@ -1171,8 +1101,7 @@ export class CapacitorApp {
|
|
|
1171
1101
|
await Bun.write(path.join(this.targetRoot, "capacitor.config.json"), content);
|
|
1172
1102
|
return content;
|
|
1173
1103
|
}
|
|
1174
|
-
//
|
|
1175
|
-
// loopback alias (10.0.2.2 / localhost), so LAN detection only applies to physical-device runs.
|
|
1104
|
+
// Emulators and simulators reach the host through a loopback alias (10.0.2.2 / localhost); only devices need the LAN.
|
|
1176
1105
|
async #resolveLocalDevHost({
|
|
1177
1106
|
override,
|
|
1178
1107
|
platform,
|
|
@@ -1196,8 +1125,6 @@ export class CapacitorApp {
|
|
|
1196
1125
|
}
|
|
1197
1126
|
return resolution;
|
|
1198
1127
|
}
|
|
1199
|
-
// Surface the live-reload URL a physical device must reach, and warn when auto-detection landed on
|
|
1200
|
-
// a likely-unreachable host so a blank WebView is not mistaken for an app bug.
|
|
1201
1128
|
#logDevHostResolution(resolution: LocalDevHostResolution, commandEnv: MobileCommandEnv) {
|
|
1202
1129
|
this.app.log(`Mobile live-reload server: ${this.#localCsrUrl(resolution.host, commandEnv)}`);
|
|
1203
1130
|
if (resolution.source === "override" || resolution.source === "platform") return;
|
|
@@ -1214,14 +1141,12 @@ export class CapacitorApp {
|
|
|
1214
1141
|
async #prepareTargetAssets() {
|
|
1215
1142
|
if (!this.target.assets) return;
|
|
1216
1143
|
await mkdir(this.targetAssetRoot, { recursive: true });
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
force: true,
|
|
1224
|
-
});
|
|
1144
|
+
for (const [name, source] of [
|
|
1145
|
+
["icon", this.target.assets.icon],
|
|
1146
|
+
["splash", this.target.assets.splash],
|
|
1147
|
+
] as const)
|
|
1148
|
+
if (source)
|
|
1149
|
+
await cp(path.join(this.app.cwdPath, source), path.join(this.targetAssetRoot, `${name}.png`), { force: true });
|
|
1225
1150
|
}
|
|
1226
1151
|
async #prepareExternalFiles(platform: "ios" | "android") {
|
|
1227
1152
|
const files = this.target.files?.[platform];
|
|
@@ -1304,20 +1229,11 @@ export class CapacitorApp {
|
|
|
1304
1229
|
operation,
|
|
1305
1230
|
env,
|
|
1306
1231
|
setIosUsageDescriptions: (descriptions) => this.#setPermissionInIos(descriptions),
|
|
1307
|
-
updateIosInfoPlist:
|
|
1308
|
-
await Promise.all([
|
|
1309
|
-
this.project.ios.updateInfoPlist(this.iosTargetName, "Debug", values),
|
|
1310
|
-
this.project.ios.updateInfoPlist(this.iosTargetName, "Release", values),
|
|
1311
|
-
]);
|
|
1312
|
-
},
|
|
1232
|
+
updateIosInfoPlist: (values) => this.#updateIosInfoPlist(values),
|
|
1313
1233
|
addIosEntitlements: (entitlements) => this.#addIosEntitlements(entitlements),
|
|
1314
1234
|
editIosAppDelegate: (transform) => this.#editIosAppDelegate(transform),
|
|
1315
|
-
addAndroidPermissions: (permissions) =>
|
|
1316
|
-
|
|
1317
|
-
},
|
|
1318
|
-
addAndroidFeatures: (features) => {
|
|
1319
|
-
this.#setFeaturesInAndroid(features);
|
|
1320
|
-
},
|
|
1235
|
+
addAndroidPermissions: (permissions) => this.#setPermissionsInAndroid(permissions),
|
|
1236
|
+
addAndroidFeatures: (features) => this.#setFeaturesInAndroid(features),
|
|
1321
1237
|
};
|
|
1322
1238
|
}
|
|
1323
1239
|
async #applyDeepLinks(platform: "ios" | "android", { operation, env }: Pick<RunConfig, "operation" | "env">) {
|
|
@@ -1397,12 +1313,6 @@ export class CapacitorApp {
|
|
|
1397
1313
|
if (this.target.indexPath) params.set("akanMobileIndexPath", this.target.indexPath);
|
|
1398
1314
|
return `http://${ip}:${port}/${pathname}?${params}`;
|
|
1399
1315
|
}
|
|
1400
|
-
async #clearRootCapacitorConfigs() {
|
|
1401
|
-
await clearRootCapacitorConfigs(this.app.cwdPath);
|
|
1402
|
-
}
|
|
1403
|
-
async #writeRootCapacitorConfig(content: string) {
|
|
1404
|
-
await writeRootCapacitorConfig(this.app.cwdPath, content);
|
|
1405
|
-
}
|
|
1406
1316
|
async #spawn(command: string, args: string[] = [], options: Parameters<AppExecutor["spawn"]>[2] = {}) {
|
|
1407
1317
|
return await this.app.spawn(command, args, { cwd: this.app.cwdPath, ...options });
|
|
1408
1318
|
}
|
|
@@ -1418,14 +1328,11 @@ export class CapacitorApp {
|
|
|
1418
1328
|
{ operation, platform: platform ?? this.#inferMobilePlatform(args), iosRunTargetKind },
|
|
1419
1329
|
mobileEnv,
|
|
1420
1330
|
);
|
|
1421
|
-
await
|
|
1331
|
+
await writeRootCapacitorConfig(this.app.cwdPath, configContent);
|
|
1422
1332
|
try {
|
|
1423
|
-
return await this.#spawn(command, args, {
|
|
1424
|
-
...spawnOptions,
|
|
1425
|
-
env: mobileEnv,
|
|
1426
|
-
});
|
|
1333
|
+
return await this.#spawn(command, args, { ...spawnOptions, env: mobileEnv });
|
|
1427
1334
|
} finally {
|
|
1428
|
-
await
|
|
1335
|
+
await clearRootCapacitorConfigs(this.app.cwdPath);
|
|
1429
1336
|
}
|
|
1430
1337
|
}
|
|
1431
1338
|
#inferMobilePlatform(args: string[]): MobilePlatform | undefined {
|
|
@@ -1458,7 +1365,6 @@ export class CapacitorApp {
|
|
|
1458
1365
|
#addIosEntitlements(entitlements: Record<string, string | string[]>) {
|
|
1459
1366
|
Object.assign(this.#iosEntitlements, entitlements);
|
|
1460
1367
|
}
|
|
1461
|
-
//* Generic in-place transform of ios/App/App/AppDelegate.swift (no-op if the file is absent).
|
|
1462
1368
|
//* Feature-specific native wiring (e.g. Firebase) lives in plugins, not the framework.
|
|
1463
1369
|
async #editIosAppDelegate(transform: (content: string) => string) {
|
|
1464
1370
|
const appDelegatePath = path.join(this.app.cwdPath, this.iosProjectPath, "App/AppDelegate.swift");
|
|
@@ -1468,24 +1374,20 @@ export class CapacitorApp {
|
|
|
1468
1374
|
const next = transform(content);
|
|
1469
1375
|
if (next !== content) await editor.setContent(next).save();
|
|
1470
1376
|
}
|
|
1471
|
-
async #
|
|
1472
|
-
|
|
1473
|
-
|
|
1377
|
+
async #updateIosInfoPlist(values: Record<string, unknown>) {
|
|
1378
|
+
await Promise.all(
|
|
1379
|
+
(["Debug", "Release"] as const).map((build) =>
|
|
1380
|
+
this.project.ios.updateInfoPlist(this.iosTargetName, build, values),
|
|
1381
|
+
),
|
|
1474
1382
|
);
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
]);
|
|
1383
|
+
}
|
|
1384
|
+
async #setPermissionInIos(permissions: { [key: string]: string }) {
|
|
1385
|
+
await this.#updateIosInfoPlist(toIosInfoPlistUsageDescriptions(permissions));
|
|
1479
1386
|
}
|
|
1480
1387
|
async #setUrlSchemesInIos(schemes: string[]) {
|
|
1481
|
-
|
|
1482
|
-
CFBundleURLName: this.target.appId,
|
|
1483
|
-
|
|
1484
|
-
}));
|
|
1485
|
-
await Promise.all([
|
|
1486
|
-
this.project.ios.updateInfoPlist(this.iosTargetName, "Debug", { CFBundleURLTypes: urlTypes }),
|
|
1487
|
-
this.project.ios.updateInfoPlist(this.iosTargetName, "Release", { CFBundleURLTypes: urlTypes }),
|
|
1488
|
-
]);
|
|
1388
|
+
await this.#updateIosInfoPlist({
|
|
1389
|
+
CFBundleURLTypes: schemes.map((scheme) => ({ CFBundleURLName: this.target.appId, CFBundleURLSchemes: [scheme] })),
|
|
1390
|
+
});
|
|
1489
1391
|
}
|
|
1490
1392
|
#serializeIosEntitlements(entitlements: Record<string, string | string[]>) {
|
|
1491
1393
|
const lines: string[] = [];
|
|
@@ -1499,8 +1401,6 @@ export class CapacitorApp {
|
|
|
1499
1401
|
}
|
|
1500
1402
|
return lines;
|
|
1501
1403
|
}
|
|
1502
|
-
//* Write the accumulated iOS entitlements (from deep links + plugins) to App/App.entitlements
|
|
1503
|
-
//* once per prepare. Skipped entirely when nothing contributed entitlements.
|
|
1504
1404
|
async #flushIosEntitlements() {
|
|
1505
1405
|
if (Object.keys(this.#iosEntitlements).length === 0) return;
|
|
1506
1406
|
const entitlementsRelPath = "App/App.entitlements";
|
|
@@ -1558,73 +1458,51 @@ export class CapacitorApp {
|
|
|
1558
1458
|
if (!(await Bun.file(entitlementsPath).exists())) return;
|
|
1559
1459
|
await this.#setCodeSignEntitlementsInIos(entitlementsRelPath);
|
|
1560
1460
|
}
|
|
1561
|
-
async #
|
|
1461
|
+
async #setDeepLinksInAndroid(schemes: string[], domains: string[]) {
|
|
1562
1462
|
const manifestPath = path.join(this.app.cwdPath, this.androidRootPath, "app/src/main/AndroidManifest.xml");
|
|
1563
|
-
|
|
1564
|
-
|
|
1463
|
+
const original = await readFile(manifestPath, "utf8");
|
|
1464
|
+
const pathPrefix = resolveMobilePath(this.target, "/");
|
|
1465
|
+
let manifest = original;
|
|
1565
1466
|
for (const scheme of schemes) {
|
|
1566
1467
|
if (manifest.includes(`android:scheme="${scheme}"`)) continue;
|
|
1567
|
-
|
|
1568
|
-
" <intent-filter>",
|
|
1569
|
-
' <action android:name="android.intent.action.VIEW" />',
|
|
1570
|
-
' <category android:name="android.intent.category.DEFAULT" />',
|
|
1571
|
-
' <category android:name="android.intent.category.BROWSABLE" />',
|
|
1572
|
-
` <data android:scheme="${scheme}" />`,
|
|
1573
|
-
" </intent-filter>",
|
|
1574
|
-
].join("\n");
|
|
1575
|
-
manifest = manifest.replace(/(\s*<\/activity>)/, `\n${filter}$1`);
|
|
1576
|
-
changed = true;
|
|
1468
|
+
manifest = CapacitorApp.#withIntentFilter(manifest, "<intent-filter>", `<data android:scheme="${scheme}" />`);
|
|
1577
1469
|
}
|
|
1578
|
-
if (changed) await writeFile(manifestPath, manifest);
|
|
1579
|
-
}
|
|
1580
|
-
async #setDeepLinksInAndroid(schemes: string[], domains: string[]) {
|
|
1581
|
-
await this.#setUrlSchemesInAndroid(schemes);
|
|
1582
|
-
const manifestPath = path.join(this.app.cwdPath, this.androidRootPath, "app/src/main/AndroidManifest.xml");
|
|
1583
|
-
let manifest = await readFile(manifestPath, "utf8");
|
|
1584
|
-
let changed = false;
|
|
1585
|
-
const pathPrefix = resolveMobilePath(this.target, "/");
|
|
1586
1470
|
for (const domain of domains) {
|
|
1587
1471
|
if (manifest.includes(`android:host="${domain}"`) && manifest.includes('android:scheme="https"')) continue;
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
'
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
` <data android:scheme="https" android:host="${domain}" android:pathPrefix="${pathPrefix}" />`,
|
|
1594
|
-
" </intent-filter>",
|
|
1595
|
-
].join("\n");
|
|
1596
|
-
manifest = manifest.replace(/(\s*<\/activity>)/, `\n${filter}$1`);
|
|
1597
|
-
changed = true;
|
|
1472
|
+
manifest = CapacitorApp.#withIntentFilter(
|
|
1473
|
+
manifest,
|
|
1474
|
+
'<intent-filter android:autoVerify="true">',
|
|
1475
|
+
`<data android:scheme="https" android:host="${domain}" android:pathPrefix="${pathPrefix}" />`,
|
|
1476
|
+
);
|
|
1598
1477
|
}
|
|
1599
|
-
if (
|
|
1478
|
+
if (manifest !== original) await writeFile(manifestPath, manifest);
|
|
1479
|
+
}
|
|
1480
|
+
static #withIntentFilter(manifest: string, open: string, data: string) {
|
|
1481
|
+
const filter = [
|
|
1482
|
+
` ${open}`,
|
|
1483
|
+
' <action android:name="android.intent.action.VIEW" />',
|
|
1484
|
+
' <category android:name="android.intent.category.DEFAULT" />',
|
|
1485
|
+
' <category android:name="android.intent.category.BROWSABLE" />',
|
|
1486
|
+
` ${data}`,
|
|
1487
|
+
" </intent-filter>",
|
|
1488
|
+
].join("\n");
|
|
1489
|
+
return manifest.replace(/(\s*<\/activity>)/, `\n${filter}$1`);
|
|
1600
1490
|
}
|
|
1601
1491
|
#setFeaturesInAndroid(features: string[]) {
|
|
1602
1492
|
for (const feature of features) {
|
|
1603
|
-
if (this.#
|
|
1493
|
+
if (this.#androidManifestNames("uses-feature").includes(feature)) {
|
|
1604
1494
|
this.app.logger.info(`${feature} already exists in android`);
|
|
1605
|
-
return
|
|
1495
|
+
return;
|
|
1606
1496
|
}
|
|
1607
1497
|
this.app.logger.info(`Adding ${feature} to android`);
|
|
1608
1498
|
this.project.android
|
|
1609
1499
|
.getAndroidManifest()
|
|
1610
1500
|
.injectFragment("manifest", `<uses-feature android:name="${feature}" />`);
|
|
1611
1501
|
}
|
|
1612
|
-
return this;
|
|
1613
|
-
}
|
|
1614
|
-
#getFeaturesInAndroid() {
|
|
1615
|
-
const androidManifest = this.project.android.getAndroidManifest();
|
|
1616
|
-
const element = androidManifest.getDocumentElement();
|
|
1617
|
-
if (!element) throw new Error("manifest not found");
|
|
1618
|
-
const usesFeature = element.getElementsByTagName("uses-feature");
|
|
1619
|
-
return Array.from(usesFeature).map((feature) => feature.getAttribute("android:name"));
|
|
1620
|
-
}
|
|
1621
|
-
#hasFeatureInAndroid(feature: string) {
|
|
1622
|
-
return this.#getFeaturesInAndroid().includes(feature);
|
|
1623
1502
|
}
|
|
1624
|
-
|
|
1625
1503
|
#setPermissionsInAndroid(permissions: string[]) {
|
|
1626
1504
|
for (const permission of permissions) {
|
|
1627
|
-
if (this.#
|
|
1505
|
+
if (this.#androidManifestNames("uses-permission").includes(`android.permission.${permission}`)) {
|
|
1628
1506
|
this.app.logger.info(`${permission} already exists in android`);
|
|
1629
1507
|
continue;
|
|
1630
1508
|
}
|
|
@@ -1633,16 +1511,10 @@ export class CapacitorApp {
|
|
|
1633
1511
|
.getAndroidManifest()
|
|
1634
1512
|
.injectFragment("manifest", `<uses-permission android:name="android.permission.${permission}" />`);
|
|
1635
1513
|
}
|
|
1636
|
-
return this;
|
|
1637
1514
|
}
|
|
1638
|
-
#
|
|
1639
|
-
const
|
|
1640
|
-
const element = androidManifest.getDocumentElement();
|
|
1515
|
+
#androidManifestNames(tagName: "uses-feature" | "uses-permission") {
|
|
1516
|
+
const element = this.project.android.getAndroidManifest().getDocumentElement();
|
|
1641
1517
|
if (!element) throw new Error("manifest not found");
|
|
1642
|
-
|
|
1643
|
-
return Array.from(usesPermission).map((permission) => permission.getAttribute("android:name"));
|
|
1644
|
-
}
|
|
1645
|
-
#hasPermissionInAndroid(permission: string) {
|
|
1646
|
-
return this.#getPermissionsInAndroid().includes(`android.permission.${permission}`);
|
|
1518
|
+
return Array.from(element.getElementsByTagName(tagName)).map((node) => node.getAttribute("android:name"));
|
|
1647
1519
|
}
|
|
1648
1520
|
}
|