@akanjs/cli 2.3.9 → 2.3.10-rc.1
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/incrementalBuilder.proc.js +226 -34
- package/index.js +289 -40
- package/package.json +2 -2
- package/templates/workspaceRoot/CLAUDE.md.template +3 -0
|
@@ -744,6 +744,44 @@ var DEFAULT_AKAN_IMAGE_CONFIG = {
|
|
|
744
744
|
fetchTimeoutMs: 7000,
|
|
745
745
|
maxRemoteBytes: 25 * 1024 * 1024
|
|
746
746
|
};
|
|
747
|
+
var normalizeIndexPath = (indexPath) => {
|
|
748
|
+
const normalized = indexPath?.trim();
|
|
749
|
+
if (!normalized)
|
|
750
|
+
return;
|
|
751
|
+
const path2 = `/${normalized.replace(/^\/+|\/+$/g, "")}`;
|
|
752
|
+
return path2 === "/" ? "/" : path2;
|
|
753
|
+
};
|
|
754
|
+
var normalizeStringList = (values) => {
|
|
755
|
+
const normalized = values?.map((value) => value.trim()).filter(Boolean) ?? [];
|
|
756
|
+
return normalized.length > 0 ? [...new Set(normalized)] : undefined;
|
|
757
|
+
};
|
|
758
|
+
var normalizeDeepLinkDomain = (domain) => {
|
|
759
|
+
const normalized = domain.trim();
|
|
760
|
+
if (!normalized)
|
|
761
|
+
return "";
|
|
762
|
+
try {
|
|
763
|
+
const url = new URL(normalized.includes("://") ? normalized : `https://${normalized}`);
|
|
764
|
+
return url.host.toLowerCase();
|
|
765
|
+
} catch {
|
|
766
|
+
return normalized.replace(/^https?:\/\//, "").replace(/\/+$/g, "").toLowerCase();
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
var normalizeDeepLinks = (deepLinks) => {
|
|
770
|
+
if (!deepLinks)
|
|
771
|
+
return;
|
|
772
|
+
const schemes = normalizeStringList(deepLinks.schemes);
|
|
773
|
+
const domains = normalizeStringList(deepLinks.domains?.map(normalizeDeepLinkDomain));
|
|
774
|
+
const teamId = deepLinks.ios?.teamId?.trim();
|
|
775
|
+
const sha256CertFingerprints = normalizeStringList(deepLinks.android?.sha256CertFingerprints);
|
|
776
|
+
if (!schemes && !domains && !teamId && !sha256CertFingerprints)
|
|
777
|
+
return;
|
|
778
|
+
return {
|
|
779
|
+
...schemes ? { schemes } : {},
|
|
780
|
+
...domains ? { domains } : {},
|
|
781
|
+
...teamId ? { ios: { teamId } } : {},
|
|
782
|
+
...sha256CertFingerprints ? { android: { sha256CertFingerprints } } : {}
|
|
783
|
+
};
|
|
784
|
+
};
|
|
747
785
|
|
|
748
786
|
class AkanAppConfig {
|
|
749
787
|
app;
|
|
@@ -757,6 +795,7 @@ class AkanAppConfig {
|
|
|
757
795
|
i18n;
|
|
758
796
|
publicEnv;
|
|
759
797
|
mobile;
|
|
798
|
+
secrets;
|
|
760
799
|
baseDevEnv;
|
|
761
800
|
libs;
|
|
762
801
|
domains = new Set;
|
|
@@ -783,11 +822,16 @@ class AkanAppConfig {
|
|
|
783
822
|
process.env.AKAN_PUBLIC_DEFAULT_LOCALE = this.i18n.defaultLocale;
|
|
784
823
|
process.env.AKAN_PUBLIC_LOCALES = this.i18n.locales.join(",");
|
|
785
824
|
this.publicEnv = config?.publicEnv ?? [];
|
|
825
|
+
this.secrets = config?.secrets ?? [];
|
|
786
826
|
this.mobile = this.#resolveMobileConfig(config.mobile);
|
|
787
827
|
this.docker = this.#makeDockerContent(config?.docker ?? {});
|
|
788
828
|
}
|
|
789
829
|
#resolveMobileConfig(mobile) {
|
|
790
|
-
const {
|
|
830
|
+
const {
|
|
831
|
+
targets: rawTargets,
|
|
832
|
+
indexPath: _indexPath,
|
|
833
|
+
...rawMobile
|
|
834
|
+
} = mobile ?? {};
|
|
791
835
|
const appName = rawMobile.appName ?? this.app.name;
|
|
792
836
|
const appId = rawMobile.appId ?? `com.${this.app.name}.app`;
|
|
793
837
|
const version = rawMobile.version ?? "0.0.1";
|
|
@@ -800,6 +844,8 @@ class AkanAppConfig {
|
|
|
800
844
|
const target = rawTarget;
|
|
801
845
|
const fallbackBasePath = !rawTargets && this.basePaths.has(name) ? name : undefined;
|
|
802
846
|
const basePath2 = (target.basePath ?? fallbackBasePath)?.replace(/^\/+|\/+$/g, "") || undefined;
|
|
847
|
+
const indexPath = normalizeIndexPath(target.indexPath);
|
|
848
|
+
const deepLinks = normalizeDeepLinks(target.deepLinks);
|
|
803
849
|
if (basePath2 && !this.basePaths.has(basePath2)) {
|
|
804
850
|
throw new Error(`Mobile target '${name}' uses unknown basePath '${basePath2}' in apps/${this.app.name}/akan.config.ts`);
|
|
805
851
|
}
|
|
@@ -808,6 +854,8 @@ class AkanAppConfig {
|
|
|
808
854
|
...target,
|
|
809
855
|
name,
|
|
810
856
|
basePath: basePath2,
|
|
857
|
+
indexPath,
|
|
858
|
+
deepLinks,
|
|
811
859
|
appName: target.appName ?? appName,
|
|
812
860
|
appId: target.appId ?? appId,
|
|
813
861
|
version: target.version ?? version,
|
|
@@ -2746,8 +2794,8 @@ class Executor {
|
|
|
2746
2794
|
return filePath;
|
|
2747
2795
|
if (filePath.startsWith("."))
|
|
2748
2796
|
return path7.join(this.cwdPath, filePath);
|
|
2749
|
-
const baseParts = this.cwdPath.split(
|
|
2750
|
-
const targetParts = filePath.split(
|
|
2797
|
+
const baseParts = this.cwdPath.split(/[\\/]/).filter(Boolean);
|
|
2798
|
+
const targetParts = filePath.split(/[\\/]/).filter(Boolean);
|
|
2751
2799
|
let overlapLength = 0;
|
|
2752
2800
|
for (let i = 1;i <= Math.min(baseParts.length, targetParts.length); i++) {
|
|
2753
2801
|
let isOverlap = true;
|
|
@@ -2759,8 +2807,7 @@ class Executor {
|
|
|
2759
2807
|
if (isOverlap)
|
|
2760
2808
|
overlapLength = i;
|
|
2761
2809
|
}
|
|
2762
|
-
|
|
2763
|
-
return result.replace(/\/+/g, "/");
|
|
2810
|
+
return path7.join(this.cwdPath, ...targetParts.slice(overlapLength));
|
|
2764
2811
|
}
|
|
2765
2812
|
async mkdir(dirPath) {
|
|
2766
2813
|
const writePath = this.getPath(dirPath);
|
|
@@ -10225,6 +10272,7 @@ import path24 from "path";
|
|
|
10225
10272
|
// pkgs/@akanjs/devkit/frontendBuild/pagesEntrySourceGenerator.ts
|
|
10226
10273
|
import fs3 from "fs";
|
|
10227
10274
|
import path23 from "path";
|
|
10275
|
+
import { pathToFileURL } from "url";
|
|
10228
10276
|
import ts8 from "typescript";
|
|
10229
10277
|
|
|
10230
10278
|
class PagesEntrySourceGenerator {
|
|
@@ -10237,8 +10285,8 @@ class PagesEntrySourceGenerator {
|
|
|
10237
10285
|
}
|
|
10238
10286
|
generate() {
|
|
10239
10287
|
const lines = this.#pageEntries.map(({ key, moduleAbsPath }) => {
|
|
10240
|
-
const
|
|
10241
|
-
return ` ${JSON.stringify(key)}: () => import(${JSON.stringify(
|
|
10288
|
+
const specifier = pathToFileURL(path23.resolve(moduleAbsPath)).href;
|
|
10289
|
+
return ` ${JSON.stringify(key)}: () => import(${JSON.stringify(specifier)}),`;
|
|
10242
10290
|
});
|
|
10243
10291
|
return `export const pages = {
|
|
10244
10292
|
${lines.join(`
|
|
@@ -10251,8 +10299,8 @@ ${lines.join(`
|
|
|
10251
10299
|
}
|
|
10252
10300
|
generateStatic() {
|
|
10253
10301
|
const imports = this.#pageEntries.map(({ moduleAbsPath }, index) => {
|
|
10254
|
-
const
|
|
10255
|
-
return `import * as page${index} from ${JSON.stringify(
|
|
10302
|
+
const specifier = pathToFileURL(path23.resolve(moduleAbsPath)).href;
|
|
10303
|
+
return `import * as page${index} from ${JSON.stringify(specifier)};`;
|
|
10256
10304
|
});
|
|
10257
10305
|
const entries = this.#pageEntries.map(({ key, moduleAbsPath }, index) => {
|
|
10258
10306
|
const isAsyncDefault = PagesEntrySourceGenerator.#hasAsyncDefaultExport(moduleAbsPath);
|
|
@@ -12116,7 +12164,14 @@ class SsrBaseArtifactBuilder {
|
|
|
12116
12164
|
basePaths: [...akanConfig2.basePaths],
|
|
12117
12165
|
branches: [...akanConfig2.branches],
|
|
12118
12166
|
i18n: akanConfig2.i18n,
|
|
12119
|
-
imageConfig: akanConfig2.images
|
|
12167
|
+
imageConfig: akanConfig2.images,
|
|
12168
|
+
deepLinkAssociations: Object.values(akanConfig2.mobile.targets).filter((target) => (target.deepLinks?.domains?.length ?? 0) > 0).map((target) => ({
|
|
12169
|
+
targetName: target.name,
|
|
12170
|
+
appId: target.appId,
|
|
12171
|
+
domains: target.deepLinks?.domains ?? [],
|
|
12172
|
+
iosTeamId: target.deepLinks?.ios?.teamId,
|
|
12173
|
+
androidSha256CertFingerprints: target.deepLinks?.android?.sha256CertFingerprints
|
|
12174
|
+
}))
|
|
12120
12175
|
};
|
|
12121
12176
|
await Bun.write(path35.join(this.#absArtifactDir, "base-artifact.json"), `${JSON.stringify(artifact, null, 2)}
|
|
12122
12177
|
`);
|
|
@@ -12125,11 +12180,11 @@ class SsrBaseArtifactBuilder {
|
|
|
12125
12180
|
}
|
|
12126
12181
|
async#buildRuntimeClientEntries() {
|
|
12127
12182
|
const akanServerPath = await this.#resolveAkanServerPath();
|
|
12128
|
-
const rscClientEntry =
|
|
12129
|
-
const rscSegmentOutletEntry =
|
|
12183
|
+
const rscClientEntry = path35.resolve(akanServerPath, "rscClient.tsx");
|
|
12184
|
+
const rscSegmentOutletEntry = path35.resolve(akanServerPath, "rscSegmentOutlet.tsx");
|
|
12130
12185
|
const vendorEntries = VENDOR_SPECIFIERS.map((specifier) => ({
|
|
12131
12186
|
specifier,
|
|
12132
|
-
absPath:
|
|
12187
|
+
absPath: path35.resolve(akanServerPath, "vendor", `${specifier.replaceAll("/", "-").replaceAll(".", "-")}.ts`)
|
|
12133
12188
|
}));
|
|
12134
12189
|
const entries = [rscClientEntry, rscSegmentOutletEntry, ...vendorEntries.map((v) => v.absPath)];
|
|
12135
12190
|
const clientBundle = await new ClientEntriesBundler({ app: this.#app, entries, command: this.#command }).bundle();
|
|
@@ -12890,7 +12945,7 @@ class Builder {
|
|
|
12890
12945
|
}
|
|
12891
12946
|
}
|
|
12892
12947
|
// pkgs/@akanjs/devkit/capacitorApp.ts
|
|
12893
|
-
import { cp as cp2, mkdir as mkdir11, rm as rm5 } from "fs/promises";
|
|
12948
|
+
import { cp as cp2, mkdir as mkdir11, readFile as readFile2, rm as rm5, writeFile as writeFile2 } from "fs/promises";
|
|
12894
12949
|
import os from "os";
|
|
12895
12950
|
import path41 from "path";
|
|
12896
12951
|
import { select as select2 } from "@inquirer/prompts";
|
|
@@ -13404,11 +13459,12 @@ function materializeCapacitorConfig(target, { operation, localServerUrl, localIp
|
|
|
13404
13459
|
const {
|
|
13405
13460
|
name,
|
|
13406
13461
|
basePath: _basePath,
|
|
13462
|
+
indexPath: _indexPath,
|
|
13407
13463
|
version: _version,
|
|
13408
13464
|
buildNum: _buildNum,
|
|
13409
13465
|
assets: _assets,
|
|
13410
13466
|
permissions: _permissions,
|
|
13411
|
-
|
|
13467
|
+
deepLinks: _deepLinks,
|
|
13412
13468
|
files: _files,
|
|
13413
13469
|
appId,
|
|
13414
13470
|
appName,
|
|
@@ -13530,7 +13586,7 @@ class CapacitorApp {
|
|
|
13530
13586
|
await this.#prepareExternalFiles("ios");
|
|
13531
13587
|
await this.#applyIosMetadata();
|
|
13532
13588
|
await this.#applyPermissions();
|
|
13533
|
-
await this.#
|
|
13589
|
+
await this.#applyDeepLinks("ios", { operation, env });
|
|
13534
13590
|
await this.project.commit();
|
|
13535
13591
|
await this.#generateAssets({ operation, env });
|
|
13536
13592
|
this.app.verbose(`syncing iOS`);
|
|
@@ -13673,12 +13729,13 @@ ${error.message}`;
|
|
|
13673
13729
|
await this.#prepareExternalFiles("android");
|
|
13674
13730
|
await this.#applyAndroidMetadata();
|
|
13675
13731
|
await this.#applyPermissions();
|
|
13676
|
-
await this.#
|
|
13732
|
+
await this.#applyDeepLinks("android", { operation, env });
|
|
13677
13733
|
await this.project.commit();
|
|
13678
13734
|
await this.#generateAssets({ operation, env });
|
|
13679
13735
|
await this.#ensureAndroidAssetsDir();
|
|
13680
13736
|
await this.#ensureAndroidDebugKeystore();
|
|
13681
13737
|
await this.#spawnMobile("npx", ["cap", "sync", "android"], { operation, env });
|
|
13738
|
+
await this.#setDeepLinksInAndroid(this.target.deepLinks?.schemes ?? [], this.target.deepLinks?.domains ?? []);
|
|
13682
13739
|
}
|
|
13683
13740
|
async#updateAndroidBuildTypes() {
|
|
13684
13741
|
const appGradle = await FileEditor.create(path41.join(this.app.cwdPath, this.androidRootPath, "app/build.gradle"));
|
|
@@ -13811,7 +13868,11 @@ ${error.message}`;
|
|
|
13811
13868
|
}
|
|
13812
13869
|
#injectMobileTargetMeta(html) {
|
|
13813
13870
|
const basePath2 = this.target.basePath?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
13814
|
-
const script = `<script>window.__AKAN_MOBILE_TARGET__=${JSON.stringify({
|
|
13871
|
+
const script = `<script>window.__AKAN_MOBILE_TARGET__=${JSON.stringify({
|
|
13872
|
+
name: this.target.name,
|
|
13873
|
+
basePath: basePath2,
|
|
13874
|
+
indexPath: this.target.indexPath
|
|
13875
|
+
})};</script>`;
|
|
13815
13876
|
if (html.includes("window.__AKAN_MOBILE_TARGET__"))
|
|
13816
13877
|
return html;
|
|
13817
13878
|
return html.replace(/<\/head\s*>/i, `${script}
|
|
@@ -13894,25 +13955,48 @@ ${error.message}`;
|
|
|
13894
13955
|
await this.addPush();
|
|
13895
13956
|
}
|
|
13896
13957
|
}
|
|
13897
|
-
async#
|
|
13898
|
-
const
|
|
13899
|
-
if (!
|
|
13958
|
+
async#applyDeepLinks(platform, { operation, env }) {
|
|
13959
|
+
const deepLinks = this.target.deepLinks;
|
|
13960
|
+
if (!deepLinks)
|
|
13900
13961
|
return;
|
|
13901
|
-
const schemes =
|
|
13902
|
-
|
|
13903
|
-
|
|
13904
|
-
|
|
13905
|
-
|
|
13962
|
+
const schemes = deepLinks.schemes ?? [];
|
|
13963
|
+
const domains = deepLinks.domains ?? [];
|
|
13964
|
+
if (domains.length > 0)
|
|
13965
|
+
this.#assertDeepLinkVerificationConfig(platform, { operation, env });
|
|
13966
|
+
if (platform === "ios") {
|
|
13967
|
+
if (schemes.length > 0) {
|
|
13968
|
+
await this.#setPermissionInIos({
|
|
13969
|
+
appTransportSecurity: ""
|
|
13970
|
+
});
|
|
13971
|
+
await this.#setUrlSchemesInIos(schemes);
|
|
13972
|
+
}
|
|
13973
|
+
if (domains.length > 0)
|
|
13974
|
+
await this.#setAssociatedDomainsInIos(domains);
|
|
13975
|
+
return;
|
|
13976
|
+
}
|
|
13977
|
+
if (platform === "android") {
|
|
13906
13978
|
for (const scheme of schemes) {
|
|
13907
13979
|
this.project.android.getAndroidManifest().injectFragment("activity", `<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>`);
|
|
13908
13980
|
}
|
|
13981
|
+
for (const domain of domains) {
|
|
13982
|
+
const pathPrefix = resolveMobilePath(this.target, "/");
|
|
13983
|
+
this.project.android.getAndroidManifest().injectFragment("activity", `<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>`);
|
|
13984
|
+
}
|
|
13985
|
+
await this.#setDeepLinksInAndroid(schemes, domains);
|
|
13909
13986
|
}
|
|
13910
|
-
|
|
13911
|
-
|
|
13987
|
+
}
|
|
13988
|
+
#assertDeepLinkVerificationConfig(platform, { operation, env }) {
|
|
13989
|
+
const deepLinks = this.target.deepLinks;
|
|
13990
|
+
if (!deepLinks?.domains?.length)
|
|
13991
|
+
return;
|
|
13992
|
+
if (platform === "ios" && !deepLinks.ios?.teamId) {
|
|
13993
|
+
throw new Error(`Mobile target '${this.target.name}' uses deepLinks.domains but is missing deepLinks.ios.teamId in apps/${this.app.name}/akan.config.ts`);
|
|
13912
13994
|
}
|
|
13913
|
-
|
|
13914
|
-
const
|
|
13915
|
-
|
|
13995
|
+
if (platform === "android" && !deepLinks.android?.sha256CertFingerprints?.length) {
|
|
13996
|
+
const message = `Mobile target '${this.target.name}' uses deepLinks.domains but is missing deepLinks.android.sha256CertFingerprints in apps/${this.app.name}/akan.config.ts`;
|
|
13997
|
+
if (operation === "release" || env === "main")
|
|
13998
|
+
throw new Error(message);
|
|
13999
|
+
this.app.logger.warn(message);
|
|
13916
14000
|
}
|
|
13917
14001
|
}
|
|
13918
14002
|
async#commandEnv(operation, env) {
|
|
@@ -13933,6 +14017,8 @@ ${error.message}`;
|
|
|
13933
14017
|
const params = new URLSearchParams({ csr: "true", akanMobileTarget: this.target.name });
|
|
13934
14018
|
if (basePath2)
|
|
13935
14019
|
params.set("akanMobileBasePath", basePath2);
|
|
14020
|
+
if (this.target.indexPath)
|
|
14021
|
+
params.set("akanMobileIndexPath", this.target.indexPath);
|
|
13936
14022
|
return `http://${ip}:${port}/${pathname}?${params}`;
|
|
13937
14023
|
}
|
|
13938
14024
|
async#clearRootCapacitorConfigs() {
|
|
@@ -13992,6 +14078,112 @@ ${error.message}`;
|
|
|
13992
14078
|
this.project.ios.updateInfoPlist(this.iosTargetName, "Release", updateNs)
|
|
13993
14079
|
]);
|
|
13994
14080
|
}
|
|
14081
|
+
async#setUrlSchemesInIos(schemes) {
|
|
14082
|
+
const urlTypes = schemes.map((scheme) => ({
|
|
14083
|
+
CFBundleURLName: this.target.appId,
|
|
14084
|
+
CFBundleURLSchemes: [scheme]
|
|
14085
|
+
}));
|
|
14086
|
+
await Promise.all([
|
|
14087
|
+
this.project.ios.updateInfoPlist(this.iosTargetName, "Debug", { CFBundleURLTypes: urlTypes }),
|
|
14088
|
+
this.project.ios.updateInfoPlist(this.iosTargetName, "Release", { CFBundleURLTypes: urlTypes })
|
|
14089
|
+
]);
|
|
14090
|
+
}
|
|
14091
|
+
async#setAssociatedDomainsInIos(domains) {
|
|
14092
|
+
const entitlementsRelPath = "App/App.entitlements";
|
|
14093
|
+
const entitlementsPath = path41.join(this.app.cwdPath, this.iosProjectPath, entitlementsRelPath);
|
|
14094
|
+
const values = domains.map((domain) => `applinks:${domain}`);
|
|
14095
|
+
const body = [
|
|
14096
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
14097
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
14098
|
+
'<plist version="1.0">',
|
|
14099
|
+
"<dict>",
|
|
14100
|
+
" <key>com.apple.developer.associated-domains</key>",
|
|
14101
|
+
" <array>",
|
|
14102
|
+
...values.map((value) => ` <string>${value}</string>`),
|
|
14103
|
+
" </array>",
|
|
14104
|
+
"</dict>",
|
|
14105
|
+
"</plist>",
|
|
14106
|
+
""
|
|
14107
|
+
].join(`
|
|
14108
|
+
`);
|
|
14109
|
+
await writeFile2(entitlementsPath, body);
|
|
14110
|
+
await this.#setCodeSignEntitlementsInIos(entitlementsRelPath);
|
|
14111
|
+
}
|
|
14112
|
+
async#setCodeSignEntitlementsInIos(entitlementsRelPath) {
|
|
14113
|
+
const pbxprojPath = path41.join(this.app.cwdPath, this.iosProjectPath, "App.xcodeproj/project.pbxproj");
|
|
14114
|
+
const lines = (await readFile2(pbxprojPath, "utf8")).split(`
|
|
14115
|
+
`);
|
|
14116
|
+
let changed = false;
|
|
14117
|
+
for (let index = 0;index < lines.length; index++) {
|
|
14118
|
+
const line = lines[index] ?? "";
|
|
14119
|
+
if (!line.includes(`PRODUCT_BUNDLE_IDENTIFIER = ${this.target.appId};`))
|
|
14120
|
+
continue;
|
|
14121
|
+
let start = index;
|
|
14122
|
+
while (start >= 0 && !lines[start]?.includes("buildSettings = {"))
|
|
14123
|
+
start--;
|
|
14124
|
+
let end = index;
|
|
14125
|
+
while (end < lines.length && !/^\s*\};\s*$/.test(lines[end] ?? ""))
|
|
14126
|
+
end++;
|
|
14127
|
+
const settings = lines.slice(start, end + 1);
|
|
14128
|
+
if (settings.some((setting) => setting.includes("CODE_SIGN_ENTITLEMENTS")))
|
|
14129
|
+
continue;
|
|
14130
|
+
const indent = line.match(/^\s*/)?.[0] ?? "";
|
|
14131
|
+
lines.splice(index, 0, `${indent}CODE_SIGN_ENTITLEMENTS = ${entitlementsRelPath};`);
|
|
14132
|
+
index++;
|
|
14133
|
+
changed = true;
|
|
14134
|
+
}
|
|
14135
|
+
if (changed)
|
|
14136
|
+
await writeFile2(pbxprojPath, lines.join(`
|
|
14137
|
+
`));
|
|
14138
|
+
}
|
|
14139
|
+
async#setUrlSchemesInAndroid(schemes) {
|
|
14140
|
+
const manifestPath = path41.join(this.app.cwdPath, this.androidRootPath, "app/src/main/AndroidManifest.xml");
|
|
14141
|
+
let manifest = await readFile2(manifestPath, "utf8");
|
|
14142
|
+
let changed = false;
|
|
14143
|
+
for (const scheme of schemes) {
|
|
14144
|
+
if (manifest.includes(`android:scheme="${scheme}"`))
|
|
14145
|
+
continue;
|
|
14146
|
+
const filter = [
|
|
14147
|
+
" <intent-filter>",
|
|
14148
|
+
' <action android:name="android.intent.action.VIEW" />',
|
|
14149
|
+
' <category android:name="android.intent.category.DEFAULT" />',
|
|
14150
|
+
' <category android:name="android.intent.category.BROWSABLE" />',
|
|
14151
|
+
` <data android:scheme="${scheme}" />`,
|
|
14152
|
+
" </intent-filter>"
|
|
14153
|
+
].join(`
|
|
14154
|
+
`);
|
|
14155
|
+
manifest = manifest.replace(/(\s*<\/activity>)/, `
|
|
14156
|
+
${filter}$1`);
|
|
14157
|
+
changed = true;
|
|
14158
|
+
}
|
|
14159
|
+
if (changed)
|
|
14160
|
+
await writeFile2(manifestPath, manifest);
|
|
14161
|
+
}
|
|
14162
|
+
async#setDeepLinksInAndroid(schemes, domains) {
|
|
14163
|
+
await this.#setUrlSchemesInAndroid(schemes);
|
|
14164
|
+
const manifestPath = path41.join(this.app.cwdPath, this.androidRootPath, "app/src/main/AndroidManifest.xml");
|
|
14165
|
+
let manifest = await readFile2(manifestPath, "utf8");
|
|
14166
|
+
let changed = false;
|
|
14167
|
+
const pathPrefix = resolveMobilePath(this.target, "/");
|
|
14168
|
+
for (const domain of domains) {
|
|
14169
|
+
if (manifest.includes(`android:host="${domain}"`) && manifest.includes('android:scheme="https"'))
|
|
14170
|
+
continue;
|
|
14171
|
+
const filter = [
|
|
14172
|
+
' <intent-filter android:autoVerify="true">',
|
|
14173
|
+
' <action android:name="android.intent.action.VIEW" />',
|
|
14174
|
+
' <category android:name="android.intent.category.DEFAULT" />',
|
|
14175
|
+
' <category android:name="android.intent.category.BROWSABLE" />',
|
|
14176
|
+
` <data android:scheme="https" android:host="${domain}" android:pathPrefix="${pathPrefix}" />`,
|
|
14177
|
+
" </intent-filter>"
|
|
14178
|
+
].join(`
|
|
14179
|
+
`);
|
|
14180
|
+
manifest = manifest.replace(/(\s*<\/activity>)/, `
|
|
14181
|
+
${filter}$1`);
|
|
14182
|
+
changed = true;
|
|
14183
|
+
}
|
|
14184
|
+
if (changed)
|
|
14185
|
+
await writeFile2(manifestPath, manifest);
|
|
14186
|
+
}
|
|
13995
14187
|
#setFeaturesInAndroid(features) {
|
|
13996
14188
|
for (const feature of features) {
|
|
13997
14189
|
if (this.#hasFeatureInAndroid(feature)) {
|
|
@@ -15074,7 +15266,7 @@ ${document}
|
|
|
15074
15266
|
}
|
|
15075
15267
|
// pkgs/@akanjs/devkit/qualityScanner.ts
|
|
15076
15268
|
import { createHash } from "crypto";
|
|
15077
|
-
import { readdir as readdir3, readFile as
|
|
15269
|
+
import { readdir as readdir3, readFile as readFile3, stat as stat4 } from "fs/promises";
|
|
15078
15270
|
import path43 from "path";
|
|
15079
15271
|
import ignore2 from "ignore";
|
|
15080
15272
|
import ts11 from "typescript";
|
|
@@ -15163,7 +15355,7 @@ class AkanQualityScanner {
|
|
|
15163
15355
|
const gitIgnorePath = path43.join(workspaceRoot, ".gitignore");
|
|
15164
15356
|
if (!await Bun.file(gitIgnorePath).exists())
|
|
15165
15357
|
return [];
|
|
15166
|
-
return (await
|
|
15358
|
+
return (await readFile3(gitIgnorePath, "utf8")).split(/\r?\n/);
|
|
15167
15359
|
}
|
|
15168
15360
|
async#walkTargetFiles(workspaceRoot, currentPath, ignoreFilter, files) {
|
|
15169
15361
|
const entries = await readdir3(currentPath, { withFileTypes: true });
|
|
@@ -15183,7 +15375,7 @@ class AkanQualityScanner {
|
|
|
15183
15375
|
}
|
|
15184
15376
|
async#readSourceFile(workspaceRoot, file) {
|
|
15185
15377
|
const absolutePath = path43.join(workspaceRoot, file);
|
|
15186
|
-
const content = await
|
|
15378
|
+
const content = await readFile3(absolutePath, "utf8");
|
|
15187
15379
|
return {
|
|
15188
15380
|
file,
|
|
15189
15381
|
absolutePath,
|
package/index.js
CHANGED
|
@@ -742,6 +742,44 @@ var DEFAULT_AKAN_IMAGE_CONFIG = {
|
|
|
742
742
|
fetchTimeoutMs: 7000,
|
|
743
743
|
maxRemoteBytes: 25 * 1024 * 1024
|
|
744
744
|
};
|
|
745
|
+
var normalizeIndexPath = (indexPath) => {
|
|
746
|
+
const normalized = indexPath?.trim();
|
|
747
|
+
if (!normalized)
|
|
748
|
+
return;
|
|
749
|
+
const path2 = `/${normalized.replace(/^\/+|\/+$/g, "")}`;
|
|
750
|
+
return path2 === "/" ? "/" : path2;
|
|
751
|
+
};
|
|
752
|
+
var normalizeStringList = (values) => {
|
|
753
|
+
const normalized = values?.map((value) => value.trim()).filter(Boolean) ?? [];
|
|
754
|
+
return normalized.length > 0 ? [...new Set(normalized)] : undefined;
|
|
755
|
+
};
|
|
756
|
+
var normalizeDeepLinkDomain = (domain) => {
|
|
757
|
+
const normalized = domain.trim();
|
|
758
|
+
if (!normalized)
|
|
759
|
+
return "";
|
|
760
|
+
try {
|
|
761
|
+
const url = new URL(normalized.includes("://") ? normalized : `https://${normalized}`);
|
|
762
|
+
return url.host.toLowerCase();
|
|
763
|
+
} catch {
|
|
764
|
+
return normalized.replace(/^https?:\/\//, "").replace(/\/+$/g, "").toLowerCase();
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
var normalizeDeepLinks = (deepLinks) => {
|
|
768
|
+
if (!deepLinks)
|
|
769
|
+
return;
|
|
770
|
+
const schemes = normalizeStringList(deepLinks.schemes);
|
|
771
|
+
const domains = normalizeStringList(deepLinks.domains?.map(normalizeDeepLinkDomain));
|
|
772
|
+
const teamId = deepLinks.ios?.teamId?.trim();
|
|
773
|
+
const sha256CertFingerprints = normalizeStringList(deepLinks.android?.sha256CertFingerprints);
|
|
774
|
+
if (!schemes && !domains && !teamId && !sha256CertFingerprints)
|
|
775
|
+
return;
|
|
776
|
+
return {
|
|
777
|
+
...schemes ? { schemes } : {},
|
|
778
|
+
...domains ? { domains } : {},
|
|
779
|
+
...teamId ? { ios: { teamId } } : {},
|
|
780
|
+
...sha256CertFingerprints ? { android: { sha256CertFingerprints } } : {}
|
|
781
|
+
};
|
|
782
|
+
};
|
|
745
783
|
|
|
746
784
|
class AkanAppConfig {
|
|
747
785
|
app;
|
|
@@ -755,6 +793,7 @@ class AkanAppConfig {
|
|
|
755
793
|
i18n;
|
|
756
794
|
publicEnv;
|
|
757
795
|
mobile;
|
|
796
|
+
secrets;
|
|
758
797
|
baseDevEnv;
|
|
759
798
|
libs;
|
|
760
799
|
domains = new Set;
|
|
@@ -781,11 +820,16 @@ class AkanAppConfig {
|
|
|
781
820
|
process.env.AKAN_PUBLIC_DEFAULT_LOCALE = this.i18n.defaultLocale;
|
|
782
821
|
process.env.AKAN_PUBLIC_LOCALES = this.i18n.locales.join(",");
|
|
783
822
|
this.publicEnv = config?.publicEnv ?? [];
|
|
823
|
+
this.secrets = config?.secrets ?? [];
|
|
784
824
|
this.mobile = this.#resolveMobileConfig(config.mobile);
|
|
785
825
|
this.docker = this.#makeDockerContent(config?.docker ?? {});
|
|
786
826
|
}
|
|
787
827
|
#resolveMobileConfig(mobile) {
|
|
788
|
-
const {
|
|
828
|
+
const {
|
|
829
|
+
targets: rawTargets,
|
|
830
|
+
indexPath: _indexPath,
|
|
831
|
+
...rawMobile
|
|
832
|
+
} = mobile ?? {};
|
|
789
833
|
const appName = rawMobile.appName ?? this.app.name;
|
|
790
834
|
const appId = rawMobile.appId ?? `com.${this.app.name}.app`;
|
|
791
835
|
const version = rawMobile.version ?? "0.0.1";
|
|
@@ -798,6 +842,8 @@ class AkanAppConfig {
|
|
|
798
842
|
const target = rawTarget;
|
|
799
843
|
const fallbackBasePath = !rawTargets && this.basePaths.has(name) ? name : undefined;
|
|
800
844
|
const basePath2 = (target.basePath ?? fallbackBasePath)?.replace(/^\/+|\/+$/g, "") || undefined;
|
|
845
|
+
const indexPath = normalizeIndexPath(target.indexPath);
|
|
846
|
+
const deepLinks = normalizeDeepLinks(target.deepLinks);
|
|
801
847
|
if (basePath2 && !this.basePaths.has(basePath2)) {
|
|
802
848
|
throw new Error(`Mobile target '${name}' uses unknown basePath '${basePath2}' in apps/${this.app.name}/akan.config.ts`);
|
|
803
849
|
}
|
|
@@ -806,6 +852,8 @@ class AkanAppConfig {
|
|
|
806
852
|
...target,
|
|
807
853
|
name,
|
|
808
854
|
basePath: basePath2,
|
|
855
|
+
indexPath,
|
|
856
|
+
deepLinks,
|
|
809
857
|
appName: target.appName ?? appName,
|
|
810
858
|
appId: target.appId ?? appId,
|
|
811
859
|
version: target.version ?? version,
|
|
@@ -2744,8 +2792,8 @@ class Executor {
|
|
|
2744
2792
|
return filePath;
|
|
2745
2793
|
if (filePath.startsWith("."))
|
|
2746
2794
|
return path7.join(this.cwdPath, filePath);
|
|
2747
|
-
const baseParts = this.cwdPath.split(
|
|
2748
|
-
const targetParts = filePath.split(
|
|
2795
|
+
const baseParts = this.cwdPath.split(/[\\/]/).filter(Boolean);
|
|
2796
|
+
const targetParts = filePath.split(/[\\/]/).filter(Boolean);
|
|
2749
2797
|
let overlapLength = 0;
|
|
2750
2798
|
for (let i = 1;i <= Math.min(baseParts.length, targetParts.length); i++) {
|
|
2751
2799
|
let isOverlap = true;
|
|
@@ -2757,8 +2805,7 @@ class Executor {
|
|
|
2757
2805
|
if (isOverlap)
|
|
2758
2806
|
overlapLength = i;
|
|
2759
2807
|
}
|
|
2760
|
-
|
|
2761
|
-
return result.replace(/\/+/g, "/");
|
|
2808
|
+
return path7.join(this.cwdPath, ...targetParts.slice(overlapLength));
|
|
2762
2809
|
}
|
|
2763
2810
|
async mkdir(dirPath) {
|
|
2764
2811
|
const writePath = this.getPath(dirPath);
|
|
@@ -10223,6 +10270,7 @@ import path24 from "path";
|
|
|
10223
10270
|
// pkgs/@akanjs/devkit/frontendBuild/pagesEntrySourceGenerator.ts
|
|
10224
10271
|
import fs3 from "fs";
|
|
10225
10272
|
import path23 from "path";
|
|
10273
|
+
import { pathToFileURL } from "url";
|
|
10226
10274
|
import ts8 from "typescript";
|
|
10227
10275
|
|
|
10228
10276
|
class PagesEntrySourceGenerator {
|
|
@@ -10235,8 +10283,8 @@ class PagesEntrySourceGenerator {
|
|
|
10235
10283
|
}
|
|
10236
10284
|
generate() {
|
|
10237
10285
|
const lines = this.#pageEntries.map(({ key, moduleAbsPath }) => {
|
|
10238
|
-
const
|
|
10239
|
-
return ` ${JSON.stringify(key)}: () => import(${JSON.stringify(
|
|
10286
|
+
const specifier = pathToFileURL(path23.resolve(moduleAbsPath)).href;
|
|
10287
|
+
return ` ${JSON.stringify(key)}: () => import(${JSON.stringify(specifier)}),`;
|
|
10240
10288
|
});
|
|
10241
10289
|
return `export const pages = {
|
|
10242
10290
|
${lines.join(`
|
|
@@ -10249,8 +10297,8 @@ ${lines.join(`
|
|
|
10249
10297
|
}
|
|
10250
10298
|
generateStatic() {
|
|
10251
10299
|
const imports = this.#pageEntries.map(({ moduleAbsPath }, index) => {
|
|
10252
|
-
const
|
|
10253
|
-
return `import * as page${index} from ${JSON.stringify(
|
|
10300
|
+
const specifier = pathToFileURL(path23.resolve(moduleAbsPath)).href;
|
|
10301
|
+
return `import * as page${index} from ${JSON.stringify(specifier)};`;
|
|
10254
10302
|
});
|
|
10255
10303
|
const entries = this.#pageEntries.map(({ key, moduleAbsPath }, index) => {
|
|
10256
10304
|
const isAsyncDefault = PagesEntrySourceGenerator.#hasAsyncDefaultExport(moduleAbsPath);
|
|
@@ -12114,7 +12162,14 @@ class SsrBaseArtifactBuilder {
|
|
|
12114
12162
|
basePaths: [...akanConfig2.basePaths],
|
|
12115
12163
|
branches: [...akanConfig2.branches],
|
|
12116
12164
|
i18n: akanConfig2.i18n,
|
|
12117
|
-
imageConfig: akanConfig2.images
|
|
12165
|
+
imageConfig: akanConfig2.images,
|
|
12166
|
+
deepLinkAssociations: Object.values(akanConfig2.mobile.targets).filter((target) => (target.deepLinks?.domains?.length ?? 0) > 0).map((target) => ({
|
|
12167
|
+
targetName: target.name,
|
|
12168
|
+
appId: target.appId,
|
|
12169
|
+
domains: target.deepLinks?.domains ?? [],
|
|
12170
|
+
iosTeamId: target.deepLinks?.ios?.teamId,
|
|
12171
|
+
androidSha256CertFingerprints: target.deepLinks?.android?.sha256CertFingerprints
|
|
12172
|
+
}))
|
|
12118
12173
|
};
|
|
12119
12174
|
await Bun.write(path35.join(this.#absArtifactDir, "base-artifact.json"), `${JSON.stringify(artifact, null, 2)}
|
|
12120
12175
|
`);
|
|
@@ -12123,11 +12178,11 @@ class SsrBaseArtifactBuilder {
|
|
|
12123
12178
|
}
|
|
12124
12179
|
async#buildRuntimeClientEntries() {
|
|
12125
12180
|
const akanServerPath = await this.#resolveAkanServerPath();
|
|
12126
|
-
const rscClientEntry =
|
|
12127
|
-
const rscSegmentOutletEntry =
|
|
12181
|
+
const rscClientEntry = path35.resolve(akanServerPath, "rscClient.tsx");
|
|
12182
|
+
const rscSegmentOutletEntry = path35.resolve(akanServerPath, "rscSegmentOutlet.tsx");
|
|
12128
12183
|
const vendorEntries = VENDOR_SPECIFIERS.map((specifier) => ({
|
|
12129
12184
|
specifier,
|
|
12130
|
-
absPath:
|
|
12185
|
+
absPath: path35.resolve(akanServerPath, "vendor", `${specifier.replaceAll("/", "-").replaceAll(".", "-")}.ts`)
|
|
12131
12186
|
}));
|
|
12132
12187
|
const entries = [rscClientEntry, rscSegmentOutletEntry, ...vendorEntries.map((v) => v.absPath)];
|
|
12133
12188
|
const clientBundle = await new ClientEntriesBundler({ app: this.#app, entries, command: this.#command }).bundle();
|
|
@@ -12888,7 +12943,7 @@ class Builder {
|
|
|
12888
12943
|
}
|
|
12889
12944
|
}
|
|
12890
12945
|
// pkgs/@akanjs/devkit/capacitorApp.ts
|
|
12891
|
-
import { cp as cp2, mkdir as mkdir11, rm as rm5 } from "fs/promises";
|
|
12946
|
+
import { cp as cp2, mkdir as mkdir11, readFile as readFile2, rm as rm5, writeFile as writeFile2 } from "fs/promises";
|
|
12892
12947
|
import os from "os";
|
|
12893
12948
|
import path41 from "path";
|
|
12894
12949
|
import { select as select2 } from "@inquirer/prompts";
|
|
@@ -13402,11 +13457,12 @@ function materializeCapacitorConfig(target, { operation, localServerUrl, localIp
|
|
|
13402
13457
|
const {
|
|
13403
13458
|
name,
|
|
13404
13459
|
basePath: _basePath,
|
|
13460
|
+
indexPath: _indexPath,
|
|
13405
13461
|
version: _version,
|
|
13406
13462
|
buildNum: _buildNum,
|
|
13407
13463
|
assets: _assets,
|
|
13408
13464
|
permissions: _permissions,
|
|
13409
|
-
|
|
13465
|
+
deepLinks: _deepLinks,
|
|
13410
13466
|
files: _files,
|
|
13411
13467
|
appId,
|
|
13412
13468
|
appName,
|
|
@@ -13528,7 +13584,7 @@ class CapacitorApp {
|
|
|
13528
13584
|
await this.#prepareExternalFiles("ios");
|
|
13529
13585
|
await this.#applyIosMetadata();
|
|
13530
13586
|
await this.#applyPermissions();
|
|
13531
|
-
await this.#
|
|
13587
|
+
await this.#applyDeepLinks("ios", { operation, env });
|
|
13532
13588
|
await this.project.commit();
|
|
13533
13589
|
await this.#generateAssets({ operation, env });
|
|
13534
13590
|
this.app.verbose(`syncing iOS`);
|
|
@@ -13671,12 +13727,13 @@ ${error.message}`;
|
|
|
13671
13727
|
await this.#prepareExternalFiles("android");
|
|
13672
13728
|
await this.#applyAndroidMetadata();
|
|
13673
13729
|
await this.#applyPermissions();
|
|
13674
|
-
await this.#
|
|
13730
|
+
await this.#applyDeepLinks("android", { operation, env });
|
|
13675
13731
|
await this.project.commit();
|
|
13676
13732
|
await this.#generateAssets({ operation, env });
|
|
13677
13733
|
await this.#ensureAndroidAssetsDir();
|
|
13678
13734
|
await this.#ensureAndroidDebugKeystore();
|
|
13679
13735
|
await this.#spawnMobile("npx", ["cap", "sync", "android"], { operation, env });
|
|
13736
|
+
await this.#setDeepLinksInAndroid(this.target.deepLinks?.schemes ?? [], this.target.deepLinks?.domains ?? []);
|
|
13680
13737
|
}
|
|
13681
13738
|
async#updateAndroidBuildTypes() {
|
|
13682
13739
|
const appGradle = await FileEditor.create(path41.join(this.app.cwdPath, this.androidRootPath, "app/build.gradle"));
|
|
@@ -13809,7 +13866,11 @@ ${error.message}`;
|
|
|
13809
13866
|
}
|
|
13810
13867
|
#injectMobileTargetMeta(html) {
|
|
13811
13868
|
const basePath2 = this.target.basePath?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
13812
|
-
const script = `<script>window.__AKAN_MOBILE_TARGET__=${JSON.stringify({
|
|
13869
|
+
const script = `<script>window.__AKAN_MOBILE_TARGET__=${JSON.stringify({
|
|
13870
|
+
name: this.target.name,
|
|
13871
|
+
basePath: basePath2,
|
|
13872
|
+
indexPath: this.target.indexPath
|
|
13873
|
+
})};</script>`;
|
|
13813
13874
|
if (html.includes("window.__AKAN_MOBILE_TARGET__"))
|
|
13814
13875
|
return html;
|
|
13815
13876
|
return html.replace(/<\/head\s*>/i, `${script}
|
|
@@ -13892,25 +13953,48 @@ ${error.message}`;
|
|
|
13892
13953
|
await this.addPush();
|
|
13893
13954
|
}
|
|
13894
13955
|
}
|
|
13895
|
-
async#
|
|
13896
|
-
const
|
|
13897
|
-
if (!
|
|
13956
|
+
async#applyDeepLinks(platform, { operation, env }) {
|
|
13957
|
+
const deepLinks = this.target.deepLinks;
|
|
13958
|
+
if (!deepLinks)
|
|
13898
13959
|
return;
|
|
13899
|
-
const schemes =
|
|
13900
|
-
|
|
13901
|
-
|
|
13902
|
-
|
|
13903
|
-
|
|
13960
|
+
const schemes = deepLinks.schemes ?? [];
|
|
13961
|
+
const domains = deepLinks.domains ?? [];
|
|
13962
|
+
if (domains.length > 0)
|
|
13963
|
+
this.#assertDeepLinkVerificationConfig(platform, { operation, env });
|
|
13964
|
+
if (platform === "ios") {
|
|
13965
|
+
if (schemes.length > 0) {
|
|
13966
|
+
await this.#setPermissionInIos({
|
|
13967
|
+
appTransportSecurity: ""
|
|
13968
|
+
});
|
|
13969
|
+
await this.#setUrlSchemesInIos(schemes);
|
|
13970
|
+
}
|
|
13971
|
+
if (domains.length > 0)
|
|
13972
|
+
await this.#setAssociatedDomainsInIos(domains);
|
|
13973
|
+
return;
|
|
13974
|
+
}
|
|
13975
|
+
if (platform === "android") {
|
|
13904
13976
|
for (const scheme of schemes) {
|
|
13905
13977
|
this.project.android.getAndroidManifest().injectFragment("activity", `<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>`);
|
|
13906
13978
|
}
|
|
13979
|
+
for (const domain of domains) {
|
|
13980
|
+
const pathPrefix = resolveMobilePath(this.target, "/");
|
|
13981
|
+
this.project.android.getAndroidManifest().injectFragment("activity", `<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>`);
|
|
13982
|
+
}
|
|
13983
|
+
await this.#setDeepLinksInAndroid(schemes, domains);
|
|
13907
13984
|
}
|
|
13908
|
-
|
|
13909
|
-
|
|
13985
|
+
}
|
|
13986
|
+
#assertDeepLinkVerificationConfig(platform, { operation, env }) {
|
|
13987
|
+
const deepLinks = this.target.deepLinks;
|
|
13988
|
+
if (!deepLinks?.domains?.length)
|
|
13989
|
+
return;
|
|
13990
|
+
if (platform === "ios" && !deepLinks.ios?.teamId) {
|
|
13991
|
+
throw new Error(`Mobile target '${this.target.name}' uses deepLinks.domains but is missing deepLinks.ios.teamId in apps/${this.app.name}/akan.config.ts`);
|
|
13910
13992
|
}
|
|
13911
|
-
|
|
13912
|
-
const
|
|
13913
|
-
|
|
13993
|
+
if (platform === "android" && !deepLinks.android?.sha256CertFingerprints?.length) {
|
|
13994
|
+
const message = `Mobile target '${this.target.name}' uses deepLinks.domains but is missing deepLinks.android.sha256CertFingerprints in apps/${this.app.name}/akan.config.ts`;
|
|
13995
|
+
if (operation === "release" || env === "main")
|
|
13996
|
+
throw new Error(message);
|
|
13997
|
+
this.app.logger.warn(message);
|
|
13914
13998
|
}
|
|
13915
13999
|
}
|
|
13916
14000
|
async#commandEnv(operation, env) {
|
|
@@ -13931,6 +14015,8 @@ ${error.message}`;
|
|
|
13931
14015
|
const params = new URLSearchParams({ csr: "true", akanMobileTarget: this.target.name });
|
|
13932
14016
|
if (basePath2)
|
|
13933
14017
|
params.set("akanMobileBasePath", basePath2);
|
|
14018
|
+
if (this.target.indexPath)
|
|
14019
|
+
params.set("akanMobileIndexPath", this.target.indexPath);
|
|
13934
14020
|
return `http://${ip}:${port}/${pathname}?${params}`;
|
|
13935
14021
|
}
|
|
13936
14022
|
async#clearRootCapacitorConfigs() {
|
|
@@ -13990,6 +14076,112 @@ ${error.message}`;
|
|
|
13990
14076
|
this.project.ios.updateInfoPlist(this.iosTargetName, "Release", updateNs)
|
|
13991
14077
|
]);
|
|
13992
14078
|
}
|
|
14079
|
+
async#setUrlSchemesInIos(schemes) {
|
|
14080
|
+
const urlTypes = schemes.map((scheme) => ({
|
|
14081
|
+
CFBundleURLName: this.target.appId,
|
|
14082
|
+
CFBundleURLSchemes: [scheme]
|
|
14083
|
+
}));
|
|
14084
|
+
await Promise.all([
|
|
14085
|
+
this.project.ios.updateInfoPlist(this.iosTargetName, "Debug", { CFBundleURLTypes: urlTypes }),
|
|
14086
|
+
this.project.ios.updateInfoPlist(this.iosTargetName, "Release", { CFBundleURLTypes: urlTypes })
|
|
14087
|
+
]);
|
|
14088
|
+
}
|
|
14089
|
+
async#setAssociatedDomainsInIos(domains) {
|
|
14090
|
+
const entitlementsRelPath = "App/App.entitlements";
|
|
14091
|
+
const entitlementsPath = path41.join(this.app.cwdPath, this.iosProjectPath, entitlementsRelPath);
|
|
14092
|
+
const values = domains.map((domain) => `applinks:${domain}`);
|
|
14093
|
+
const body = [
|
|
14094
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
14095
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
14096
|
+
'<plist version="1.0">',
|
|
14097
|
+
"<dict>",
|
|
14098
|
+
" <key>com.apple.developer.associated-domains</key>",
|
|
14099
|
+
" <array>",
|
|
14100
|
+
...values.map((value) => ` <string>${value}</string>`),
|
|
14101
|
+
" </array>",
|
|
14102
|
+
"</dict>",
|
|
14103
|
+
"</plist>",
|
|
14104
|
+
""
|
|
14105
|
+
].join(`
|
|
14106
|
+
`);
|
|
14107
|
+
await writeFile2(entitlementsPath, body);
|
|
14108
|
+
await this.#setCodeSignEntitlementsInIos(entitlementsRelPath);
|
|
14109
|
+
}
|
|
14110
|
+
async#setCodeSignEntitlementsInIos(entitlementsRelPath) {
|
|
14111
|
+
const pbxprojPath = path41.join(this.app.cwdPath, this.iosProjectPath, "App.xcodeproj/project.pbxproj");
|
|
14112
|
+
const lines = (await readFile2(pbxprojPath, "utf8")).split(`
|
|
14113
|
+
`);
|
|
14114
|
+
let changed = false;
|
|
14115
|
+
for (let index = 0;index < lines.length; index++) {
|
|
14116
|
+
const line = lines[index] ?? "";
|
|
14117
|
+
if (!line.includes(`PRODUCT_BUNDLE_IDENTIFIER = ${this.target.appId};`))
|
|
14118
|
+
continue;
|
|
14119
|
+
let start = index;
|
|
14120
|
+
while (start >= 0 && !lines[start]?.includes("buildSettings = {"))
|
|
14121
|
+
start--;
|
|
14122
|
+
let end = index;
|
|
14123
|
+
while (end < lines.length && !/^\s*\};\s*$/.test(lines[end] ?? ""))
|
|
14124
|
+
end++;
|
|
14125
|
+
const settings = lines.slice(start, end + 1);
|
|
14126
|
+
if (settings.some((setting) => setting.includes("CODE_SIGN_ENTITLEMENTS")))
|
|
14127
|
+
continue;
|
|
14128
|
+
const indent = line.match(/^\s*/)?.[0] ?? "";
|
|
14129
|
+
lines.splice(index, 0, `${indent}CODE_SIGN_ENTITLEMENTS = ${entitlementsRelPath};`);
|
|
14130
|
+
index++;
|
|
14131
|
+
changed = true;
|
|
14132
|
+
}
|
|
14133
|
+
if (changed)
|
|
14134
|
+
await writeFile2(pbxprojPath, lines.join(`
|
|
14135
|
+
`));
|
|
14136
|
+
}
|
|
14137
|
+
async#setUrlSchemesInAndroid(schemes) {
|
|
14138
|
+
const manifestPath = path41.join(this.app.cwdPath, this.androidRootPath, "app/src/main/AndroidManifest.xml");
|
|
14139
|
+
let manifest = await readFile2(manifestPath, "utf8");
|
|
14140
|
+
let changed = false;
|
|
14141
|
+
for (const scheme of schemes) {
|
|
14142
|
+
if (manifest.includes(`android:scheme="${scheme}"`))
|
|
14143
|
+
continue;
|
|
14144
|
+
const filter = [
|
|
14145
|
+
" <intent-filter>",
|
|
14146
|
+
' <action android:name="android.intent.action.VIEW" />',
|
|
14147
|
+
' <category android:name="android.intent.category.DEFAULT" />',
|
|
14148
|
+
' <category android:name="android.intent.category.BROWSABLE" />',
|
|
14149
|
+
` <data android:scheme="${scheme}" />`,
|
|
14150
|
+
" </intent-filter>"
|
|
14151
|
+
].join(`
|
|
14152
|
+
`);
|
|
14153
|
+
manifest = manifest.replace(/(\s*<\/activity>)/, `
|
|
14154
|
+
${filter}$1`);
|
|
14155
|
+
changed = true;
|
|
14156
|
+
}
|
|
14157
|
+
if (changed)
|
|
14158
|
+
await writeFile2(manifestPath, manifest);
|
|
14159
|
+
}
|
|
14160
|
+
async#setDeepLinksInAndroid(schemes, domains) {
|
|
14161
|
+
await this.#setUrlSchemesInAndroid(schemes);
|
|
14162
|
+
const manifestPath = path41.join(this.app.cwdPath, this.androidRootPath, "app/src/main/AndroidManifest.xml");
|
|
14163
|
+
let manifest = await readFile2(manifestPath, "utf8");
|
|
14164
|
+
let changed = false;
|
|
14165
|
+
const pathPrefix = resolveMobilePath(this.target, "/");
|
|
14166
|
+
for (const domain of domains) {
|
|
14167
|
+
if (manifest.includes(`android:host="${domain}"`) && manifest.includes('android:scheme="https"'))
|
|
14168
|
+
continue;
|
|
14169
|
+
const filter = [
|
|
14170
|
+
' <intent-filter android:autoVerify="true">',
|
|
14171
|
+
' <action android:name="android.intent.action.VIEW" />',
|
|
14172
|
+
' <category android:name="android.intent.category.DEFAULT" />',
|
|
14173
|
+
' <category android:name="android.intent.category.BROWSABLE" />',
|
|
14174
|
+
` <data android:scheme="https" android:host="${domain}" android:pathPrefix="${pathPrefix}" />`,
|
|
14175
|
+
" </intent-filter>"
|
|
14176
|
+
].join(`
|
|
14177
|
+
`);
|
|
14178
|
+
manifest = manifest.replace(/(\s*<\/activity>)/, `
|
|
14179
|
+
${filter}$1`);
|
|
14180
|
+
changed = true;
|
|
14181
|
+
}
|
|
14182
|
+
if (changed)
|
|
14183
|
+
await writeFile2(manifestPath, manifest);
|
|
14184
|
+
}
|
|
13993
14185
|
#setFeaturesInAndroid(features) {
|
|
13994
14186
|
for (const feature of features) {
|
|
13995
14187
|
if (this.#hasFeatureInAndroid(feature)) {
|
|
@@ -15072,7 +15264,7 @@ ${document}
|
|
|
15072
15264
|
}
|
|
15073
15265
|
// pkgs/@akanjs/devkit/qualityScanner.ts
|
|
15074
15266
|
import { createHash } from "crypto";
|
|
15075
|
-
import { readdir as readdir3, readFile as
|
|
15267
|
+
import { readdir as readdir3, readFile as readFile3, stat as stat4 } from "fs/promises";
|
|
15076
15268
|
import path43 from "path";
|
|
15077
15269
|
import ignore2 from "ignore";
|
|
15078
15270
|
import ts11 from "typescript";
|
|
@@ -15161,7 +15353,7 @@ class AkanQualityScanner {
|
|
|
15161
15353
|
const gitIgnorePath = path43.join(workspaceRoot, ".gitignore");
|
|
15162
15354
|
if (!await Bun.file(gitIgnorePath).exists())
|
|
15163
15355
|
return [];
|
|
15164
|
-
return (await
|
|
15356
|
+
return (await readFile3(gitIgnorePath, "utf8")).split(/\r?\n/);
|
|
15165
15357
|
}
|
|
15166
15358
|
async#walkTargetFiles(workspaceRoot, currentPath, ignoreFilter, files) {
|
|
15167
15359
|
const entries = await readdir3(currentPath, { withFileTypes: true });
|
|
@@ -15181,7 +15373,7 @@ class AkanQualityScanner {
|
|
|
15181
15373
|
}
|
|
15182
15374
|
async#readSourceFile(workspaceRoot, file) {
|
|
15183
15375
|
const absolutePath = path43.join(workspaceRoot, file);
|
|
15184
|
-
const content = await
|
|
15376
|
+
const content = await readFile3(absolutePath, "utf8");
|
|
15185
15377
|
return {
|
|
15186
15378
|
file,
|
|
15187
15379
|
absolutePath,
|
|
@@ -17184,7 +17376,10 @@ ${chalk7.green("\u27A4")} Authentication Required`));
|
|
|
17184
17376
|
...appNames.map((appName) => `apps/${appName}/env`),
|
|
17185
17377
|
...libNames.map((libName) => `libs/${libName}/env`)
|
|
17186
17378
|
];
|
|
17187
|
-
const
|
|
17379
|
+
const defaultEnvFilePaths = (await Promise.all(envDirs.map(async (envDir) => (await workspace.readdir(envDir)).filter((fileName) => envFilePattern.test(fileName)).map((fileName) => `${envDir}/${fileName}`)))).flat();
|
|
17380
|
+
await this.#syncSecretGitignore(workspace, appNames);
|
|
17381
|
+
const customSecretPaths = await this.#gatherCustomSecretFiles(workspace, appNames);
|
|
17382
|
+
const envFilePaths = [...new Set([...defaultEnvFilePaths, ...customSecretPaths])].sort();
|
|
17188
17383
|
await workspace.mkdir("local");
|
|
17189
17384
|
await workspace.remove("local/env.tar");
|
|
17190
17385
|
if (envFilePaths.length === 0)
|
|
@@ -17195,6 +17390,52 @@ ${chalk7.green("\u27A4")} Authentication Required`));
|
|
|
17195
17390
|
Logger15.info(`Archived ${envFilePaths.length} environment files to local/env.tar`);
|
|
17196
17391
|
return { files: envFilePaths, path: "local/env.tar" };
|
|
17197
17392
|
}
|
|
17393
|
+
async#gatherCustomSecretFiles(workspace, appNames) {
|
|
17394
|
+
const secretPaths = await Promise.all(appNames.map(async (appName) => {
|
|
17395
|
+
const config = await AppExecutor.from(workspace, appName).getConfig();
|
|
17396
|
+
const secretGlobs = config.secrets ?? [];
|
|
17397
|
+
if (secretGlobs.length === 0)
|
|
17398
|
+
return [];
|
|
17399
|
+
const appDir = path45.join(workspace.workspaceRoot, "apps", appName);
|
|
17400
|
+
return secretGlobs.flatMap((pattern) => Array.from(new Bun.Glob(pattern).scanSync({ cwd: appDir, onlyFiles: true })).map((match) => `apps/${appName}/${match.split(path45.sep).join("/")}`));
|
|
17401
|
+
}));
|
|
17402
|
+
return secretPaths.flat();
|
|
17403
|
+
}
|
|
17404
|
+
async#syncSecretGitignore(workspace, appNames) {
|
|
17405
|
+
const patterns = (await Promise.all(appNames.map(async (appName) => {
|
|
17406
|
+
const config = await AppExecutor.from(workspace, appName).getConfig();
|
|
17407
|
+
return (config.secrets ?? []).map((pattern) => `apps/${appName}/${pattern.replace(/^\/+/, "")}`);
|
|
17408
|
+
}))).flat();
|
|
17409
|
+
const uniquePatterns = [...new Set(patterns)].sort();
|
|
17410
|
+
const existing = await workspace.exists(".gitignore") ? await workspace.readFile(".gitignore") : "";
|
|
17411
|
+
const nextContent = this.#applySecretGitignoreBlock(existing, uniquePatterns);
|
|
17412
|
+
if (nextContent === existing)
|
|
17413
|
+
return;
|
|
17414
|
+
await workspace.writeFile(".gitignore", nextContent);
|
|
17415
|
+
Logger15.info(uniquePatterns.length ? `Synced ${uniquePatterns.length} secret pattern(s) from akan.config.ts to .gitignore` : "Removed managed secret patterns from .gitignore");
|
|
17416
|
+
}
|
|
17417
|
+
#applySecretGitignoreBlock(content, patterns) {
|
|
17418
|
+
const beginMarker = "# akan:secrets (managed by akan.config.ts \u2014 do not edit)";
|
|
17419
|
+
const endMarker = "# akan:secrets:end";
|
|
17420
|
+
const lines = content.split(`
|
|
17421
|
+
`);
|
|
17422
|
+
const beginIdx = lines.indexOf(beginMarker);
|
|
17423
|
+
const endIdx = lines.indexOf(endMarker);
|
|
17424
|
+
const stripped = beginIdx !== -1 && endIdx !== -1 && endIdx >= beginIdx ? [...lines.slice(0, beginIdx), ...lines.slice(endIdx + 1)] : [...lines];
|
|
17425
|
+
while (stripped.length && stripped[stripped.length - 1]?.trim() === "")
|
|
17426
|
+
stripped.pop();
|
|
17427
|
+
if (patterns.length === 0)
|
|
17428
|
+
return stripped.length ? `${stripped.join(`
|
|
17429
|
+
`)}
|
|
17430
|
+
` : "";
|
|
17431
|
+
const body = stripped.length ? `${stripped.join(`
|
|
17432
|
+
`)}
|
|
17433
|
+
|
|
17434
|
+
` : "";
|
|
17435
|
+
return `${body}${[beginMarker, ...patterns, endMarker].join(`
|
|
17436
|
+
`)}
|
|
17437
|
+
`;
|
|
17438
|
+
}
|
|
17198
17439
|
}
|
|
17199
17440
|
|
|
17200
17441
|
// pkgs/@akanjs/cli/cloud/cloud.script.ts
|
|
@@ -17474,7 +17715,7 @@ class RepairRunner extends runner("repair") {
|
|
|
17474
17715
|
}
|
|
17475
17716
|
|
|
17476
17717
|
// pkgs/@akanjs/cli/workflow/workflow.runner.ts
|
|
17477
|
-
import { mkdir as mkdir12, readFile as
|
|
17718
|
+
import { mkdir as mkdir12, readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
|
|
17478
17719
|
import path46 from "path";
|
|
17479
17720
|
import { capitalize as capitalize8 } from "akanjs/common";
|
|
17480
17721
|
|
|
@@ -18116,7 +18357,7 @@ var defaultValidationExecutor = (workspace) => async (command3) => {
|
|
|
18116
18357
|
};
|
|
18117
18358
|
}
|
|
18118
18359
|
};
|
|
18119
|
-
var readJsonFile = async (filePath) => JSON.parse(await
|
|
18360
|
+
var readJsonFile = async (filePath) => JSON.parse(await readFile4(resolvePath(filePath), "utf8"));
|
|
18120
18361
|
var planInputString2 = (plan2, key) => {
|
|
18121
18362
|
const value = plan2.inputs[key];
|
|
18122
18363
|
return typeof value === "string" ? value : "";
|
|
@@ -18213,7 +18454,7 @@ class WorkflowRunner extends runner("workflow") {
|
|
|
18213
18454
|
if (out) {
|
|
18214
18455
|
const outPath = resolvePath(out);
|
|
18215
18456
|
await mkdir12(path46.dirname(outPath), { recursive: true });
|
|
18216
|
-
await
|
|
18457
|
+
await writeFile3(outPath, jsonText(plan2));
|
|
18217
18458
|
}
|
|
18218
18459
|
return format === "json" ? jsonText(plan2) : renderWorkflowPlan(plan2);
|
|
18219
18460
|
}
|
|
@@ -18231,7 +18472,7 @@ class WorkflowRunner extends runner("workflow") {
|
|
|
18231
18472
|
};
|
|
18232
18473
|
let plan2;
|
|
18233
18474
|
try {
|
|
18234
|
-
const parsed = JSON.parse(await
|
|
18475
|
+
const parsed = JSON.parse(await readFile4(resolvePath(planPath), "utf8"));
|
|
18235
18476
|
if (!isWorkflowPlan2(parsed)) {
|
|
18236
18477
|
const report = failedApplyReport("unknown", [
|
|
18237
18478
|
{
|
|
@@ -20822,6 +21063,12 @@ class WorkspaceRunner extends runner("workspace") {
|
|
|
20822
21063
|
dict,
|
|
20823
21064
|
overwrite
|
|
20824
21065
|
});
|
|
21066
|
+
created.push(...await workspace.applyTemplate({
|
|
21067
|
+
basePath: ".",
|
|
21068
|
+
template: "workspaceRoot/CLAUDE.md.template",
|
|
21069
|
+
dict,
|
|
21070
|
+
overwrite
|
|
21071
|
+
}));
|
|
20825
21072
|
if (!cursorRules)
|
|
20826
21073
|
return created;
|
|
20827
21074
|
return [
|
|
@@ -21116,7 +21363,9 @@ class WorkspaceCommand extends command("workspace", [WorkspaceScript], ({ public
|
|
|
21116
21363
|
...registry ? { registryUrl: registry } : {}
|
|
21117
21364
|
});
|
|
21118
21365
|
}),
|
|
21119
|
-
generateAgentRules: target({
|
|
21366
|
+
generateAgentRules: target({
|
|
21367
|
+
desc: "Generate AGENTS.md, CLAUDE.md, and optional Cursor rules for Akan coding agents"
|
|
21368
|
+
}).option("overwrite", Boolean, { desc: "Overwrite existing agent rule files", default: false }).option("cursorRules", Boolean, { desc: "Generate .cursor/rules/akan.mdc", default: true }).with(Workspace).exec(async function(overwrite, cursorRules, workspace) {
|
|
21120
21369
|
await this.workspaceScript.generateAgentRules(workspace, { overwrite, cursorRules });
|
|
21121
21370
|
}),
|
|
21122
21371
|
lint: target({ desc: "Lint and fix code in a specific app/lib/pkg" }).with(Exec).option("fix", Boolean, { default: true }).with(Workspace).exec(async function(exec2, fix, workspace) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akanjs/cli",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.10-rc.1",
|
|
4
4
|
"sourceType": "module",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"@langchain/openai": "^1.4.6",
|
|
35
35
|
"@tailwindcss/node": "^4.3.0",
|
|
36
36
|
"@trapezedev/project": "^7.1.4",
|
|
37
|
-
"akanjs": "2.3.
|
|
37
|
+
"akanjs": "2.3.10-rc.1",
|
|
38
38
|
"chalk": "^5.6.2",
|
|
39
39
|
"commander": "^14.0.3",
|
|
40
40
|
"daisyui": "5.5.23",
|