@akanjs/devkit 3.0.0-beta.19 → 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/abstractDoc.test.ts +6 -15
- package/abstractDoc.ts +1 -4
- 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 +64 -250
- package/akanApp/devHostPolicy.test.ts +10 -34
- package/akanApp/devHostPolicy.ts +12 -98
- package/akanConfig/akanConfig.test.ts +30 -39
- package/akanConfig/akanConfig.ts +40 -136
- package/akanContext.ts +106 -159
- package/akanMcpContract.ts +28 -73
- package/appSelectionMemory.ts +1 -4
- package/applicationBuildReporter.ts +37 -5
- package/applicationBuildRunner.ts +25 -59
- package/applicationReleasePackager.ts +24 -44
- package/applicationTestPreload.ts +3 -12
- package/artifact/implicitRootLayout.test.ts +3 -19
- package/artifact/implicitRootLayout.ts +34 -91
- package/artifact/routeSeedIndex.ts +11 -44
- package/biomeBase.ts +2 -5
- package/capacitorApp.test.ts +4 -14
- package/capacitorApp.ts +86 -235
- package/cloud/cloudApi.test.ts +202 -0
- package/cloud/cloudApi.ts +33 -57
- package/cloud/configLock.test.ts +64 -0
- package/cloud/configLock.ts +96 -0
- package/cloud/constants.ts +3 -2
- package/cloud/globalConfig.ts +64 -31
- package/codeAgent/agent/AkanCodeServices.ts +15 -57
- package/codeAgent/agent/AkanContextFiles.ts +5 -28
- package/codeAgent/agent/CodeAgent.ts +33 -108
- package/codeAgent/agent/CodeAgentAsks.ts +7 -30
- package/codeAgent/agent/CodeAgentEventMapper.ts +9 -52
- package/codeAgent/agent/CodeAgentGate.ts +8 -34
- package/codeAgent/agent/CodeAgentProxy.ts +4 -13
- package/codeAgent/agent/CodeAgentRpcHost.ts +3 -16
- package/codeAgent/agent/CodeAgentRpcListener.ts +2 -10
- package/codeAgent/agent/CodeAgentStreamPrinter.ts +4 -15
- package/codeAgent/agent/CodeAgentSuspended.ts +1 -8
- package/codeAgent/agent/CodeAgentUi.ts +2 -7
- package/codeAgent/agent/CodeMailbox.ts +7 -34
- package/codeAgent/agent/CodeSessionFork.ts +4 -19
- package/codeAgent/agent/CodeSessionIndex.ts +5 -31
- package/codeAgent/agent/SubagentPool.ts +48 -58
- package/codeAgent/agent/akanCodeModel.ts +5 -39
- package/codeAgent/agent/akanCodePaths.ts +4 -24
- package/codeAgent/agent/akanContextFiles.test.ts +0 -2
- package/codeAgent/agent/akanSystemPrompt.ts +1 -9
- package/codeAgent/agent/codeAgent.test.ts +178 -0
- package/codeAgent/agent/codeAgentContract.test.ts +5 -28
- package/codeAgent/agent/codeMailbox.test.ts +0 -4
- package/codeAgent/agent/codeSessionFork.test.ts +0 -2
- package/codeAgent/agent/codeSessionIndex.test.ts +9 -4
- package/codeAgent/agent/consoleToStderr.ts +2 -12
- package/codeAgent/agent/jsonLines.ts +29 -0
- package/codeAgent/agent/stringArg.ts +5 -0
- package/codeAgent/feedback/DevLogFeedback.ts +3 -18
- package/codeAgent/feedback/PreviewChrome.ts +5 -29
- package/codeAgent/feedback/PreviewFeedback.ts +2 -9
- package/codeAgent/feedback/PreviewView.ts +3 -15
- package/codeAgent/feedback/TurnFeedback.ts +2 -17
- package/codeAgent/feedback/VerifyFeedback.ts +1 -7
- package/codeAgent/feedback/previewChrome.test.ts +1 -1
- package/codeAgent/index.ts +2 -10
- package/codeAgent/skills/akan-code/SKILL.md +12 -4
- package/codeAgent/tools/AkanCli.ts +4 -16
- package/codeAgent/tools/AkanCodePlugins.ts +21 -43
- package/codeAgent/tools/AkanEditScope.ts +2 -12
- package/codeAgent/tools/AkanEnvKeys.ts +3 -11
- package/codeAgent/tools/AkanToolPack.ts +6 -28
- package/codeAgent/tools/AkanVerifier.ts +1 -8
- package/codeAgent/tools/AskToolPack.ts +3 -15
- package/codeAgent/tools/MailToolPack.ts +0 -9
- package/codeAgent/tools/McpClient.ts +3 -24
- package/codeAgent/tools/McpOAuth.ts +6 -41
- package/codeAgent/tools/McpOAuthLoopback.ts +2 -15
- package/codeAgent/tools/McpServerConfig.ts +7 -26
- package/codeAgent/tools/McpSignIn.ts +46 -49
- package/codeAgent/tools/McpTokenStore.ts +6 -17
- package/codeAgent/tools/McpToolPack.ts +107 -35
- package/codeAgent/tools/SessionToolPack.ts +1 -11
- package/codeAgent/tools/WebToolPack.ts +1 -7
- package/codeAgent/tools/akanCodePlugins.test.ts +231 -63
- package/codeAgent/tools/askToolPack.test.ts +0 -3
- package/codeAgent/tools/mcpOAuth.test.ts +315 -27
- package/codeAgent/tools/mcpServerConfig.test.ts +21 -14
- 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 +44 -102
- package/commandDecorators/commandBuilder.ts +30 -61
- package/commandDecorators/commandDecorators.test.ts +1 -9
- package/commandDecorators/dependencyBuilder.ts +10 -1
- package/commandDecorators/helpFormatter.ts +66 -144
- 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 +27 -78
- package/devkitUtils.test.ts +47 -19
- package/executors.test.ts +114 -60
- package/executors.ts +165 -484
- package/fileEditor.ts +9 -37
- package/{getDirname.test.ts → fileSys.test.ts} +1 -1
- package/fileSys.ts +6 -8
- package/formSetterScanner.ts +3 -16
- package/frontendBuild/allRoutesBuilder.ts +7 -21
- package/frontendBuild/autoImportSync.test.ts +14 -38
- package/frontendBuild/autoImportSync.ts +15 -67
- package/frontendBuild/buildRouteClient.test.ts +10 -36
- 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 +5 -18
- package/frontendBuild/devChangePlanner.ts +7 -10
- package/frontendBuild/devGeneratedIndexSync.test.ts +12 -27
- package/frontendBuild/devGeneratedIndexSync.ts +1 -4
- package/frontendBuild/fontOptimizer.test.ts +5 -13
- package/frontendBuild/fontOptimizer.ts +15 -72
- package/frontendBuild/fontPruner.test.ts +5 -10
- package/frontendBuild/fontPruner.ts +5 -20
- package/frontendBuild/frontendBuild.test.ts +27 -52
- package/frontendBuild/hmrChangeClassifier.ts +2 -2
- package/frontendBuild/hmrWatcher.test.ts +7 -31
- package/frontendBuild/hmrWatcher.ts +14 -71
- package/frontendBuild/pagesBundleBuilder.ts +4 -29
- package/frontendBuild/precompressArtifacts.ts +5 -18
- package/frontendBuild/routeClientBuilder.ts +7 -47
- package/frontendBuild/routesManifestArtifactSerializer.ts +5 -36
- package/frontendBuild/sourceMtimeIndex.test.ts +37 -94
- 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 +14 -18
- 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 +48 -161
- package/index.ts +3 -15
- package/integration/devResourceProbe.ts +50 -56
- package/integration/devStability.integration.test.ts +47 -194
- package/integration/devStabilityHarness.test.ts +9 -29
- 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 +6 -23
- package/linter.test.ts +1 -2
- package/linter.ts +22 -115
- package/mobile/mobileTarget.ts +14 -13
- package/package.json +2 -2
- package/packageExports.test.ts +3 -18
- package/packageExportsMap.ts +3 -19
- package/piDependencyPins.test.ts +3 -14
- package/platformTest/IdleWatchedProcess.test.ts +5 -13
- package/platformTest/IdleWatchedProcess.ts +1 -5
- package/platformTest/LinuxDockerTarget.ts +20 -35
- package/platformTest/PlatformTestRun.test.ts +5 -9
- package/platformTest/PlatformTestTarget.ts +24 -2
- package/platformTest/TestSnapshot.test.ts +6 -14
- package/platformTest/TestSnapshot.ts +1 -5
- package/platformTest/WindowsSshTarget.ts +6 -22
- package/prompter.ts +3 -6
- package/qualityScanner.test.ts +6 -24
- package/qualityScanner.ts +196 -284
- package/recipeScanner.test.ts +7 -12
- package/recipeScanner.ts +9 -38
- package/repoIdentity.ts +2 -9
- package/routeSourceValidator.ts +7 -30
- package/scanInfo.ts +82 -214
- 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/subspace.test.ts +6 -25
- package/subspace.ts +31 -109
- package/subspaceConfig.ts +3 -11
- package/testHelpers.ts +49 -34
- 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/uploadRelease.ts +10 -25
- package/workflow/artifacts.ts +18 -0
- package/workflow/executor.test.ts +1 -12
- package/workflow/executor.ts +85 -146
- 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/source.ts +33 -56
- package/workflow/sourceAst.ts +50 -0
- package/workspaceLayout.ts +4 -19
- package/commandDecorators/commandMeta.ts +0 -7
- package/commandDecorators/types.ts +0 -10
- package/getDirname.ts +0 -4
- 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;
|
|
@@ -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",
|
|
@@ -643,9 +587,7 @@ export function assertJsonSerializable(value: unknown, label = "capacitor.config
|
|
|
643
587
|
if (seen.has(objectValue)) throw new Error(`${label} must be JSON serializable. Found circular reference.`);
|
|
644
588
|
seen.add(objectValue);
|
|
645
589
|
if (Array.isArray(value)) {
|
|
646
|
-
value.
|
|
647
|
-
assertJsonSerializable(item, `${label}[${index}]`, seen);
|
|
648
|
-
});
|
|
590
|
+
for (const [index, item] of value.entries()) assertJsonSerializable(item, `${label}[${index}]`, seen);
|
|
649
591
|
return;
|
|
650
592
|
}
|
|
651
593
|
for (const [key, item] of Object.entries(value as Record<string, unknown>)) {
|
|
@@ -750,8 +692,6 @@ export class CapacitorApp {
|
|
|
750
692
|
readonly iosProjectPath = "ios/App";
|
|
751
693
|
readonly androidRootPath = "android";
|
|
752
694
|
readonly androidAssetsPath = "android/app/src/main/assets";
|
|
753
|
-
//* Accumulates iOS entitlements contributed by deep links and plugins during a prepare,
|
|
754
|
-
//* then flushed to App/App.entitlements once (see #flushIosEntitlements).
|
|
755
695
|
#iosEntitlements: Record<string, string | string[]> = {};
|
|
756
696
|
constructor(
|
|
757
697
|
private readonly app: AppExecutor,
|
|
@@ -814,7 +754,6 @@ export class CapacitorApp {
|
|
|
814
754
|
await this.#prepareIos({ operation: "release", env, regenerate });
|
|
815
755
|
await this.#spawnMobile("npx", ["cap", "build", "ios"], { operation: "release", env }, { stdio: "inherit" });
|
|
816
756
|
this.app.verbose(`build completed iOS.`);
|
|
817
|
-
return;
|
|
818
757
|
}
|
|
819
758
|
async syncIos() {
|
|
820
759
|
await this.#spawnMobile("npx", ["cap", "sync", "ios"], { operation: "local", env: "local" });
|
|
@@ -839,7 +778,10 @@ export class CapacitorApp {
|
|
|
839
778
|
}
|
|
840
779
|
|
|
841
780
|
async #selectIosRunTarget(deviceId?: string) {
|
|
842
|
-
const targets = sortIosRunTargets(
|
|
781
|
+
const targets = sortIosRunTargets([
|
|
782
|
+
...(await this.#loadPhysicalIosDevices()),
|
|
783
|
+
...(await this.#loadIosSimulators()),
|
|
784
|
+
]);
|
|
843
785
|
if (deviceId) {
|
|
844
786
|
const needle = deviceId.toLowerCase();
|
|
845
787
|
const found =
|
|
@@ -881,12 +823,6 @@ export class CapacitorApp {
|
|
|
881
823
|
);
|
|
882
824
|
}
|
|
883
825
|
|
|
884
|
-
async #loadIosRunTargets() {
|
|
885
|
-
const devices = await this.#loadPhysicalIosDevices();
|
|
886
|
-
const simulators = await this.#loadIosSimulators();
|
|
887
|
-
return [...devices, ...simulators];
|
|
888
|
-
}
|
|
889
|
-
|
|
890
826
|
async #loadPhysicalIosDevices() {
|
|
891
827
|
try {
|
|
892
828
|
return parseDevicectlDevices(await this.#spawn("xcrun", ["devicectl", "list", "devices", "--json-output", "-"]));
|
|
@@ -929,12 +865,11 @@ export class CapacitorApp {
|
|
|
929
865
|
{ operation, platform: "ios", iosRunTargetKind: runTarget.kind },
|
|
930
866
|
mobileEnv,
|
|
931
867
|
);
|
|
932
|
-
await
|
|
933
|
-
const scheme = this.#iosScheme();
|
|
868
|
+
await writeRootCapacitorConfig(this.app.cwdPath, configContent);
|
|
934
869
|
const command = buildIosNativeRunCommand({
|
|
935
870
|
appRoot: this.app.cwdPath,
|
|
936
871
|
device: runTarget,
|
|
937
|
-
scheme,
|
|
872
|
+
scheme: isRecord(this.target.ios) && typeof this.target.ios.scheme === "string" ? this.target.ios.scheme : "App",
|
|
938
873
|
configuration: operation === "release" ? "Release" : "Debug",
|
|
939
874
|
});
|
|
940
875
|
const xcodebuildArgs = noAllowProvisioningUpdates
|
|
@@ -946,16 +881,10 @@ export class CapacitorApp {
|
|
|
946
881
|
env: mobileEnv,
|
|
947
882
|
});
|
|
948
883
|
const devicectlId = runTarget.devicectlId ?? runTarget.id;
|
|
949
|
-
|
|
950
|
-
env: mobileEnv
|
|
951
|
-
|
|
952
|
-
await this
|
|
953
|
-
"xcrun",
|
|
954
|
-
["devicectl", "device", "process", "launch", "--device", devicectlId, this.target.appId],
|
|
955
|
-
{
|
|
956
|
-
env: mobileEnv,
|
|
957
|
-
},
|
|
958
|
-
);
|
|
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);
|
|
959
888
|
} catch (error) {
|
|
960
889
|
throw new Error(
|
|
961
890
|
formatIosRunFailureMessage({
|
|
@@ -966,14 +895,10 @@ export class CapacitorApp {
|
|
|
966
895
|
}),
|
|
967
896
|
);
|
|
968
897
|
} finally {
|
|
969
|
-
await
|
|
898
|
+
await clearRootCapacitorConfigs(this.app.cwdPath);
|
|
970
899
|
}
|
|
971
900
|
}
|
|
972
901
|
|
|
973
|
-
#iosScheme() {
|
|
974
|
-
return isRecord(this.target.ios) && typeof this.target.ios.scheme === "string" ? this.target.ios.scheme : "App";
|
|
975
|
-
}
|
|
976
|
-
|
|
977
902
|
async #getIosDevelopmentTeam() {
|
|
978
903
|
const pbxprojPath = path.join(this.app.cwdPath, this.iosProjectPath, "App.xcodeproj/project.pbxproj");
|
|
979
904
|
if (!(await Bun.file(pbxprojPath).exists())) return undefined;
|
|
@@ -996,15 +921,13 @@ export class CapacitorApp {
|
|
|
996
921
|
await this.#disableNativeKeyboardResizeInAndroid();
|
|
997
922
|
await this.project.commit();
|
|
998
923
|
await this.#generateAssets({ operation, env });
|
|
999
|
-
await this
|
|
924
|
+
await mkdir(path.join(this.app.cwdPath, this.androidAssetsPath), { recursive: true });
|
|
1000
925
|
await this.#ensureAndroidDebugKeystore();
|
|
1001
926
|
await this.#spawnMobile("npx", ["cap", "sync", "android"], { operation, env });
|
|
1002
927
|
await this.#setDeepLinksInAndroid(this.target.deepLinks?.schemes ?? [], this.target.deepLinks?.domains ?? []);
|
|
1003
928
|
}
|
|
1004
929
|
|
|
1005
930
|
async #updateAndroidBuildTypes() {
|
|
1006
|
-
//keystore 기본 설정 및 debug, release 설정
|
|
1007
|
-
|
|
1008
931
|
const appGradle = await FileEditor.create(path.join(this.app.cwdPath, this.androidRootPath, "app/build.gradle"));
|
|
1009
932
|
const buildTypesBlock = `
|
|
1010
933
|
debug {
|
|
@@ -1048,10 +971,7 @@ export class CapacitorApp {
|
|
|
1048
971
|
await this.#prepareAndroid({ operation: "release", env, regenerate });
|
|
1049
972
|
await this.#assertAndroidReleaseSigningConfig();
|
|
1050
973
|
await this.#updateAndroidBuildTypes();
|
|
1051
|
-
|
|
1052
|
-
const isWindows = process.platform === "win32";
|
|
1053
|
-
const gradleCommand = isWindows ? "gradlew.bat" : "./gradlew";
|
|
1054
|
-
|
|
974
|
+
const gradleCommand = process.platform === "win32" ? "gradlew.bat" : "./gradlew";
|
|
1055
975
|
await this.app.spawn(gradleCommand, [assembleType === "apk" ? "assembleRelease" : "bundleRelease"], {
|
|
1056
976
|
stdio: "inherit",
|
|
1057
977
|
cwd: path.join(this.app.cwdPath, this.androidRootPath),
|
|
@@ -1079,30 +999,18 @@ export class CapacitorApp {
|
|
|
1079
999
|
});
|
|
1080
1000
|
if (changed) await writeFile(manifestPath, manifest);
|
|
1081
1001
|
}
|
|
1082
|
-
async #ensureAndroidAssetsDir() {
|
|
1083
|
-
await mkdir(path.join(this.app.cwdPath, this.androidAssetsPath), { recursive: true });
|
|
1084
|
-
}
|
|
1085
1002
|
async #ensureAndroidDebugKeystore() {
|
|
1086
1003
|
const keystorePath = path.join(this.app.cwdPath, this.androidRootPath, "app/debug.keystore");
|
|
1087
1004
|
if (await Bun.file(keystorePath).exists()) return;
|
|
1088
1005
|
|
|
1006
|
+
const options =
|
|
1007
|
+
"-storepass android -alias androiddebugkey -keypass android -keyalg RSA -keysize 2048 -validity 10000";
|
|
1089
1008
|
await this.#spawn("keytool", [
|
|
1090
1009
|
"-genkeypair",
|
|
1091
1010
|
"-v",
|
|
1092
1011
|
"-keystore",
|
|
1093
1012
|
keystorePath,
|
|
1094
|
-
"
|
|
1095
|
-
"android",
|
|
1096
|
-
"-alias",
|
|
1097
|
-
"androiddebugkey",
|
|
1098
|
-
"-keypass",
|
|
1099
|
-
"android",
|
|
1100
|
-
"-keyalg",
|
|
1101
|
-
"RSA",
|
|
1102
|
-
"-keysize",
|
|
1103
|
-
"2048",
|
|
1104
|
-
"-validity",
|
|
1105
|
-
"10000",
|
|
1013
|
+
...options.split(" "),
|
|
1106
1014
|
"-dname",
|
|
1107
1015
|
"CN=Android Debug,O=Android,C=US",
|
|
1108
1016
|
]);
|
|
@@ -1117,8 +1025,7 @@ export class CapacitorApp {
|
|
|
1117
1025
|
await this.#prepareAndroid({ operation, env, regenerate });
|
|
1118
1026
|
await this.#assertAndroidAdbReady();
|
|
1119
1027
|
this.app.logger.info(`Running Android in ${operation} mode on ${env} env`);
|
|
1120
|
-
|
|
1121
|
-
await this.#spawnMobile("npx", args, { operation, env }, { stdio: "inherit" });
|
|
1028
|
+
await this.#spawnMobile("npx", ["cap", "run", "android"], { operation, env }, { stdio: "inherit" });
|
|
1122
1029
|
}
|
|
1123
1030
|
|
|
1124
1031
|
async #assertAndroidReleaseSigningConfig() {
|
|
@@ -1194,8 +1101,7 @@ export class CapacitorApp {
|
|
|
1194
1101
|
await Bun.write(path.join(this.targetRoot, "capacitor.config.json"), content);
|
|
1195
1102
|
return content;
|
|
1196
1103
|
}
|
|
1197
|
-
//
|
|
1198
|
-
// 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.
|
|
1199
1105
|
async #resolveLocalDevHost({
|
|
1200
1106
|
override,
|
|
1201
1107
|
platform,
|
|
@@ -1219,8 +1125,6 @@ export class CapacitorApp {
|
|
|
1219
1125
|
}
|
|
1220
1126
|
return resolution;
|
|
1221
1127
|
}
|
|
1222
|
-
// Surface the live-reload URL a physical device must reach, and warn when auto-detection landed on
|
|
1223
|
-
// a likely-unreachable host so a blank WebView is not mistaken for an app bug.
|
|
1224
1128
|
#logDevHostResolution(resolution: LocalDevHostResolution, commandEnv: MobileCommandEnv) {
|
|
1225
1129
|
this.app.log(`Mobile live-reload server: ${this.#localCsrUrl(resolution.host, commandEnv)}`);
|
|
1226
1130
|
if (resolution.source === "override" || resolution.source === "platform") return;
|
|
@@ -1237,14 +1141,12 @@ export class CapacitorApp {
|
|
|
1237
1141
|
async #prepareTargetAssets() {
|
|
1238
1142
|
if (!this.target.assets) return;
|
|
1239
1143
|
await mkdir(this.targetAssetRoot, { recursive: true });
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
force: true,
|
|
1247
|
-
});
|
|
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 });
|
|
1248
1150
|
}
|
|
1249
1151
|
async #prepareExternalFiles(platform: "ios" | "android") {
|
|
1250
1152
|
const files = this.target.files?.[platform];
|
|
@@ -1327,20 +1229,11 @@ export class CapacitorApp {
|
|
|
1327
1229
|
operation,
|
|
1328
1230
|
env,
|
|
1329
1231
|
setIosUsageDescriptions: (descriptions) => this.#setPermissionInIos(descriptions),
|
|
1330
|
-
updateIosInfoPlist:
|
|
1331
|
-
await Promise.all([
|
|
1332
|
-
this.project.ios.updateInfoPlist(this.iosTargetName, "Debug", values),
|
|
1333
|
-
this.project.ios.updateInfoPlist(this.iosTargetName, "Release", values),
|
|
1334
|
-
]);
|
|
1335
|
-
},
|
|
1232
|
+
updateIosInfoPlist: (values) => this.#updateIosInfoPlist(values),
|
|
1336
1233
|
addIosEntitlements: (entitlements) => this.#addIosEntitlements(entitlements),
|
|
1337
1234
|
editIosAppDelegate: (transform) => this.#editIosAppDelegate(transform),
|
|
1338
|
-
addAndroidPermissions: (permissions) =>
|
|
1339
|
-
|
|
1340
|
-
},
|
|
1341
|
-
addAndroidFeatures: (features) => {
|
|
1342
|
-
this.#setFeaturesInAndroid(features);
|
|
1343
|
-
},
|
|
1235
|
+
addAndroidPermissions: (permissions) => this.#setPermissionsInAndroid(permissions),
|
|
1236
|
+
addAndroidFeatures: (features) => this.#setFeaturesInAndroid(features),
|
|
1344
1237
|
};
|
|
1345
1238
|
}
|
|
1346
1239
|
async #applyDeepLinks(platform: "ios" | "android", { operation, env }: Pick<RunConfig, "operation" | "env">) {
|
|
@@ -1420,12 +1313,6 @@ export class CapacitorApp {
|
|
|
1420
1313
|
if (this.target.indexPath) params.set("akanMobileIndexPath", this.target.indexPath);
|
|
1421
1314
|
return `http://${ip}:${port}/${pathname}?${params}`;
|
|
1422
1315
|
}
|
|
1423
|
-
async #clearRootCapacitorConfigs() {
|
|
1424
|
-
await clearRootCapacitorConfigs(this.app.cwdPath);
|
|
1425
|
-
}
|
|
1426
|
-
async #writeRootCapacitorConfig(content: string) {
|
|
1427
|
-
await writeRootCapacitorConfig(this.app.cwdPath, content);
|
|
1428
|
-
}
|
|
1429
1316
|
async #spawn(command: string, args: string[] = [], options: Parameters<AppExecutor["spawn"]>[2] = {}) {
|
|
1430
1317
|
return await this.app.spawn(command, args, { cwd: this.app.cwdPath, ...options });
|
|
1431
1318
|
}
|
|
@@ -1441,14 +1328,11 @@ export class CapacitorApp {
|
|
|
1441
1328
|
{ operation, platform: platform ?? this.#inferMobilePlatform(args), iosRunTargetKind },
|
|
1442
1329
|
mobileEnv,
|
|
1443
1330
|
);
|
|
1444
|
-
await
|
|
1331
|
+
await writeRootCapacitorConfig(this.app.cwdPath, configContent);
|
|
1445
1332
|
try {
|
|
1446
|
-
return await this.#spawn(command, args, {
|
|
1447
|
-
...spawnOptions,
|
|
1448
|
-
env: mobileEnv,
|
|
1449
|
-
});
|
|
1333
|
+
return await this.#spawn(command, args, { ...spawnOptions, env: mobileEnv });
|
|
1450
1334
|
} finally {
|
|
1451
|
-
await
|
|
1335
|
+
await clearRootCapacitorConfigs(this.app.cwdPath);
|
|
1452
1336
|
}
|
|
1453
1337
|
}
|
|
1454
1338
|
#inferMobilePlatform(args: string[]): MobilePlatform | undefined {
|
|
@@ -1481,7 +1365,6 @@ export class CapacitorApp {
|
|
|
1481
1365
|
#addIosEntitlements(entitlements: Record<string, string | string[]>) {
|
|
1482
1366
|
Object.assign(this.#iosEntitlements, entitlements);
|
|
1483
1367
|
}
|
|
1484
|
-
//* Generic in-place transform of ios/App/App/AppDelegate.swift (no-op if the file is absent).
|
|
1485
1368
|
//* Feature-specific native wiring (e.g. Firebase) lives in plugins, not the framework.
|
|
1486
1369
|
async #editIosAppDelegate(transform: (content: string) => string) {
|
|
1487
1370
|
const appDelegatePath = path.join(this.app.cwdPath, this.iosProjectPath, "App/AppDelegate.swift");
|
|
@@ -1491,22 +1374,20 @@ export class CapacitorApp {
|
|
|
1491
1374
|
const next = transform(content);
|
|
1492
1375
|
if (next !== content) await editor.setContent(next).save();
|
|
1493
1376
|
}
|
|
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
|
+
),
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1494
1384
|
async #setPermissionInIos(permissions: { [key: string]: string }) {
|
|
1495
|
-
|
|
1496
|
-
await Promise.all([
|
|
1497
|
-
this.project.ios.updateInfoPlist(this.iosTargetName, "Debug", updateNs),
|
|
1498
|
-
this.project.ios.updateInfoPlist(this.iosTargetName, "Release", updateNs),
|
|
1499
|
-
]);
|
|
1385
|
+
await this.#updateIosInfoPlist(toIosInfoPlistUsageDescriptions(permissions));
|
|
1500
1386
|
}
|
|
1501
1387
|
async #setUrlSchemesInIos(schemes: string[]) {
|
|
1502
|
-
|
|
1503
|
-
CFBundleURLName: this.target.appId,
|
|
1504
|
-
|
|
1505
|
-
}));
|
|
1506
|
-
await Promise.all([
|
|
1507
|
-
this.project.ios.updateInfoPlist(this.iosTargetName, "Debug", { CFBundleURLTypes: urlTypes }),
|
|
1508
|
-
this.project.ios.updateInfoPlist(this.iosTargetName, "Release", { CFBundleURLTypes: urlTypes }),
|
|
1509
|
-
]);
|
|
1388
|
+
await this.#updateIosInfoPlist({
|
|
1389
|
+
CFBundleURLTypes: schemes.map((scheme) => ({ CFBundleURLName: this.target.appId, CFBundleURLSchemes: [scheme] })),
|
|
1390
|
+
});
|
|
1510
1391
|
}
|
|
1511
1392
|
#serializeIosEntitlements(entitlements: Record<string, string | string[]>) {
|
|
1512
1393
|
const lines: string[] = [];
|
|
@@ -1520,8 +1401,6 @@ export class CapacitorApp {
|
|
|
1520
1401
|
}
|
|
1521
1402
|
return lines;
|
|
1522
1403
|
}
|
|
1523
|
-
//* Write the accumulated iOS entitlements (from deep links + plugins) to App/App.entitlements
|
|
1524
|
-
//* once per prepare. Skipped entirely when nothing contributed entitlements.
|
|
1525
1404
|
async #flushIosEntitlements() {
|
|
1526
1405
|
if (Object.keys(this.#iosEntitlements).length === 0) return;
|
|
1527
1406
|
const entitlementsRelPath = "App/App.entitlements";
|
|
@@ -1579,73 +1458,51 @@ export class CapacitorApp {
|
|
|
1579
1458
|
if (!(await Bun.file(entitlementsPath).exists())) return;
|
|
1580
1459
|
await this.#setCodeSignEntitlementsInIos(entitlementsRelPath);
|
|
1581
1460
|
}
|
|
1582
|
-
async #
|
|
1461
|
+
async #setDeepLinksInAndroid(schemes: string[], domains: string[]) {
|
|
1583
1462
|
const manifestPath = path.join(this.app.cwdPath, this.androidRootPath, "app/src/main/AndroidManifest.xml");
|
|
1584
|
-
|
|
1585
|
-
|
|
1463
|
+
const original = await readFile(manifestPath, "utf8");
|
|
1464
|
+
const pathPrefix = resolveMobilePath(this.target, "/");
|
|
1465
|
+
let manifest = original;
|
|
1586
1466
|
for (const scheme of schemes) {
|
|
1587
1467
|
if (manifest.includes(`android:scheme="${scheme}"`)) continue;
|
|
1588
|
-
|
|
1589
|
-
" <intent-filter>",
|
|
1590
|
-
' <action android:name="android.intent.action.VIEW" />',
|
|
1591
|
-
' <category android:name="android.intent.category.DEFAULT" />',
|
|
1592
|
-
' <category android:name="android.intent.category.BROWSABLE" />',
|
|
1593
|
-
` <data android:scheme="${scheme}" />`,
|
|
1594
|
-
" </intent-filter>",
|
|
1595
|
-
].join("\n");
|
|
1596
|
-
manifest = manifest.replace(/(\s*<\/activity>)/, `\n${filter}$1`);
|
|
1597
|
-
changed = true;
|
|
1468
|
+
manifest = CapacitorApp.#withIntentFilter(manifest, "<intent-filter>", `<data android:scheme="${scheme}" />`);
|
|
1598
1469
|
}
|
|
1599
|
-
if (changed) await writeFile(manifestPath, manifest);
|
|
1600
|
-
}
|
|
1601
|
-
async #setDeepLinksInAndroid(schemes: string[], domains: string[]) {
|
|
1602
|
-
await this.#setUrlSchemesInAndroid(schemes);
|
|
1603
|
-
const manifestPath = path.join(this.app.cwdPath, this.androidRootPath, "app/src/main/AndroidManifest.xml");
|
|
1604
|
-
let manifest = await readFile(manifestPath, "utf8");
|
|
1605
|
-
let changed = false;
|
|
1606
|
-
const pathPrefix = resolveMobilePath(this.target, "/");
|
|
1607
1470
|
for (const domain of domains) {
|
|
1608
1471
|
if (manifest.includes(`android:host="${domain}"`) && manifest.includes('android:scheme="https"')) continue;
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
'
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
` <data android:scheme="https" android:host="${domain}" android:pathPrefix="${pathPrefix}" />`,
|
|
1615
|
-
" </intent-filter>",
|
|
1616
|
-
].join("\n");
|
|
1617
|
-
manifest = manifest.replace(/(\s*<\/activity>)/, `\n${filter}$1`);
|
|
1618
|
-
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
|
+
);
|
|
1619
1477
|
}
|
|
1620
|
-
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`);
|
|
1621
1490
|
}
|
|
1622
1491
|
#setFeaturesInAndroid(features: string[]) {
|
|
1623
1492
|
for (const feature of features) {
|
|
1624
|
-
if (this.#
|
|
1493
|
+
if (this.#androidManifestNames("uses-feature").includes(feature)) {
|
|
1625
1494
|
this.app.logger.info(`${feature} already exists in android`);
|
|
1626
|
-
return
|
|
1495
|
+
return;
|
|
1627
1496
|
}
|
|
1628
1497
|
this.app.logger.info(`Adding ${feature} to android`);
|
|
1629
1498
|
this.project.android
|
|
1630
1499
|
.getAndroidManifest()
|
|
1631
1500
|
.injectFragment("manifest", `<uses-feature android:name="${feature}" />`);
|
|
1632
1501
|
}
|
|
1633
|
-
return this;
|
|
1634
1502
|
}
|
|
1635
|
-
#getFeaturesInAndroid() {
|
|
1636
|
-
const androidManifest = this.project.android.getAndroidManifest();
|
|
1637
|
-
const element = androidManifest.getDocumentElement();
|
|
1638
|
-
if (!element) throw new Error("manifest not found");
|
|
1639
|
-
const usesFeature = element.getElementsByTagName("uses-feature");
|
|
1640
|
-
return Array.from(usesFeature).map((feature) => feature.getAttribute("android:name"));
|
|
1641
|
-
}
|
|
1642
|
-
#hasFeatureInAndroid(feature: string) {
|
|
1643
|
-
return this.#getFeaturesInAndroid().includes(feature);
|
|
1644
|
-
}
|
|
1645
|
-
|
|
1646
1503
|
#setPermissionsInAndroid(permissions: string[]) {
|
|
1647
1504
|
for (const permission of permissions) {
|
|
1648
|
-
if (this.#
|
|
1505
|
+
if (this.#androidManifestNames("uses-permission").includes(`android.permission.${permission}`)) {
|
|
1649
1506
|
this.app.logger.info(`${permission} already exists in android`);
|
|
1650
1507
|
continue;
|
|
1651
1508
|
}
|
|
@@ -1654,16 +1511,10 @@ export class CapacitorApp {
|
|
|
1654
1511
|
.getAndroidManifest()
|
|
1655
1512
|
.injectFragment("manifest", `<uses-permission android:name="android.permission.${permission}" />`);
|
|
1656
1513
|
}
|
|
1657
|
-
return this;
|
|
1658
1514
|
}
|
|
1659
|
-
#
|
|
1660
|
-
const
|
|
1661
|
-
const element = androidManifest.getDocumentElement();
|
|
1515
|
+
#androidManifestNames(tagName: "uses-feature" | "uses-permission") {
|
|
1516
|
+
const element = this.project.android.getAndroidManifest().getDocumentElement();
|
|
1662
1517
|
if (!element) throw new Error("manifest not found");
|
|
1663
|
-
|
|
1664
|
-
return Array.from(usesPermission).map((permission) => permission.getAttribute("android:name"));
|
|
1665
|
-
}
|
|
1666
|
-
#hasPermissionInAndroid(permission: string) {
|
|
1667
|
-
return this.#getPermissionsInAndroid().includes(`android.permission.${permission}`);
|
|
1518
|
+
return Array.from(element.getElementsByTagName(tagName)).map((node) => node.getAttribute("android:name"));
|
|
1668
1519
|
}
|
|
1669
1520
|
}
|