@akanjs/devkit 2.3.9 → 2.3.10-rc.0
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.
|
@@ -281,11 +281,17 @@ describe("AkanAppConfig", () => {
|
|
|
281
281
|
targets: {
|
|
282
282
|
admin: {
|
|
283
283
|
basePath: "admin",
|
|
284
|
+
indexPath: "/admin/home/",
|
|
284
285
|
appName: "Portal Admin",
|
|
285
286
|
appId: "com.portal.admin",
|
|
286
287
|
buildNum: 8,
|
|
287
288
|
permissions: ["camera"],
|
|
288
|
-
|
|
289
|
+
deepLinks: {
|
|
290
|
+
schemes: ["portal-admin", "portal-admin"],
|
|
291
|
+
domains: ["https://Portal.Admin/"],
|
|
292
|
+
ios: { teamId: " TEAMID " },
|
|
293
|
+
android: { sha256CertFingerprints: ["AA:BB", "AA:BB"] },
|
|
294
|
+
},
|
|
289
295
|
},
|
|
290
296
|
},
|
|
291
297
|
},
|
|
@@ -296,12 +302,18 @@ describe("AkanAppConfig", () => {
|
|
|
296
302
|
expect(config.mobile.targets.admin).toMatchObject({
|
|
297
303
|
name: "admin",
|
|
298
304
|
basePath: "admin",
|
|
305
|
+
indexPath: "/admin/home",
|
|
299
306
|
appName: "Portal Admin",
|
|
300
307
|
appId: "com.portal.admin",
|
|
301
308
|
version: "1.0.0",
|
|
302
309
|
buildNum: 8,
|
|
303
310
|
permissions: ["camera"],
|
|
304
|
-
|
|
311
|
+
deepLinks: {
|
|
312
|
+
schemes: ["portal-admin"],
|
|
313
|
+
domains: ["portal.admin"],
|
|
314
|
+
ios: { teamId: "TEAMID" },
|
|
315
|
+
android: { sha256CertFingerprints: ["AA:BB"] },
|
|
316
|
+
},
|
|
305
317
|
});
|
|
306
318
|
|
|
307
319
|
expect(
|
package/akanConfig/akanConfig.ts
CHANGED
|
@@ -76,6 +76,47 @@ const DEFAULT_AKAN_IMAGE_CONFIG: AkanImageConfig = {
|
|
|
76
76
|
maxRemoteBytes: 25 * 1024 * 1024,
|
|
77
77
|
};
|
|
78
78
|
|
|
79
|
+
const normalizeIndexPath = (indexPath: string | undefined): string | undefined => {
|
|
80
|
+
const normalized = indexPath?.trim();
|
|
81
|
+
if (!normalized) return undefined;
|
|
82
|
+
const path = `/${normalized.replace(/^\/+|\/+$/g, "")}`;
|
|
83
|
+
return path === "/" ? "/" : path;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const normalizeStringList = (values: string[] | undefined) => {
|
|
87
|
+
const normalized = values?.map((value) => value.trim()).filter(Boolean) ?? [];
|
|
88
|
+
return normalized.length > 0 ? [...new Set(normalized)] : undefined;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const normalizeDeepLinkDomain = (domain: string) => {
|
|
92
|
+
const normalized = domain.trim();
|
|
93
|
+
if (!normalized) return "";
|
|
94
|
+
try {
|
|
95
|
+
const url = new URL(normalized.includes("://") ? normalized : `https://${normalized}`);
|
|
96
|
+
return url.host.toLowerCase();
|
|
97
|
+
} catch {
|
|
98
|
+
return normalized
|
|
99
|
+
.replace(/^https?:\/\//, "")
|
|
100
|
+
.replace(/\/+$/g, "")
|
|
101
|
+
.toLowerCase();
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const normalizeDeepLinks = (deepLinks: DeepPartial<AkanMobileTargetConfig["deepLinks"]> | undefined) => {
|
|
106
|
+
if (!deepLinks) return undefined;
|
|
107
|
+
const schemes = normalizeStringList(deepLinks.schemes as string[] | undefined);
|
|
108
|
+
const domains = normalizeStringList((deepLinks.domains as string[] | undefined)?.map(normalizeDeepLinkDomain));
|
|
109
|
+
const teamId = (deepLinks.ios?.teamId as string | undefined)?.trim();
|
|
110
|
+
const sha256CertFingerprints = normalizeStringList(deepLinks.android?.sha256CertFingerprints as string[] | undefined);
|
|
111
|
+
if (!schemes && !domains && !teamId && !sha256CertFingerprints) return undefined;
|
|
112
|
+
return {
|
|
113
|
+
...(schemes ? { schemes } : {}),
|
|
114
|
+
...(domains ? { domains } : {}),
|
|
115
|
+
...(teamId ? { ios: { teamId } } : {}),
|
|
116
|
+
...(sha256CertFingerprints ? { android: { sha256CertFingerprints } } : {}),
|
|
117
|
+
} satisfies AkanMobileTargetConfig["deepLinks"];
|
|
118
|
+
};
|
|
119
|
+
|
|
79
120
|
export class AkanAppConfig implements AppConfigResult {
|
|
80
121
|
app: App;
|
|
81
122
|
rootPackageJson: PackageJson;
|
|
@@ -88,6 +129,7 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
88
129
|
i18n: AkanI18nConfig;
|
|
89
130
|
publicEnv: string[];
|
|
90
131
|
mobile: AkanMobileConfig;
|
|
132
|
+
secrets: string[];
|
|
91
133
|
baseDevEnv: BaseDevEnv;
|
|
92
134
|
libs: string[];
|
|
93
135
|
domains = new Set<string>();
|
|
@@ -120,11 +162,18 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
120
162
|
process.env.AKAN_PUBLIC_DEFAULT_LOCALE = this.i18n.defaultLocale;
|
|
121
163
|
process.env.AKAN_PUBLIC_LOCALES = this.i18n.locales.join(",");
|
|
122
164
|
this.publicEnv = (config?.publicEnv as string[] | undefined) ?? ([] as string[]);
|
|
165
|
+
this.secrets = (config?.secrets as string[] | undefined) ?? ([] as string[]);
|
|
123
166
|
this.mobile = this.#resolveMobileConfig(config.mobile);
|
|
124
167
|
this.docker = this.#makeDockerContent(config?.docker ?? {});
|
|
125
168
|
}
|
|
126
169
|
#resolveMobileConfig(mobile: DeepPartial<AkanMobileConfig> | undefined): AkanMobileConfig {
|
|
127
|
-
const {
|
|
170
|
+
const {
|
|
171
|
+
targets: rawTargets,
|
|
172
|
+
indexPath: _indexPath,
|
|
173
|
+
...rawMobile
|
|
174
|
+
} = (mobile ?? {}) as DeepPartial<AkanMobileConfig> & {
|
|
175
|
+
indexPath?: unknown;
|
|
176
|
+
};
|
|
128
177
|
const appName = rawMobile.appName ?? this.app.name;
|
|
129
178
|
const appId = rawMobile.appId ?? `com.${this.app.name}.app`;
|
|
130
179
|
const version = rawMobile.version ?? "0.0.1";
|
|
@@ -140,6 +189,8 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
140
189
|
const target = rawTarget as DeepPartial<AkanMobileTargetConfig>;
|
|
141
190
|
const fallbackBasePath = !rawTargets && this.basePaths.has(name) ? name : undefined;
|
|
142
191
|
const basePath = (target.basePath ?? fallbackBasePath)?.replace(/^\/+|\/+$/g, "") || undefined;
|
|
192
|
+
const indexPath = normalizeIndexPath(target.indexPath as string | undefined);
|
|
193
|
+
const deepLinks = normalizeDeepLinks(target.deepLinks);
|
|
143
194
|
if (basePath && !this.basePaths.has(basePath)) {
|
|
144
195
|
throw new Error(
|
|
145
196
|
`Mobile target '${name}' uses unknown basePath '${basePath}' in apps/${this.app.name}/akan.config.ts`,
|
|
@@ -150,6 +201,8 @@ export class AkanAppConfig implements AppConfigResult {
|
|
|
150
201
|
...target,
|
|
151
202
|
name,
|
|
152
203
|
basePath,
|
|
204
|
+
indexPath,
|
|
205
|
+
deepLinks,
|
|
153
206
|
appName: target.appName ?? appName,
|
|
154
207
|
appId: target.appId ?? appId,
|
|
155
208
|
version: target.version ?? version,
|
package/capacitorApp.test.ts
CHANGED
|
@@ -34,9 +34,10 @@ const baseTarget: AkanMobileTargetConfig = {
|
|
|
34
34
|
version: "1.2.3",
|
|
35
35
|
buildNum: 7,
|
|
36
36
|
basePath: "admin",
|
|
37
|
+
indexPath: "/dashboard",
|
|
37
38
|
assets: { icon: "mobile/icon.png" },
|
|
38
39
|
permissions: ["camera"],
|
|
39
|
-
|
|
40
|
+
deepLinks: { schemes: ["minimal"], domains: ["minimal.app"] },
|
|
40
41
|
files: { android: { "app/google-services.json": "private/google-services.json" } },
|
|
41
42
|
};
|
|
42
43
|
|
|
@@ -74,11 +75,12 @@ describe("materializeCapacitorConfig", () => {
|
|
|
74
75
|
});
|
|
75
76
|
expect(config).not.toHaveProperty("name");
|
|
76
77
|
expect(config).not.toHaveProperty("basePath");
|
|
78
|
+
expect(config).not.toHaveProperty("indexPath");
|
|
77
79
|
expect(config).not.toHaveProperty("version");
|
|
78
80
|
expect(config).not.toHaveProperty("buildNum");
|
|
79
81
|
expect(config).not.toHaveProperty("assets");
|
|
80
82
|
expect(config).not.toHaveProperty("permissions");
|
|
81
|
-
expect(config).not.toHaveProperty("
|
|
83
|
+
expect(config).not.toHaveProperty("deepLinks");
|
|
82
84
|
expect(config).not.toHaveProperty("files");
|
|
83
85
|
expect(config).not.toHaveProperty("server");
|
|
84
86
|
});
|
package/capacitorApp.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cp, mkdir, rm } from "node:fs/promises";
|
|
1
|
+
import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import type { CapacitorConfig } from "@capacitor/cli";
|
|
@@ -463,11 +463,12 @@ export function materializeCapacitorConfig(
|
|
|
463
463
|
const {
|
|
464
464
|
name,
|
|
465
465
|
basePath: _basePath,
|
|
466
|
+
indexPath: _indexPath,
|
|
466
467
|
version: _version,
|
|
467
468
|
buildNum: _buildNum,
|
|
468
469
|
assets: _assets,
|
|
469
470
|
permissions: _permissions,
|
|
470
|
-
|
|
471
|
+
deepLinks: _deepLinks,
|
|
471
472
|
files: _files,
|
|
472
473
|
appId,
|
|
473
474
|
appName,
|
|
@@ -587,7 +588,7 @@ export class CapacitorApp {
|
|
|
587
588
|
await this.#prepareExternalFiles("ios");
|
|
588
589
|
await this.#applyIosMetadata();
|
|
589
590
|
await this.#applyPermissions();
|
|
590
|
-
await this.#
|
|
591
|
+
await this.#applyDeepLinks("ios", { operation, env });
|
|
591
592
|
await this.project.commit();
|
|
592
593
|
await this.#generateAssets({ operation, env });
|
|
593
594
|
this.app.verbose(`syncing iOS`);
|
|
@@ -749,12 +750,13 @@ export class CapacitorApp {
|
|
|
749
750
|
await this.#prepareExternalFiles("android");
|
|
750
751
|
await this.#applyAndroidMetadata();
|
|
751
752
|
await this.#applyPermissions();
|
|
752
|
-
await this.#
|
|
753
|
+
await this.#applyDeepLinks("android", { operation, env });
|
|
753
754
|
await this.project.commit();
|
|
754
755
|
await this.#generateAssets({ operation, env });
|
|
755
756
|
await this.#ensureAndroidAssetsDir();
|
|
756
757
|
await this.#ensureAndroidDebugKeystore();
|
|
757
758
|
await this.#spawnMobile("npx", ["cap", "sync", "android"], { operation, env });
|
|
759
|
+
await this.#setDeepLinksInAndroid(this.target.deepLinks?.schemes ?? [], this.target.deepLinks?.domains ?? []);
|
|
758
760
|
}
|
|
759
761
|
|
|
760
762
|
async #updateAndroidBuildTypes() {
|
|
@@ -898,7 +900,11 @@ export class CapacitorApp {
|
|
|
898
900
|
}
|
|
899
901
|
#injectMobileTargetMeta(html: string) {
|
|
900
902
|
const basePath = this.target.basePath?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
901
|
-
const script = `<script>window.__AKAN_MOBILE_TARGET__=${JSON.stringify({
|
|
903
|
+
const script = `<script>window.__AKAN_MOBILE_TARGET__=${JSON.stringify({
|
|
904
|
+
name: this.target.name,
|
|
905
|
+
basePath,
|
|
906
|
+
indexPath: this.target.indexPath,
|
|
907
|
+
})};</script>`;
|
|
902
908
|
if (html.includes("window.__AKAN_MOBILE_TARGET__")) return html;
|
|
903
909
|
return html.replace(/<\/head\s*>/i, `${script}\n</head>`);
|
|
904
910
|
}
|
|
@@ -977,14 +983,23 @@ export class CapacitorApp {
|
|
|
977
983
|
else if (permission === "push") await this.addPush();
|
|
978
984
|
}
|
|
979
985
|
}
|
|
980
|
-
async #
|
|
981
|
-
const
|
|
982
|
-
if (!
|
|
983
|
-
const schemes =
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
986
|
+
async #applyDeepLinks(platform: "ios" | "android", { operation, env }: Pick<RunConfig, "operation" | "env">) {
|
|
987
|
+
const deepLinks = this.target.deepLinks;
|
|
988
|
+
if (!deepLinks) return;
|
|
989
|
+
const schemes = deepLinks.schemes ?? [];
|
|
990
|
+
const domains = deepLinks.domains ?? [];
|
|
991
|
+
if (domains.length > 0) this.#assertDeepLinkVerificationConfig(platform, { operation, env });
|
|
992
|
+
if (platform === "ios") {
|
|
993
|
+
if (schemes.length > 0) {
|
|
994
|
+
await this.#setPermissionInIos({
|
|
995
|
+
appTransportSecurity: "",
|
|
996
|
+
});
|
|
997
|
+
await this.#setUrlSchemesInIos(schemes);
|
|
998
|
+
}
|
|
999
|
+
if (domains.length > 0) await this.#setAssociatedDomainsInIos(domains);
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
if (platform === "android") {
|
|
988
1003
|
for (const scheme of schemes) {
|
|
989
1004
|
this.project.android
|
|
990
1005
|
.getAndroidManifest()
|
|
@@ -993,18 +1008,30 @@ export class CapacitorApp {
|
|
|
993
1008
|
`<intent-filter><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" /><data android:scheme="${scheme}" /></intent-filter>`,
|
|
994
1009
|
);
|
|
995
1010
|
}
|
|
1011
|
+
for (const domain of domains) {
|
|
1012
|
+
const pathPrefix = resolveMobilePath(this.target, "/");
|
|
1013
|
+
this.project.android
|
|
1014
|
+
.getAndroidManifest()
|
|
1015
|
+
.injectFragment(
|
|
1016
|
+
"activity",
|
|
1017
|
+
`<intent-filter android:autoVerify="true"><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" /><data android:scheme="https" android:host="${domain}" android:pathPrefix="${pathPrefix}" /></intent-filter>`,
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
await this.#setDeepLinksInAndroid(schemes, domains);
|
|
996
1021
|
}
|
|
997
|
-
|
|
998
|
-
|
|
1022
|
+
}
|
|
1023
|
+
#assertDeepLinkVerificationConfig(platform: "ios" | "android", { operation, env }: Pick<RunConfig, "operation" | "env">) {
|
|
1024
|
+
const deepLinks = this.target.deepLinks;
|
|
1025
|
+
if (!deepLinks?.domains?.length) return;
|
|
1026
|
+
if (platform === "ios" && !deepLinks.ios?.teamId) {
|
|
1027
|
+
throw new Error(
|
|
1028
|
+
`Mobile target '${this.target.name}' uses deepLinks.domains but is missing deepLinks.ios.teamId in apps/${this.app.name}/akan.config.ts`,
|
|
1029
|
+
);
|
|
999
1030
|
}
|
|
1000
|
-
|
|
1001
|
-
const
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
.injectFragment(
|
|
1005
|
-
"activity",
|
|
1006
|
-
`<intent-filter android:autoVerify="true"><action android:name="android.intent.action.VIEW" /><category android:name="android.intent.category.DEFAULT" /><category android:name="android.intent.category.BROWSABLE" /><data android:scheme="https" android:host="${host}" android:pathPrefix="${pathPrefix}" /></intent-filter>`,
|
|
1007
|
-
);
|
|
1031
|
+
if (platform === "android" && !deepLinks.android?.sha256CertFingerprints?.length) {
|
|
1032
|
+
const message = `Mobile target '${this.target.name}' uses deepLinks.domains but is missing deepLinks.android.sha256CertFingerprints in apps/${this.app.name}/akan.config.ts`;
|
|
1033
|
+
if (operation === "release" || env === "main") throw new Error(message);
|
|
1034
|
+
this.app.logger.warn(message);
|
|
1008
1035
|
}
|
|
1009
1036
|
}
|
|
1010
1037
|
async #commandEnv(operation: "local" | "release", env: "local" | "debug" | "develop" | "main") {
|
|
@@ -1024,6 +1051,7 @@ export class CapacitorApp {
|
|
|
1024
1051
|
const port = commandEnv.AKAN_PUBLIC_CLIENT_PORT ?? commandEnv.PORT ?? "8282";
|
|
1025
1052
|
const params = new URLSearchParams({ csr: "true", akanMobileTarget: this.target.name });
|
|
1026
1053
|
if (basePath) params.set("akanMobileBasePath", basePath);
|
|
1054
|
+
if (this.target.indexPath) params.set("akanMobileIndexPath", this.target.indexPath);
|
|
1027
1055
|
return `http://${ip}:${port}/${pathname}?${params}`;
|
|
1028
1056
|
}
|
|
1029
1057
|
async #clearRootCapacitorConfigs() {
|
|
@@ -1090,6 +1118,96 @@ export class CapacitorApp {
|
|
|
1090
1118
|
this.project.ios.updateInfoPlist(this.iosTargetName, "Release", updateNs),
|
|
1091
1119
|
]);
|
|
1092
1120
|
}
|
|
1121
|
+
async #setUrlSchemesInIos(schemes: string[]) {
|
|
1122
|
+
const urlTypes = schemes.map((scheme) => ({
|
|
1123
|
+
CFBundleURLName: this.target.appId,
|
|
1124
|
+
CFBundleURLSchemes: [scheme],
|
|
1125
|
+
}));
|
|
1126
|
+
await Promise.all([
|
|
1127
|
+
this.project.ios.updateInfoPlist(this.iosTargetName, "Debug", { CFBundleURLTypes: urlTypes }),
|
|
1128
|
+
this.project.ios.updateInfoPlist(this.iosTargetName, "Release", { CFBundleURLTypes: urlTypes }),
|
|
1129
|
+
]);
|
|
1130
|
+
}
|
|
1131
|
+
async #setAssociatedDomainsInIos(domains: string[]) {
|
|
1132
|
+
const entitlementsRelPath = "App/App.entitlements";
|
|
1133
|
+
const entitlementsPath = path.join(this.app.cwdPath, this.iosProjectPath, entitlementsRelPath);
|
|
1134
|
+
const values = domains.map((domain) => `applinks:${domain}`);
|
|
1135
|
+
const body = [
|
|
1136
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
1137
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
1138
|
+
'<plist version="1.0">',
|
|
1139
|
+
"<dict>",
|
|
1140
|
+
" <key>com.apple.developer.associated-domains</key>",
|
|
1141
|
+
" <array>",
|
|
1142
|
+
...values.map((value) => ` <string>${value}</string>`),
|
|
1143
|
+
" </array>",
|
|
1144
|
+
"</dict>",
|
|
1145
|
+
"</plist>",
|
|
1146
|
+
"",
|
|
1147
|
+
].join("\n");
|
|
1148
|
+
await writeFile(entitlementsPath, body);
|
|
1149
|
+
await this.#setCodeSignEntitlementsInIos(entitlementsRelPath);
|
|
1150
|
+
}
|
|
1151
|
+
async #setCodeSignEntitlementsInIos(entitlementsRelPath: string) {
|
|
1152
|
+
const pbxprojPath = path.join(this.app.cwdPath, this.iosProjectPath, "App.xcodeproj/project.pbxproj");
|
|
1153
|
+
const lines = (await readFile(pbxprojPath, "utf8")).split("\n");
|
|
1154
|
+
let changed = false;
|
|
1155
|
+
for (let index = 0; index < lines.length; index++) {
|
|
1156
|
+
const line = lines[index] ?? "";
|
|
1157
|
+
if (!line.includes(`PRODUCT_BUNDLE_IDENTIFIER = ${this.target.appId};`)) continue;
|
|
1158
|
+
let start = index;
|
|
1159
|
+
while (start >= 0 && !lines[start]?.includes("buildSettings = {")) start--;
|
|
1160
|
+
let end = index;
|
|
1161
|
+
while (end < lines.length && !/^\s*\};\s*$/.test(lines[end] ?? "")) end++;
|
|
1162
|
+
const settings = lines.slice(start, end + 1);
|
|
1163
|
+
if (settings.some((setting) => setting.includes("CODE_SIGN_ENTITLEMENTS"))) continue;
|
|
1164
|
+
const indent = line.match(/^\s*/)?.[0] ?? "";
|
|
1165
|
+
lines.splice(index, 0, `${indent}CODE_SIGN_ENTITLEMENTS = ${entitlementsRelPath};`);
|
|
1166
|
+
index++;
|
|
1167
|
+
changed = true;
|
|
1168
|
+
}
|
|
1169
|
+
if (changed) await writeFile(pbxprojPath, lines.join("\n"));
|
|
1170
|
+
}
|
|
1171
|
+
async #setUrlSchemesInAndroid(schemes: string[]) {
|
|
1172
|
+
const manifestPath = path.join(this.app.cwdPath, this.androidRootPath, "app/src/main/AndroidManifest.xml");
|
|
1173
|
+
let manifest = await readFile(manifestPath, "utf8");
|
|
1174
|
+
let changed = false;
|
|
1175
|
+
for (const scheme of schemes) {
|
|
1176
|
+
if (manifest.includes(`android:scheme="${scheme}"`)) continue;
|
|
1177
|
+
const filter = [
|
|
1178
|
+
" <intent-filter>",
|
|
1179
|
+
' <action android:name="android.intent.action.VIEW" />',
|
|
1180
|
+
' <category android:name="android.intent.category.DEFAULT" />',
|
|
1181
|
+
' <category android:name="android.intent.category.BROWSABLE" />',
|
|
1182
|
+
` <data android:scheme="${scheme}" />`,
|
|
1183
|
+
" </intent-filter>",
|
|
1184
|
+
].join("\n");
|
|
1185
|
+
manifest = manifest.replace(/(\s*<\/activity>)/, `\n${filter}$1`);
|
|
1186
|
+
changed = true;
|
|
1187
|
+
}
|
|
1188
|
+
if (changed) await writeFile(manifestPath, manifest);
|
|
1189
|
+
}
|
|
1190
|
+
async #setDeepLinksInAndroid(schemes: string[], domains: string[]) {
|
|
1191
|
+
await this.#setUrlSchemesInAndroid(schemes);
|
|
1192
|
+
const manifestPath = path.join(this.app.cwdPath, this.androidRootPath, "app/src/main/AndroidManifest.xml");
|
|
1193
|
+
let manifest = await readFile(manifestPath, "utf8");
|
|
1194
|
+
let changed = false;
|
|
1195
|
+
const pathPrefix = resolveMobilePath(this.target, "/");
|
|
1196
|
+
for (const domain of domains) {
|
|
1197
|
+
if (manifest.includes(`android:host="${domain}"`) && manifest.includes('android:scheme="https"')) continue;
|
|
1198
|
+
const filter = [
|
|
1199
|
+
' <intent-filter android:autoVerify="true">',
|
|
1200
|
+
' <action android:name="android.intent.action.VIEW" />',
|
|
1201
|
+
' <category android:name="android.intent.category.DEFAULT" />',
|
|
1202
|
+
' <category android:name="android.intent.category.BROWSABLE" />',
|
|
1203
|
+
` <data android:scheme="https" android:host="${domain}" android:pathPrefix="${pathPrefix}" />`,
|
|
1204
|
+
" </intent-filter>",
|
|
1205
|
+
].join("\n");
|
|
1206
|
+
manifest = manifest.replace(/(\s*<\/activity>)/, `\n${filter}$1`);
|
|
1207
|
+
changed = true;
|
|
1208
|
+
}
|
|
1209
|
+
if (changed) await writeFile(manifestPath, manifest);
|
|
1210
|
+
}
|
|
1093
1211
|
#setFeaturesInAndroid(features: string[]) {
|
|
1094
1212
|
for (const feature of features) {
|
|
1095
1213
|
if (this.#hasFeatureInAndroid(feature)) {
|
|
@@ -77,6 +77,15 @@ export class SsrBaseArtifactBuilder {
|
|
|
77
77
|
branches: [...akanConfig.branches],
|
|
78
78
|
i18n: akanConfig.i18n,
|
|
79
79
|
imageConfig: akanConfig.images,
|
|
80
|
+
deepLinkAssociations: Object.values(akanConfig.mobile.targets)
|
|
81
|
+
.filter((target) => (target.deepLinks?.domains?.length ?? 0) > 0)
|
|
82
|
+
.map((target) => ({
|
|
83
|
+
targetName: target.name,
|
|
84
|
+
appId: target.appId,
|
|
85
|
+
domains: target.deepLinks?.domains ?? [],
|
|
86
|
+
iosTeamId: target.deepLinks?.ios?.teamId,
|
|
87
|
+
androidSha256CertFingerprints: target.deepLinks?.android?.sha256CertFingerprints,
|
|
88
|
+
})),
|
|
80
89
|
};
|
|
81
90
|
await Bun.write(path.join(this.#absArtifactDir, "base-artifact.json"), `${JSON.stringify(artifact, null, 2)}\n`);
|
|
82
91
|
this.#app.verbose(`[base-artifact] complete in ${Date.now() - this.#started}ms`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akanjs/devkit",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.10-rc.0",
|
|
4
4
|
"sourceType": "module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"@langchain/openai": "^1.4.6",
|
|
33
33
|
"@tailwindcss/node": "^4.3.0",
|
|
34
34
|
"@trapezedev/project": "^7.1.4",
|
|
35
|
-
"akanjs": "2.3.
|
|
35
|
+
"akanjs": "2.3.10-rc.0",
|
|
36
36
|
"chalk": "^5.6.2",
|
|
37
37
|
"commander": "^14.0.3",
|
|
38
38
|
"daisyui": "5.5.23",
|