@akanjs/cli 2.3.11-rc.6 → 2.3.11-rc.8

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.
@@ -666,6 +666,7 @@ import {
666
666
  import { readFileSync as readFileSync3 } from "fs";
667
667
  import { copyFile, mkdir as mkdir2, readdir as readDirEntries, stat as stat2 } from "fs/promises";
668
668
  import path7 from "path";
669
+ import { pathToFileURL } from "url";
669
670
  import {
670
671
  capitalize,
671
672
  isRouteSourceFile,
@@ -1107,6 +1108,63 @@ var decreaseBuildNum = async (app) => {
1107
1108
  const akanConfigContent = akanConfig.replace(`buildNum: ${appConfig.mobile.buildNum}`, `buildNum: ${appConfig.mobile.buildNum - 1}`);
1108
1109
  fs.writeFileSync(akanConfigPath, akanConfigContent);
1109
1110
  };
1111
+ // pkgs/@akanjs/devkit/firebaseMessagingSw.ts
1112
+ var FIREBASE_WEB_SDK_VERSION = "12.13.0";
1113
+ function normalizeFirebaseClientConfig(config) {
1114
+ if (!config || typeof config !== "object")
1115
+ return null;
1116
+ const value = config;
1117
+ if (typeof value.apiKey !== "string" || typeof value.projectId !== "string" || typeof value.messagingSenderId !== "string" || typeof value.appId !== "string") {
1118
+ return null;
1119
+ }
1120
+ return {
1121
+ apiKey: value.apiKey,
1122
+ ...typeof value.authDomain === "string" ? { authDomain: value.authDomain } : {},
1123
+ projectId: value.projectId,
1124
+ ...typeof value.storageBucket === "string" ? { storageBucket: value.storageBucket } : {},
1125
+ messagingSenderId: value.messagingSenderId,
1126
+ appId: value.appId
1127
+ };
1128
+ }
1129
+ function createFirebaseMessagingServiceWorker(config) {
1130
+ const configJson = JSON.stringify(config);
1131
+ return `/* Generated by Akan.js. Do not edit. */
1132
+ const firebaseConfig = ${configJson};
1133
+
1134
+ if (firebaseConfig) {
1135
+ importScripts("https://www.gstatic.com/firebasejs/${FIREBASE_WEB_SDK_VERSION}/firebase-app-compat.js");
1136
+ importScripts("https://www.gstatic.com/firebasejs/${FIREBASE_WEB_SDK_VERSION}/firebase-messaging-compat.js");
1137
+
1138
+ firebase.initializeApp(firebaseConfig);
1139
+ const messaging = firebase.messaging();
1140
+
1141
+ const notificationUrl = (payload) =>
1142
+ payload?.data?.url || payload?.fcmOptions?.link || payload?.notification?.click_action;
1143
+
1144
+ messaging.onBackgroundMessage((payload) => {
1145
+ const title = payload?.notification?.title || "";
1146
+ const options = {
1147
+ body: payload?.notification?.body,
1148
+ icon: payload?.notification?.icon,
1149
+ image: payload?.notification?.image,
1150
+ data: {
1151
+ url: notificationUrl(payload),
1152
+ FCM_MSG: payload,
1153
+ },
1154
+ };
1155
+ self.registration.showNotification(title, options);
1156
+ });
1157
+ }
1158
+
1159
+ self.addEventListener("notificationclick", (event) => {
1160
+ const url = event.notification?.data?.url || event.notification?.data?.FCM_MSG?.data?.url;
1161
+ event.notification?.close();
1162
+ if (!url) return;
1163
+ event.waitUntil(clients.openWindow(url));
1164
+ });
1165
+ `;
1166
+ }
1167
+
1110
1168
  // pkgs/@akanjs/devkit/getDirname.ts
1111
1169
  import path2 from "path";
1112
1170
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -1392,6 +1450,7 @@ ${errorText}, ${warningText} found`];
1392
1450
 
1393
1451
  // pkgs/@akanjs/devkit/scanInfo.ts
1394
1452
  import path5 from "path";
1453
+ import { rm } from "fs/promises";
1395
1454
 
1396
1455
  // pkgs/@akanjs/devkit/dependencyScanner.ts
1397
1456
  import { builtinModules } from "module";
@@ -1713,6 +1772,7 @@ var appRootAllowedFiles = new Set([
1713
1772
  "tsconfig.json",
1714
1773
  "tsconfig.tsbuildinfo"
1715
1774
  ]);
1775
+ var generatedRootCapacitorConfigFiles = ["capacitor.config.js", "capacitor.config.json"];
1716
1776
  var appRootAllowedDirs = new Set([
1717
1777
  ".akan",
1718
1778
  "android",
@@ -1756,11 +1816,17 @@ var rootSignalTestFilePattern = /^[A-Za-z][A-Za-z0-9_-]*\.signal\.(test|spec)\.(
1756
1816
  var isAllowedTestFile = (filename) => testFilePattern.test(filename);
1757
1817
  var isAllowedLibRootFile = (filename) => libRootAllowedFiles.has(filename) || rootSignalTestFilePattern.test(filename);
1758
1818
  var getScanPath = (exec, relativePath) => path5.posix.join(`${exec.type}s`, exec.name, relativePath.split(path5.sep).join("/"));
1819
+ async function clearGeneratedRootCapacitorConfigs(exec) {
1820
+ if (exec.type !== "app")
1821
+ return;
1822
+ await Promise.all(generatedRootCapacitorConfigFiles.map((filename) => rm(exec.getPath(filename), { force: true })));
1823
+ }
1759
1824
  var getModuleNameFromPath = (kind, modulePath) => {
1760
1825
  const dirname = path5.basename(modulePath);
1761
1826
  return kind === "service" ? dirname.replace(/^_+/, "") : dirname;
1762
1827
  };
1763
1828
  async function assertScanConvention(exec, libRoot) {
1829
+ await clearGeneratedRootCapacitorConfigs(exec);
1764
1830
  const violations = [];
1765
1831
  const addViolation = (relativePath, reason) => {
1766
1832
  violations.push(`${getScanPath(exec, relativePath)}: ${reason}`);
@@ -3577,6 +3643,8 @@ class AppExecutor extends SysExecutor {
3577
3643
  ...routeEnv,
3578
3644
  ...devPort ? { PORT: devPort, AKAN_PUBLIC_CLIENT_PORT: devPort, AKAN_PUBLIC_SERVER_PORT: devPort } : {}
3579
3645
  });
3646
+ if (type === "build")
3647
+ Object.assign(process.env, env);
3580
3648
  return { env };
3581
3649
  }
3582
3650
  #publicEnv = null;
@@ -3687,8 +3755,30 @@ class AppExecutor extends SysExecutor {
3687
3755
  });
3688
3756
  if (write)
3689
3757
  await this.syncAssets(scanInfo.getScanResult().libDeps);
3758
+ if (write)
3759
+ await this.#syncFirebaseMessagingSw();
3690
3760
  return scanInfo;
3691
3761
  }
3762
+ async#syncFirebaseMessagingSw() {
3763
+ const swRelPath = "public/firebase-messaging-sw.js";
3764
+ if (await FileSys.fileExists(this.getPath(swRelPath)))
3765
+ return;
3766
+ const envClientPath = path7.join(this.cwdPath, "env", "env.client.ts");
3767
+ if (!await FileSys.fileExists(envClientPath))
3768
+ return;
3769
+ let firebaseConfig = null;
3770
+ try {
3771
+ const envUrl = pathToFileURL(envClientPath);
3772
+ envUrl.searchParams.set("t", String(Date.now()));
3773
+ const envModule = await import(envUrl.href);
3774
+ firebaseConfig = normalizeFirebaseClientConfig(envModule.env?.firebase);
3775
+ } catch {
3776
+ return;
3777
+ }
3778
+ if (!firebaseConfig)
3779
+ return;
3780
+ await this.writeFile(swRelPath, createFirebaseMessagingServiceWorker(firebaseConfig), { overwrite: false });
3781
+ }
3692
3782
  async increaseBuildNum() {
3693
3783
  await increaseBuildNum(this);
3694
3784
  }
@@ -4996,7 +5086,6 @@ var workflowSyncStatePath = (target) => `${workflowSyncDir}/${syncStateSlug(targ
4996
5086
  var generatedFilePathsForTarget = (targetRoot, reason = "Generated files were refreshed by sync.") => [
4997
5087
  { path: `${targetRoot}/lib/cnst.ts`, action: "sync", reason },
4998
5088
  { path: `${targetRoot}/lib/dict.ts`, action: "sync", reason },
4999
- { path: `${targetRoot}/lib/option.ts`, action: "sync", reason },
5000
5089
  { path: `${targetRoot}/lib/index.ts`, action: "sync", reason }
5001
5090
  ];
5002
5091
  var writeGeneratedSyncState = async (workspace, state) => {
@@ -7650,13 +7739,59 @@ var resourceList = [
7650
7739
  { uri: "akan://workspace/modules", name: "Workspace modules", mimeType: "application/json" }
7651
7740
  ];
7652
7741
  var cursorMcpConfigPath = ".cursor/mcp.json";
7742
+ var claudeMcpConfigPath = ".mcp.json";
7743
+ var codexMcpConfigPath = ".codex/config.toml";
7744
+ var akanMcpInstallTargets = ["cursor", "claude", "codex"];
7745
+ var akanMcpInstallConfigPaths = {
7746
+ cursor: cursorMcpConfigPath,
7747
+ claude: claudeMcpConfigPath,
7748
+ codex: codexMcpConfigPath
7749
+ };
7653
7750
  var cursorWorkspaceFolder = "$" + "{workspaceFolder}";
7751
+ var claudeProjectDir = "$CLAUDE_PROJECT_DIR";
7752
+ var akanMcpCommand = (mode, { cd } = {}) => cd ? `cd "${cd}" && akan mcp --mode ${mode}` : `akan mcp --mode ${mode}`;
7654
7753
  var createAkanCursorMcpServer = (mode = "readonly") => ({
7655
7754
  type: "stdio",
7656
7755
  command: "bash",
7657
- args: ["-lc", `cd "${cursorWorkspaceFolder}" && akan mcp --mode ${mode}`]
7756
+ args: ["-lc", akanMcpCommand(mode, { cd: cursorWorkspaceFolder })]
7757
+ });
7758
+ var createAkanClaudeMcpServer = (mode = "readonly") => ({
7759
+ type: "stdio",
7760
+ command: "bash",
7761
+ args: ["-lc", akanMcpCommand(mode, { cd: claudeProjectDir })]
7658
7762
  });
7763
+ var createAkanMcpServer = (target, mode = "readonly") => target === "cursor" ? createAkanCursorMcpServer(mode) : createAkanClaudeMcpServer(mode);
7659
7764
  var akanCursorMcpServer = createAkanCursorMcpServer();
7765
+ var codexMcpServerTableHeader = "[mcp_servers.akan]";
7766
+ var createAkanCodexMcpServerBlock = (mode = "readonly") => `${codexMcpServerTableHeader}
7767
+ command = "bash"
7768
+ args = ["-lc", "${akanMcpCommand(mode)}"]
7769
+ `;
7770
+ var codexAkanTablePattern = /^\[mcp_servers\.akan\][^\n]*\n(?:(?!\[)[^\n]*(?:\n|$))*/m;
7771
+ var upsertCodexMcpServerBlock = (existing, block, { force = false } = {}) => {
7772
+ const nextBlock = block.endsWith(`
7773
+ `) ? block : `${block}
7774
+ `;
7775
+ const match = existing.match(codexAkanTablePattern);
7776
+ if (!match) {
7777
+ if (!existing.trim())
7778
+ return nextBlock;
7779
+ return `${existing.replace(/\s*$/, "")}
7780
+
7781
+ ${nextBlock}`;
7782
+ }
7783
+ if (match[0].trim() === nextBlock.trim())
7784
+ return existing;
7785
+ if (!force)
7786
+ throw new Error(`${codexMcpConfigPath} already has an "akan" MCP server. Re-run with --force to overwrite it.`);
7787
+ const start = match.index ?? 0;
7788
+ const before = existing.slice(0, start);
7789
+ const after = existing.slice(start + match[0].length);
7790
+ const separator = after && !after.startsWith(`
7791
+ `) ? `
7792
+ ` : "";
7793
+ return `${before}${nextBlock}${separator}${after}`;
7794
+ };
7660
7795
  var renderDoctorText = (result) => {
7661
7796
  const lines = [`Akan doctor status: ${result.status}`];
7662
7797
  if (result.diagnostics.length === 0) {
@@ -7679,7 +7814,6 @@ var generatedFiles = [
7679
7814
  "*/lib/cnst.ts",
7680
7815
  "*/lib/db.ts",
7681
7816
  "*/lib/dict.ts",
7682
- "*/lib/option.ts",
7683
7817
  "*/lib/sig.ts",
7684
7818
  "*/lib/srv.ts",
7685
7819
  "*/lib/st.ts",
@@ -8673,7 +8807,7 @@ Caused by: ${ApplicationBuildReporter.formatError(error.cause)}` : "";
8673
8807
  }
8674
8808
  }
8675
8809
  // pkgs/@akanjs/devkit/applicationBuildRunner.ts
8676
- import { mkdir as mkdir8, rm as rm3 } from "fs/promises";
8810
+ import { mkdir as mkdir8, rm as rm4 } from "fs/promises";
8677
8811
  import path37 from "path";
8678
8812
 
8679
8813
  // pkgs/@akanjs/devkit/frontendBuild/allRoutesBuilder.ts
@@ -10431,7 +10565,7 @@ class AllRoutesBuilder {
10431
10565
  }
10432
10566
  }
10433
10567
  // pkgs/@akanjs/devkit/frontendBuild/csrArtifactBuilder.ts
10434
- import { mkdir as mkdir5, rm, unlink } from "fs/promises";
10568
+ import { mkdir as mkdir5, rm as rm2, unlink } from "fs/promises";
10435
10569
  import path24 from "path";
10436
10570
 
10437
10571
  // pkgs/@akanjs/devkit/frontendBuild/pagesEntrySourceGenerator.ts
@@ -10561,7 +10695,7 @@ class CsrArtifactBuilder {
10561
10695
  const artifact = await this.#loadCsrArtifact();
10562
10696
  const csrBasePaths = [...akanConfig2.basePaths];
10563
10697
  const htmlEntries = csrBasePaths.length > 0 ? csrBasePaths : ["index"];
10564
- await rm(this.#outputDir, { recursive: true, force: true });
10698
+ await rm2(this.#outputDir, { recursive: true, force: true });
10565
10699
  await mkdir5(path24.join(this.#app.cwdPath, ".akan/generated/csr"), { recursive: true });
10566
10700
  const generatedHtmlFiles = Object.fromEntries(htmlEntries.map((basePath2) => this.#createHtmlFile(basePath2)));
10567
10701
  const result = await Bun.build({
@@ -11295,7 +11429,7 @@ class DevChangePlanner {
11295
11429
  }
11296
11430
  var uniqueResolved = (files) => [...new Set(files.map((file) => path27.resolve(file)))].sort();
11297
11431
  // pkgs/@akanjs/devkit/frontendBuild/devGeneratedIndexSync.ts
11298
- import { mkdir as mkdir6, readdir as readdir2, readFile, rm as rm2, stat as stat3, writeFile } from "fs/promises";
11432
+ import { mkdir as mkdir6, readdir as readdir2, readFile, rm as rm3, stat as stat3, writeFile } from "fs/promises";
11299
11433
  import path28 from "path";
11300
11434
  var BARREL_FACETS2 = new Set(["common", "srvkit", "ui", "webkit"]);
11301
11435
  var FACET_SOURCE_FILE_RE = /\.(ts|tsx)$/;
@@ -11378,7 +11512,7 @@ class DevGeneratedIndexSync {
11378
11512
  if (content === null) {
11379
11513
  if (!await exists(indexPath))
11380
11514
  return false;
11381
- await rm2(indexPath, { force: true });
11515
+ await rm3(indexPath, { force: true });
11382
11516
  return true;
11383
11517
  }
11384
11518
  const current = await readFile(indexPath, "utf8").catch(() => null);
@@ -12525,7 +12659,7 @@ class ApplicationBuildRunner {
12525
12659
  await this.#app.getPageKeys({ refresh: true });
12526
12660
  const { typecheckDir, tsconfigPath } = await this.#writeTypecheckTsconfig({ incremental });
12527
12661
  if (clean)
12528
- await rm3(path37.join(typecheckDir, "tsconfig.tsbuildinfo"), { force: true });
12662
+ await rm4(path37.join(typecheckDir, "tsconfig.tsbuildinfo"), { force: true });
12529
12663
  await this.#checkProjectInChildProcess(tsconfigPath);
12530
12664
  }
12531
12665
  async#runPhase(id, label, task, summarize, options = {}) {
@@ -12725,7 +12859,7 @@ void run().catch((error) => {
12725
12859
  }
12726
12860
  }
12727
12861
  // pkgs/@akanjs/devkit/applicationReleasePackager.ts
12728
- import { cp, mkdir as mkdir9, rm as rm4 } from "fs/promises";
12862
+ import { cp, mkdir as mkdir9, rm as rm5 } from "fs/promises";
12729
12863
  import path38 from "path";
12730
12864
 
12731
12865
  // pkgs/@akanjs/devkit/uploadRelease.ts
@@ -12832,7 +12966,7 @@ class ApplicationReleasePackager {
12832
12966
  const platformVersion = akanConfig2.mobile.version;
12833
12967
  const buildRoot = `${this.#app.workspace.workspaceRoot}/releases/builds/${this.#app.name}`;
12834
12968
  if (await FileSys.dirExists(buildRoot))
12835
- await rm4(buildRoot, { recursive: true, force: true });
12969
+ await rm5(buildRoot, { recursive: true, force: true });
12836
12970
  await mkdir9(buildRoot, { recursive: true });
12837
12971
  if (rebuild || !await FileSys.dirExists(`${this.#app.dist.cwdPath}/backend`))
12838
12972
  await this.#build();
@@ -12841,7 +12975,7 @@ class ApplicationReleasePackager {
12841
12975
  await mkdir9(buildPath, { recursive: true });
12842
12976
  await cp(`${this.#app.dist.cwdPath}/backend`, `${buildPath}/backend`, { recursive: true });
12843
12977
  await cp(this.#app.dist.cwdPath, buildRoot, { recursive: true });
12844
- await rm4(`${buildRoot}/frontend/.next`, { recursive: true, force: true });
12978
+ await rm5(`${buildRoot}/frontend/.next`, { recursive: true, force: true });
12845
12979
  const releaseRoot = this.#app.workspace.workspaceRoot;
12846
12980
  const releaseArchivePath = path38.relative(releaseRoot, `${releaseRoot}/releases/builds/${this.#app.name}-release.tar.gz`).split(path38.sep).join("/");
12847
12981
  await this.#app.workspace.spawn("tar", ["-zcf", releaseArchivePath, "-C", buildRoot, "./"], { cwd: releaseRoot });
@@ -12857,7 +12991,7 @@ class ApplicationReleasePackager {
12857
12991
  `${this.#app.workspace.workspaceRoot}/releases/builds/${this.#app.name}-appBuild.zip`,
12858
12992
  "./csr"
12859
12993
  ]);
12860
- await rm4("./csr", { recursive: true, force: true });
12994
+ await rm5("./csr", { recursive: true, force: true });
12861
12995
  }
12862
12996
  async#writeSourceArchive({ readme }) {
12863
12997
  const sourceRoot = `${this.#app.workspace.workspaceRoot}/releases/sources/${this.#app.name}`;
@@ -12868,7 +13002,7 @@ class ApplicationReleasePackager {
12868
13002
  await Promise.all([".next", "ios", "android", "public/libs"].map(async (path39) => {
12869
13003
  const targetPath = `${sourceRoot}/apps/${this.#app.name}/${path39}`;
12870
13004
  if (await FileSys.dirExists(targetPath))
12871
- await rm4(targetPath, { recursive: true, force: true });
13005
+ await rm5(targetPath, { recursive: true, force: true });
12872
13006
  }));
12873
13007
  const syncPaths = [".husky", ".gitignore", "package.json"];
12874
13008
  await Promise.all(syncPaths.map((path39) => cp(`${this.#app.workspace.cwdPath}/${path39}`, `${sourceRoot}/${path39}`, { recursive: true })));
@@ -12883,7 +13017,7 @@ class ApplicationReleasePackager {
12883
13017
  const maxRetry = 3;
12884
13018
  for (let i = 0;i < maxRetry; i++) {
12885
13019
  try {
12886
- await rm4(sourceRoot, { recursive: true, force: true });
13020
+ await rm5(sourceRoot, { recursive: true, force: true });
12887
13021
  } catch {}
12888
13022
  }
12889
13023
  }
@@ -13116,7 +13250,7 @@ class Builder {
13116
13250
  }
13117
13251
  }
13118
13252
  // pkgs/@akanjs/devkit/capacitorApp.ts
13119
- import { cp as cp2, mkdir as mkdir11, readFile as readFile2, rm as rm5, writeFile as writeFile2 } from "fs/promises";
13253
+ import { cp as cp2, mkdir as mkdir11, readFile as readFile2, rm as rm6, writeFile as writeFile2 } from "fs/promises";
13120
13254
  import os from "os";
13121
13255
  import path41 from "path";
13122
13256
  import { select as select2 } from "@inquirer/prompts";
@@ -13327,7 +13461,7 @@ var rootCapacitorConfigFilenames = [
13327
13461
  ];
13328
13462
  var rootCapacitorConfigPaths = (appRoot) => rootCapacitorConfigFilenames.map((file) => path41.join(appRoot, file));
13329
13463
  async function clearRootCapacitorConfigs(appRoot) {
13330
- await Promise.all(rootCapacitorConfigPaths(appRoot).map((file) => rm5(file, { force: true })));
13464
+ await Promise.all(rootCapacitorConfigPaths(appRoot).map((file) => rm6(file, { force: true })));
13331
13465
  }
13332
13466
  async function writeRootCapacitorConfig(appRoot, content) {
13333
13467
  await clearRootCapacitorConfigs(appRoot);
@@ -13348,6 +13482,7 @@ var getLocalIP = () => {
13348
13482
  var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
13349
13483
  var asString = (value) => typeof value === "string" ? value : undefined;
13350
13484
  var firstString = (...values) => values.find((value) => typeof value === "string");
13485
+ var resolveIosApnsEnvironment = ({ operation }) => operation === "release" ? "production" : "development";
13351
13486
  var scoreIosDeviceTarget = (target) => {
13352
13487
  const state = target.state?.toLowerCase() ?? "";
13353
13488
  return (state.includes("available") ? 4 : 0) + (state.includes("wired") ? 2 : 0) + (state.includes("paired") ? 1 : 0);
@@ -13653,6 +13788,8 @@ function materializeCapacitorConfig(target, { operation, localServerUrl, localIp
13653
13788
  const experimentalConfig = isRecord2(experimental) ? experimental : undefined;
13654
13789
  const pluginsConfig = isRecord2(plugins) ? plugins : {};
13655
13790
  const keyboardPluginConfig = isRecord2(pluginsConfig.Keyboard) ? pluginsConfig.Keyboard : {};
13791
+ const pushNotificationsPluginConfig = isRecord2(pluginsConfig.PushNotifications) ? pluginsConfig.PushNotifications : {};
13792
+ const usesPushNotifications = target.permissions?.includes("push") ?? false;
13656
13793
  const config = {
13657
13794
  ...capacitorConfig,
13658
13795
  appId,
@@ -13661,6 +13798,12 @@ function materializeCapacitorConfig(target, { operation, localServerUrl, localIp
13661
13798
  plugins: {
13662
13799
  CapacitorCookies: { enabled: true },
13663
13800
  ...pluginsConfig,
13801
+ ...usesPushNotifications || isRecord2(pluginsConfig.PushNotifications) ? {
13802
+ PushNotifications: {
13803
+ ...usesPushNotifications ? { presentationOptions: ["badge", "sound", "alert"] } : {},
13804
+ ...pushNotificationsPluginConfig
13805
+ }
13806
+ } : {},
13664
13807
  Keyboard: {
13665
13808
  resize: "none",
13666
13809
  ...keyboardPluginConfig
@@ -13732,9 +13875,9 @@ class CapacitorApp {
13732
13875
  await mkdir11(this.targetRoot, { recursive: true });
13733
13876
  if (regenerate) {
13734
13877
  if (!platform || platform === "ios")
13735
- await rm5(path41.join(this.app.cwdPath, this.iosRootPath), { recursive: true, force: true });
13878
+ await rm6(path41.join(this.app.cwdPath, this.iosRootPath), { recursive: true, force: true });
13736
13879
  if (!platform || platform === "android")
13737
- await rm5(path41.join(this.app.cwdPath, this.androidRootPath), { recursive: true, force: true });
13880
+ await rm6(path41.join(this.app.cwdPath, this.androidRootPath), { recursive: true, force: true });
13738
13881
  }
13739
13882
  const project = this.project;
13740
13883
  await this.project.load();
@@ -13756,7 +13899,7 @@ class CapacitorApp {
13756
13899
  await this.#prepareTargetAssets();
13757
13900
  await this.#prepareExternalFiles("ios");
13758
13901
  await this.#applyIosMetadata();
13759
- await this.#applyPermissions();
13902
+ await this.#applyPermissions({ operation, env });
13760
13903
  await this.#applyDeepLinks("ios", { operation, env });
13761
13904
  await this.project.commit();
13762
13905
  await this.#generateAssets({ operation, env });
@@ -13899,7 +14042,7 @@ ${error.message}`;
13899
14042
  await this.#prepareTargetAssets();
13900
14043
  await this.#prepareExternalFiles("android");
13901
14044
  await this.#applyAndroidMetadata();
13902
- await this.#applyPermissions();
14045
+ await this.#applyPermissions({ operation, env });
13903
14046
  await this.#applyDeepLinks("android", { operation, env });
13904
14047
  await this.project.commit();
13905
14048
  await this.#generateAssets({ operation, env });
@@ -14033,7 +14176,7 @@ ${error.message}`;
14033
14176
  const htmlSource = path41.join(this.app.dist.cwdPath, "csr", targetHtmlFilename(this.target));
14034
14177
  if (!await Bun.file(htmlSource).exists())
14035
14178
  throw new Error(`CSR html for mobile target '${this.target.name}' not found: ${htmlSource}`);
14036
- await rm5(this.targetWebRoot, { recursive: true, force: true });
14179
+ await rm6(this.targetWebRoot, { recursive: true, force: true });
14037
14180
  await mkdir11(this.targetWebRoot, { recursive: true });
14038
14181
  await Bun.write(path41.join(this.targetWebRoot, "index.html"), this.#injectMobileTargetMeta(await Bun.file(htmlSource).text()));
14039
14182
  }
@@ -14114,7 +14257,7 @@ ${error.message}`;
14114
14257
  await this.project.android.setVersionCode(this.target.buildNum);
14115
14258
  await this.project.android.setAppName(this.target.appName);
14116
14259
  }
14117
- async#applyPermissions() {
14260
+ async#applyPermissions({ operation, env }) {
14118
14261
  for (const permission of this.target.permissions ?? []) {
14119
14262
  if (permission === "camera")
14120
14263
  await this.addCamera();
@@ -14123,7 +14266,7 @@ ${error.message}`;
14123
14266
  else if (permission === "location")
14124
14267
  await this.addLocation();
14125
14268
  else if (permission === "push")
14126
- await this.addPush();
14269
+ await this.addPush({ operation, env });
14127
14270
  }
14128
14271
  }
14129
14272
  async#applyDeepLinks(platform, { operation, env }) {
@@ -14142,7 +14285,7 @@ ${error.message}`;
14142
14285
  await this.#setUrlSchemesInIos(schemes);
14143
14286
  }
14144
14287
  if (domains.length > 0)
14145
- await this.#setAssociatedDomainsInIos(domains);
14288
+ await this.#setAssociatedDomainsInIos(domains, { operation, env });
14146
14289
  return;
14147
14290
  }
14148
14291
  if (platform === "android") {
@@ -14236,12 +14379,64 @@ ${error.message}`;
14236
14379
  this.#setPermissionsInAndroid(["ACCESS_COARSE_LOCATION", "ACCESS_FINE_LOCATION"]);
14237
14380
  this.#setFeaturesInAndroid(["android.hardware.location.gps"]);
14238
14381
  }
14239
- async addPush() {
14382
+ async addPush({ operation, env }) {
14240
14383
  await this.#setPermissionInIos({
14241
14384
  userNotificationsUsageDescription: "$(PRODUCT_NAME) uses notifications to keep you updated."
14242
14385
  });
14386
+ await Promise.all([
14387
+ this.project.ios.updateInfoPlist(this.iosTargetName, "Debug", { UIBackgroundModes: ["remote-notification"] }),
14388
+ this.project.ios.updateInfoPlist(this.iosTargetName, "Release", { UIBackgroundModes: ["remote-notification"] }),
14389
+ this.#writeEntitlementsInIos(this.target.deepLinks?.domains ?? [], { operation, env }),
14390
+ this.#ensurePushAppDelegateInIos()
14391
+ ]);
14243
14392
  this.#setPermissionsInAndroid(["POST_NOTIFICATIONS"]);
14244
14393
  }
14394
+ async#ensurePushAppDelegateInIos() {
14395
+ const appDelegatePath = path41.join(this.app.cwdPath, this.iosProjectPath, "App/AppDelegate.swift");
14396
+ if (!await Bun.file(appDelegatePath).exists())
14397
+ return;
14398
+ const editor = await FileEditor.create(appDelegatePath);
14399
+ let content = editor.getContent();
14400
+ if (!content.includes("import FirebaseCore")) {
14401
+ content = content.replace("import Capacitor", `import Capacitor
14402
+ import FirebaseCore`);
14403
+ }
14404
+ if (!content.includes("import FirebaseMessaging")) {
14405
+ content = content.replace("import FirebaseCore", `import FirebaseCore
14406
+ import FirebaseMessaging`);
14407
+ }
14408
+ if (!content.includes("FirebaseApp.configure()")) {
14409
+ content = content.replace(/\n(\s*)return true/, `
14410
+ $1FirebaseApp.configure()
14411
+
14412
+ $1return true`);
14413
+ }
14414
+ if (!content.includes("didRegisterForRemoteNotificationsWithDeviceToken")) {
14415
+ const delegateMethods = `
14416
+ func application(_ application: UIApplication,
14417
+ didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
14418
+ Messaging.messaging().apnsToken = deviceToken
14419
+ NotificationCenter.default.post(
14420
+ name: .capacitorDidRegisterForRemoteNotifications,
14421
+ object: deviceToken
14422
+ )
14423
+ }
14424
+
14425
+ func application(_ application: UIApplication,
14426
+ didFailToRegisterForRemoteNotificationsWithError error: Error) {
14427
+ NotificationCenter.default.post(
14428
+ name: .capacitorDidFailToRegisterForRemoteNotifications,
14429
+ object: error
14430
+ )
14431
+ }
14432
+ `;
14433
+ content = content.replace(`
14434
+ func applicationWillResignActive`, `${delegateMethods}
14435
+ func applicationWillResignActive`);
14436
+ }
14437
+ if (content !== editor.getContent())
14438
+ await editor.setContent(content).save();
14439
+ }
14245
14440
  async#setPermissionInIos(permissions) {
14246
14441
  const updateNs = Object.fromEntries(Object.entries(permissions).map(([key, value]) => [`NS${capitalize5(key)}`, value]));
14247
14442
  await Promise.all([
@@ -14259,25 +14454,35 @@ ${error.message}`;
14259
14454
  this.project.ios.updateInfoPlist(this.iosTargetName, "Release", { CFBundleURLTypes: urlTypes })
14260
14455
  ]);
14261
14456
  }
14262
- async#setAssociatedDomainsInIos(domains) {
14457
+ async#setAssociatedDomainsInIos(domains, { operation, env }) {
14458
+ await this.#writeEntitlementsInIos(domains, { operation, env });
14459
+ }
14460
+ async#writeEntitlementsInIos(domains, runConfig) {
14263
14461
  const entitlementsRelPath = "App/App.entitlements";
14264
14462
  const entitlementsPath = path41.join(this.app.cwdPath, this.iosProjectPath, entitlementsRelPath);
14265
14463
  const values = domains.map((domain) => `applinks:${domain}`);
14464
+ const usesPush = this.target.permissions?.includes("push") ?? false;
14465
+ const apsEnvironment = resolveIosApnsEnvironment(runConfig);
14266
14466
  const body = [
14267
14467
  '<?xml version="1.0" encoding="UTF-8"?>',
14268
14468
  '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
14269
14469
  '<plist version="1.0">',
14270
14470
  "<dict>",
14271
- " <key>com.apple.developer.associated-domains</key>",
14272
- " <array>",
14273
- ...values.map((value) => ` <string>${value}</string>`),
14274
- " </array>",
14471
+ ...usesPush ? [" <key>aps-environment</key>", ` <string>${apsEnvironment}</string>`] : [],
14472
+ ...values.length > 0 ? [
14473
+ " <key>com.apple.developer.associated-domains</key>",
14474
+ " <array>",
14475
+ ...values.map((value) => ` <string>${value}</string>`),
14476
+ " </array>"
14477
+ ] : [],
14275
14478
  "</dict>",
14276
14479
  "</plist>",
14277
14480
  ""
14278
14481
  ].join(`
14279
14482
  `);
14280
- await writeFile2(entitlementsPath, body);
14483
+ const currentBody = await Bun.file(entitlementsPath).exists() ? await Bun.file(entitlementsPath).text() : undefined;
14484
+ if (currentBody !== body)
14485
+ await writeFile2(entitlementsPath, body);
14281
14486
  await this.#setCodeSignEntitlementsInIos(entitlementsRelPath);
14282
14487
  }
14283
14488
  async#setCodeSignEntitlementsInIos(entitlementsRelPath) {
@@ -14381,7 +14586,7 @@ ${filter}$1`);
14381
14586
  for (const permission of permissions) {
14382
14587
  if (this.#hasPermissionInAndroid(permission)) {
14383
14588
  this.app.logger.info(`${permission} already exists in android`);
14384
- return this;
14589
+ continue;
14385
14590
  }
14386
14591
  this.app.logger.info(`Adding ${permission} to android`);
14387
14592
  this.project.android.getAndroidManifest().injectFragment("manifest", `<uses-permission android:name="android.permission.${permission}" />`);
@@ -14397,7 +14602,7 @@ ${filter}$1`);
14397
14602
  return Array.from(usesPermission).map((permission) => permission.getAttribute("android:name"));
14398
14603
  }
14399
14604
  #hasPermissionInAndroid(permission) {
14400
- return this.#getPermissionsInAndroid().includes(permission);
14605
+ return this.#getPermissionsInAndroid().includes(`android.permission.${permission}`);
14401
14606
  }
14402
14607
  }
14403
14608
  // pkgs/@akanjs/devkit/commandDecorators/targetMeta.ts