@akanjs/cli 2.3.11-rc.7 → 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}`);
@@ -3689,8 +3755,30 @@ class AppExecutor extends SysExecutor {
3689
3755
  });
3690
3756
  if (write)
3691
3757
  await this.syncAssets(scanInfo.getScanResult().libDeps);
3758
+ if (write)
3759
+ await this.#syncFirebaseMessagingSw();
3692
3760
  return scanInfo;
3693
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
+ }
3694
3782
  async increaseBuildNum() {
3695
3783
  await increaseBuildNum(this);
3696
3784
  }
@@ -8719,7 +8807,7 @@ Caused by: ${ApplicationBuildReporter.formatError(error.cause)}` : "";
8719
8807
  }
8720
8808
  }
8721
8809
  // pkgs/@akanjs/devkit/applicationBuildRunner.ts
8722
- import { mkdir as mkdir8, rm as rm3 } from "fs/promises";
8810
+ import { mkdir as mkdir8, rm as rm4 } from "fs/promises";
8723
8811
  import path37 from "path";
8724
8812
 
8725
8813
  // pkgs/@akanjs/devkit/frontendBuild/allRoutesBuilder.ts
@@ -10477,7 +10565,7 @@ class AllRoutesBuilder {
10477
10565
  }
10478
10566
  }
10479
10567
  // pkgs/@akanjs/devkit/frontendBuild/csrArtifactBuilder.ts
10480
- import { mkdir as mkdir5, rm, unlink } from "fs/promises";
10568
+ import { mkdir as mkdir5, rm as rm2, unlink } from "fs/promises";
10481
10569
  import path24 from "path";
10482
10570
 
10483
10571
  // pkgs/@akanjs/devkit/frontendBuild/pagesEntrySourceGenerator.ts
@@ -10607,7 +10695,7 @@ class CsrArtifactBuilder {
10607
10695
  const artifact = await this.#loadCsrArtifact();
10608
10696
  const csrBasePaths = [...akanConfig2.basePaths];
10609
10697
  const htmlEntries = csrBasePaths.length > 0 ? csrBasePaths : ["index"];
10610
- await rm(this.#outputDir, { recursive: true, force: true });
10698
+ await rm2(this.#outputDir, { recursive: true, force: true });
10611
10699
  await mkdir5(path24.join(this.#app.cwdPath, ".akan/generated/csr"), { recursive: true });
10612
10700
  const generatedHtmlFiles = Object.fromEntries(htmlEntries.map((basePath2) => this.#createHtmlFile(basePath2)));
10613
10701
  const result = await Bun.build({
@@ -11341,7 +11429,7 @@ class DevChangePlanner {
11341
11429
  }
11342
11430
  var uniqueResolved = (files) => [...new Set(files.map((file) => path27.resolve(file)))].sort();
11343
11431
  // pkgs/@akanjs/devkit/frontendBuild/devGeneratedIndexSync.ts
11344
- 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";
11345
11433
  import path28 from "path";
11346
11434
  var BARREL_FACETS2 = new Set(["common", "srvkit", "ui", "webkit"]);
11347
11435
  var FACET_SOURCE_FILE_RE = /\.(ts|tsx)$/;
@@ -11424,7 +11512,7 @@ class DevGeneratedIndexSync {
11424
11512
  if (content === null) {
11425
11513
  if (!await exists(indexPath))
11426
11514
  return false;
11427
- await rm2(indexPath, { force: true });
11515
+ await rm3(indexPath, { force: true });
11428
11516
  return true;
11429
11517
  }
11430
11518
  const current = await readFile(indexPath, "utf8").catch(() => null);
@@ -12571,7 +12659,7 @@ class ApplicationBuildRunner {
12571
12659
  await this.#app.getPageKeys({ refresh: true });
12572
12660
  const { typecheckDir, tsconfigPath } = await this.#writeTypecheckTsconfig({ incremental });
12573
12661
  if (clean)
12574
- await rm3(path37.join(typecheckDir, "tsconfig.tsbuildinfo"), { force: true });
12662
+ await rm4(path37.join(typecheckDir, "tsconfig.tsbuildinfo"), { force: true });
12575
12663
  await this.#checkProjectInChildProcess(tsconfigPath);
12576
12664
  }
12577
12665
  async#runPhase(id, label, task, summarize, options = {}) {
@@ -12771,7 +12859,7 @@ void run().catch((error) => {
12771
12859
  }
12772
12860
  }
12773
12861
  // pkgs/@akanjs/devkit/applicationReleasePackager.ts
12774
- import { cp, mkdir as mkdir9, rm as rm4 } from "fs/promises";
12862
+ import { cp, mkdir as mkdir9, rm as rm5 } from "fs/promises";
12775
12863
  import path38 from "path";
12776
12864
 
12777
12865
  // pkgs/@akanjs/devkit/uploadRelease.ts
@@ -12878,7 +12966,7 @@ class ApplicationReleasePackager {
12878
12966
  const platformVersion = akanConfig2.mobile.version;
12879
12967
  const buildRoot = `${this.#app.workspace.workspaceRoot}/releases/builds/${this.#app.name}`;
12880
12968
  if (await FileSys.dirExists(buildRoot))
12881
- await rm4(buildRoot, { recursive: true, force: true });
12969
+ await rm5(buildRoot, { recursive: true, force: true });
12882
12970
  await mkdir9(buildRoot, { recursive: true });
12883
12971
  if (rebuild || !await FileSys.dirExists(`${this.#app.dist.cwdPath}/backend`))
12884
12972
  await this.#build();
@@ -12887,7 +12975,7 @@ class ApplicationReleasePackager {
12887
12975
  await mkdir9(buildPath, { recursive: true });
12888
12976
  await cp(`${this.#app.dist.cwdPath}/backend`, `${buildPath}/backend`, { recursive: true });
12889
12977
  await cp(this.#app.dist.cwdPath, buildRoot, { recursive: true });
12890
- await rm4(`${buildRoot}/frontend/.next`, { recursive: true, force: true });
12978
+ await rm5(`${buildRoot}/frontend/.next`, { recursive: true, force: true });
12891
12979
  const releaseRoot = this.#app.workspace.workspaceRoot;
12892
12980
  const releaseArchivePath = path38.relative(releaseRoot, `${releaseRoot}/releases/builds/${this.#app.name}-release.tar.gz`).split(path38.sep).join("/");
12893
12981
  await this.#app.workspace.spawn("tar", ["-zcf", releaseArchivePath, "-C", buildRoot, "./"], { cwd: releaseRoot });
@@ -12903,7 +12991,7 @@ class ApplicationReleasePackager {
12903
12991
  `${this.#app.workspace.workspaceRoot}/releases/builds/${this.#app.name}-appBuild.zip`,
12904
12992
  "./csr"
12905
12993
  ]);
12906
- await rm4("./csr", { recursive: true, force: true });
12994
+ await rm5("./csr", { recursive: true, force: true });
12907
12995
  }
12908
12996
  async#writeSourceArchive({ readme }) {
12909
12997
  const sourceRoot = `${this.#app.workspace.workspaceRoot}/releases/sources/${this.#app.name}`;
@@ -12914,7 +13002,7 @@ class ApplicationReleasePackager {
12914
13002
  await Promise.all([".next", "ios", "android", "public/libs"].map(async (path39) => {
12915
13003
  const targetPath = `${sourceRoot}/apps/${this.#app.name}/${path39}`;
12916
13004
  if (await FileSys.dirExists(targetPath))
12917
- await rm4(targetPath, { recursive: true, force: true });
13005
+ await rm5(targetPath, { recursive: true, force: true });
12918
13006
  }));
12919
13007
  const syncPaths = [".husky", ".gitignore", "package.json"];
12920
13008
  await Promise.all(syncPaths.map((path39) => cp(`${this.#app.workspace.cwdPath}/${path39}`, `${sourceRoot}/${path39}`, { recursive: true })));
@@ -12929,7 +13017,7 @@ class ApplicationReleasePackager {
12929
13017
  const maxRetry = 3;
12930
13018
  for (let i = 0;i < maxRetry; i++) {
12931
13019
  try {
12932
- await rm4(sourceRoot, { recursive: true, force: true });
13020
+ await rm5(sourceRoot, { recursive: true, force: true });
12933
13021
  } catch {}
12934
13022
  }
12935
13023
  }
@@ -13162,7 +13250,7 @@ class Builder {
13162
13250
  }
13163
13251
  }
13164
13252
  // pkgs/@akanjs/devkit/capacitorApp.ts
13165
- 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";
13166
13254
  import os from "os";
13167
13255
  import path41 from "path";
13168
13256
  import { select as select2 } from "@inquirer/prompts";
@@ -13373,7 +13461,7 @@ var rootCapacitorConfigFilenames = [
13373
13461
  ];
13374
13462
  var rootCapacitorConfigPaths = (appRoot) => rootCapacitorConfigFilenames.map((file) => path41.join(appRoot, file));
13375
13463
  async function clearRootCapacitorConfigs(appRoot) {
13376
- await Promise.all(rootCapacitorConfigPaths(appRoot).map((file) => rm5(file, { force: true })));
13464
+ await Promise.all(rootCapacitorConfigPaths(appRoot).map((file) => rm6(file, { force: true })));
13377
13465
  }
13378
13466
  async function writeRootCapacitorConfig(appRoot, content) {
13379
13467
  await clearRootCapacitorConfigs(appRoot);
@@ -13394,6 +13482,7 @@ var getLocalIP = () => {
13394
13482
  var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
13395
13483
  var asString = (value) => typeof value === "string" ? value : undefined;
13396
13484
  var firstString = (...values) => values.find((value) => typeof value === "string");
13485
+ var resolveIosApnsEnvironment = ({ operation }) => operation === "release" ? "production" : "development";
13397
13486
  var scoreIosDeviceTarget = (target) => {
13398
13487
  const state = target.state?.toLowerCase() ?? "";
13399
13488
  return (state.includes("available") ? 4 : 0) + (state.includes("wired") ? 2 : 0) + (state.includes("paired") ? 1 : 0);
@@ -13699,6 +13788,8 @@ function materializeCapacitorConfig(target, { operation, localServerUrl, localIp
13699
13788
  const experimentalConfig = isRecord2(experimental) ? experimental : undefined;
13700
13789
  const pluginsConfig = isRecord2(plugins) ? plugins : {};
13701
13790
  const keyboardPluginConfig = isRecord2(pluginsConfig.Keyboard) ? pluginsConfig.Keyboard : {};
13791
+ const pushNotificationsPluginConfig = isRecord2(pluginsConfig.PushNotifications) ? pluginsConfig.PushNotifications : {};
13792
+ const usesPushNotifications = target.permissions?.includes("push") ?? false;
13702
13793
  const config = {
13703
13794
  ...capacitorConfig,
13704
13795
  appId,
@@ -13707,6 +13798,12 @@ function materializeCapacitorConfig(target, { operation, localServerUrl, localIp
13707
13798
  plugins: {
13708
13799
  CapacitorCookies: { enabled: true },
13709
13800
  ...pluginsConfig,
13801
+ ...usesPushNotifications || isRecord2(pluginsConfig.PushNotifications) ? {
13802
+ PushNotifications: {
13803
+ ...usesPushNotifications ? { presentationOptions: ["badge", "sound", "alert"] } : {},
13804
+ ...pushNotificationsPluginConfig
13805
+ }
13806
+ } : {},
13710
13807
  Keyboard: {
13711
13808
  resize: "none",
13712
13809
  ...keyboardPluginConfig
@@ -13778,9 +13875,9 @@ class CapacitorApp {
13778
13875
  await mkdir11(this.targetRoot, { recursive: true });
13779
13876
  if (regenerate) {
13780
13877
  if (!platform || platform === "ios")
13781
- 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 });
13782
13879
  if (!platform || platform === "android")
13783
- 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 });
13784
13881
  }
13785
13882
  const project = this.project;
13786
13883
  await this.project.load();
@@ -13802,7 +13899,7 @@ class CapacitorApp {
13802
13899
  await this.#prepareTargetAssets();
13803
13900
  await this.#prepareExternalFiles("ios");
13804
13901
  await this.#applyIosMetadata();
13805
- await this.#applyPermissions();
13902
+ await this.#applyPermissions({ operation, env });
13806
13903
  await this.#applyDeepLinks("ios", { operation, env });
13807
13904
  await this.project.commit();
13808
13905
  await this.#generateAssets({ operation, env });
@@ -13945,7 +14042,7 @@ ${error.message}`;
13945
14042
  await this.#prepareTargetAssets();
13946
14043
  await this.#prepareExternalFiles("android");
13947
14044
  await this.#applyAndroidMetadata();
13948
- await this.#applyPermissions();
14045
+ await this.#applyPermissions({ operation, env });
13949
14046
  await this.#applyDeepLinks("android", { operation, env });
13950
14047
  await this.project.commit();
13951
14048
  await this.#generateAssets({ operation, env });
@@ -14079,7 +14176,7 @@ ${error.message}`;
14079
14176
  const htmlSource = path41.join(this.app.dist.cwdPath, "csr", targetHtmlFilename(this.target));
14080
14177
  if (!await Bun.file(htmlSource).exists())
14081
14178
  throw new Error(`CSR html for mobile target '${this.target.name}' not found: ${htmlSource}`);
14082
- await rm5(this.targetWebRoot, { recursive: true, force: true });
14179
+ await rm6(this.targetWebRoot, { recursive: true, force: true });
14083
14180
  await mkdir11(this.targetWebRoot, { recursive: true });
14084
14181
  await Bun.write(path41.join(this.targetWebRoot, "index.html"), this.#injectMobileTargetMeta(await Bun.file(htmlSource).text()));
14085
14182
  }
@@ -14160,7 +14257,7 @@ ${error.message}`;
14160
14257
  await this.project.android.setVersionCode(this.target.buildNum);
14161
14258
  await this.project.android.setAppName(this.target.appName);
14162
14259
  }
14163
- async#applyPermissions() {
14260
+ async#applyPermissions({ operation, env }) {
14164
14261
  for (const permission of this.target.permissions ?? []) {
14165
14262
  if (permission === "camera")
14166
14263
  await this.addCamera();
@@ -14169,7 +14266,7 @@ ${error.message}`;
14169
14266
  else if (permission === "location")
14170
14267
  await this.addLocation();
14171
14268
  else if (permission === "push")
14172
- await this.addPush();
14269
+ await this.addPush({ operation, env });
14173
14270
  }
14174
14271
  }
14175
14272
  async#applyDeepLinks(platform, { operation, env }) {
@@ -14188,7 +14285,7 @@ ${error.message}`;
14188
14285
  await this.#setUrlSchemesInIos(schemes);
14189
14286
  }
14190
14287
  if (domains.length > 0)
14191
- await this.#setAssociatedDomainsInIos(domains);
14288
+ await this.#setAssociatedDomainsInIos(domains, { operation, env });
14192
14289
  return;
14193
14290
  }
14194
14291
  if (platform === "android") {
@@ -14282,12 +14379,64 @@ ${error.message}`;
14282
14379
  this.#setPermissionsInAndroid(["ACCESS_COARSE_LOCATION", "ACCESS_FINE_LOCATION"]);
14283
14380
  this.#setFeaturesInAndroid(["android.hardware.location.gps"]);
14284
14381
  }
14285
- async addPush() {
14382
+ async addPush({ operation, env }) {
14286
14383
  await this.#setPermissionInIos({
14287
14384
  userNotificationsUsageDescription: "$(PRODUCT_NAME) uses notifications to keep you updated."
14288
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
+ ]);
14289
14392
  this.#setPermissionsInAndroid(["POST_NOTIFICATIONS"]);
14290
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
+ }
14291
14440
  async#setPermissionInIos(permissions) {
14292
14441
  const updateNs = Object.fromEntries(Object.entries(permissions).map(([key, value]) => [`NS${capitalize5(key)}`, value]));
14293
14442
  await Promise.all([
@@ -14305,25 +14454,35 @@ ${error.message}`;
14305
14454
  this.project.ios.updateInfoPlist(this.iosTargetName, "Release", { CFBundleURLTypes: urlTypes })
14306
14455
  ]);
14307
14456
  }
14308
- async#setAssociatedDomainsInIos(domains) {
14457
+ async#setAssociatedDomainsInIos(domains, { operation, env }) {
14458
+ await this.#writeEntitlementsInIos(domains, { operation, env });
14459
+ }
14460
+ async#writeEntitlementsInIos(domains, runConfig) {
14309
14461
  const entitlementsRelPath = "App/App.entitlements";
14310
14462
  const entitlementsPath = path41.join(this.app.cwdPath, this.iosProjectPath, entitlementsRelPath);
14311
14463
  const values = domains.map((domain) => `applinks:${domain}`);
14464
+ const usesPush = this.target.permissions?.includes("push") ?? false;
14465
+ const apsEnvironment = resolveIosApnsEnvironment(runConfig);
14312
14466
  const body = [
14313
14467
  '<?xml version="1.0" encoding="UTF-8"?>',
14314
14468
  '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
14315
14469
  '<plist version="1.0">',
14316
14470
  "<dict>",
14317
- " <key>com.apple.developer.associated-domains</key>",
14318
- " <array>",
14319
- ...values.map((value) => ` <string>${value}</string>`),
14320
- " </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
+ ] : [],
14321
14478
  "</dict>",
14322
14479
  "</plist>",
14323
14480
  ""
14324
14481
  ].join(`
14325
14482
  `);
14326
- 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);
14327
14486
  await this.#setCodeSignEntitlementsInIos(entitlementsRelPath);
14328
14487
  }
14329
14488
  async#setCodeSignEntitlementsInIos(entitlementsRelPath) {
@@ -14427,7 +14586,7 @@ ${filter}$1`);
14427
14586
  for (const permission of permissions) {
14428
14587
  if (this.#hasPermissionInAndroid(permission)) {
14429
14588
  this.app.logger.info(`${permission} already exists in android`);
14430
- return this;
14589
+ continue;
14431
14590
  }
14432
14591
  this.app.logger.info(`Adding ${permission} to android`);
14433
14592
  this.project.android.getAndroidManifest().injectFragment("manifest", `<uses-permission android:name="android.permission.${permission}" />`);
@@ -14443,7 +14602,7 @@ ${filter}$1`);
14443
14602
  return Array.from(usesPermission).map((permission) => permission.getAttribute("android:name"));
14444
14603
  }
14445
14604
  #hasPermissionInAndroid(permission) {
14446
- return this.#getPermissionsInAndroid().includes(permission);
14605
+ return this.#getPermissionsInAndroid().includes(`android.permission.${permission}`);
14447
14606
  }
14448
14607
  }
14449
14608
  // pkgs/@akanjs/devkit/commandDecorators/targetMeta.ts
package/index.js CHANGED
@@ -664,6 +664,7 @@ import {
664
664
  import { readFileSync as readFileSync3 } from "fs";
665
665
  import { copyFile, mkdir as mkdir2, readdir as readDirEntries, stat as stat2 } from "fs/promises";
666
666
  import path7 from "path";
667
+ import { pathToFileURL } from "url";
667
668
  import {
668
669
  capitalize,
669
670
  isRouteSourceFile,
@@ -1105,6 +1106,63 @@ var decreaseBuildNum = async (app) => {
1105
1106
  const akanConfigContent = akanConfig.replace(`buildNum: ${appConfig.mobile.buildNum}`, `buildNum: ${appConfig.mobile.buildNum - 1}`);
1106
1107
  fs.writeFileSync(akanConfigPath, akanConfigContent);
1107
1108
  };
1109
+ // pkgs/@akanjs/devkit/firebaseMessagingSw.ts
1110
+ var FIREBASE_WEB_SDK_VERSION = "12.13.0";
1111
+ function normalizeFirebaseClientConfig(config) {
1112
+ if (!config || typeof config !== "object")
1113
+ return null;
1114
+ const value = config;
1115
+ if (typeof value.apiKey !== "string" || typeof value.projectId !== "string" || typeof value.messagingSenderId !== "string" || typeof value.appId !== "string") {
1116
+ return null;
1117
+ }
1118
+ return {
1119
+ apiKey: value.apiKey,
1120
+ ...typeof value.authDomain === "string" ? { authDomain: value.authDomain } : {},
1121
+ projectId: value.projectId,
1122
+ ...typeof value.storageBucket === "string" ? { storageBucket: value.storageBucket } : {},
1123
+ messagingSenderId: value.messagingSenderId,
1124
+ appId: value.appId
1125
+ };
1126
+ }
1127
+ function createFirebaseMessagingServiceWorker(config) {
1128
+ const configJson = JSON.stringify(config);
1129
+ return `/* Generated by Akan.js. Do not edit. */
1130
+ const firebaseConfig = ${configJson};
1131
+
1132
+ if (firebaseConfig) {
1133
+ importScripts("https://www.gstatic.com/firebasejs/${FIREBASE_WEB_SDK_VERSION}/firebase-app-compat.js");
1134
+ importScripts("https://www.gstatic.com/firebasejs/${FIREBASE_WEB_SDK_VERSION}/firebase-messaging-compat.js");
1135
+
1136
+ firebase.initializeApp(firebaseConfig);
1137
+ const messaging = firebase.messaging();
1138
+
1139
+ const notificationUrl = (payload) =>
1140
+ payload?.data?.url || payload?.fcmOptions?.link || payload?.notification?.click_action;
1141
+
1142
+ messaging.onBackgroundMessage((payload) => {
1143
+ const title = payload?.notification?.title || "";
1144
+ const options = {
1145
+ body: payload?.notification?.body,
1146
+ icon: payload?.notification?.icon,
1147
+ image: payload?.notification?.image,
1148
+ data: {
1149
+ url: notificationUrl(payload),
1150
+ FCM_MSG: payload,
1151
+ },
1152
+ };
1153
+ self.registration.showNotification(title, options);
1154
+ });
1155
+ }
1156
+
1157
+ self.addEventListener("notificationclick", (event) => {
1158
+ const url = event.notification?.data?.url || event.notification?.data?.FCM_MSG?.data?.url;
1159
+ event.notification?.close();
1160
+ if (!url) return;
1161
+ event.waitUntil(clients.openWindow(url));
1162
+ });
1163
+ `;
1164
+ }
1165
+
1108
1166
  // pkgs/@akanjs/devkit/getDirname.ts
1109
1167
  import path2 from "path";
1110
1168
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -1390,6 +1448,7 @@ ${errorText}, ${warningText} found`];
1390
1448
 
1391
1449
  // pkgs/@akanjs/devkit/scanInfo.ts
1392
1450
  import path5 from "path";
1451
+ import { rm } from "fs/promises";
1393
1452
 
1394
1453
  // pkgs/@akanjs/devkit/dependencyScanner.ts
1395
1454
  import { builtinModules } from "module";
@@ -1711,6 +1770,7 @@ var appRootAllowedFiles = new Set([
1711
1770
  "tsconfig.json",
1712
1771
  "tsconfig.tsbuildinfo"
1713
1772
  ]);
1773
+ var generatedRootCapacitorConfigFiles = ["capacitor.config.js", "capacitor.config.json"];
1714
1774
  var appRootAllowedDirs = new Set([
1715
1775
  ".akan",
1716
1776
  "android",
@@ -1754,11 +1814,17 @@ var rootSignalTestFilePattern = /^[A-Za-z][A-Za-z0-9_-]*\.signal\.(test|spec)\.(
1754
1814
  var isAllowedTestFile = (filename) => testFilePattern.test(filename);
1755
1815
  var isAllowedLibRootFile = (filename) => libRootAllowedFiles.has(filename) || rootSignalTestFilePattern.test(filename);
1756
1816
  var getScanPath = (exec, relativePath) => path5.posix.join(`${exec.type}s`, exec.name, relativePath.split(path5.sep).join("/"));
1817
+ async function clearGeneratedRootCapacitorConfigs(exec) {
1818
+ if (exec.type !== "app")
1819
+ return;
1820
+ await Promise.all(generatedRootCapacitorConfigFiles.map((filename) => rm(exec.getPath(filename), { force: true })));
1821
+ }
1757
1822
  var getModuleNameFromPath = (kind, modulePath) => {
1758
1823
  const dirname = path5.basename(modulePath);
1759
1824
  return kind === "service" ? dirname.replace(/^_+/, "") : dirname;
1760
1825
  };
1761
1826
  async function assertScanConvention(exec, libRoot) {
1827
+ await clearGeneratedRootCapacitorConfigs(exec);
1762
1828
  const violations = [];
1763
1829
  const addViolation = (relativePath, reason) => {
1764
1830
  violations.push(`${getScanPath(exec, relativePath)}: ${reason}`);
@@ -3687,8 +3753,30 @@ class AppExecutor extends SysExecutor {
3687
3753
  });
3688
3754
  if (write)
3689
3755
  await this.syncAssets(scanInfo.getScanResult().libDeps);
3756
+ if (write)
3757
+ await this.#syncFirebaseMessagingSw();
3690
3758
  return scanInfo;
3691
3759
  }
3760
+ async#syncFirebaseMessagingSw() {
3761
+ const swRelPath = "public/firebase-messaging-sw.js";
3762
+ if (await FileSys.fileExists(this.getPath(swRelPath)))
3763
+ return;
3764
+ const envClientPath = path7.join(this.cwdPath, "env", "env.client.ts");
3765
+ if (!await FileSys.fileExists(envClientPath))
3766
+ return;
3767
+ let firebaseConfig = null;
3768
+ try {
3769
+ const envUrl = pathToFileURL(envClientPath);
3770
+ envUrl.searchParams.set("t", String(Date.now()));
3771
+ const envModule = await import(envUrl.href);
3772
+ firebaseConfig = normalizeFirebaseClientConfig(envModule.env?.firebase);
3773
+ } catch {
3774
+ return;
3775
+ }
3776
+ if (!firebaseConfig)
3777
+ return;
3778
+ await this.writeFile(swRelPath, createFirebaseMessagingServiceWorker(firebaseConfig), { overwrite: false });
3779
+ }
3692
3780
  async increaseBuildNum() {
3693
3781
  await increaseBuildNum(this);
3694
3782
  }
@@ -8717,7 +8805,7 @@ Caused by: ${ApplicationBuildReporter.formatError(error.cause)}` : "";
8717
8805
  }
8718
8806
  }
8719
8807
  // pkgs/@akanjs/devkit/applicationBuildRunner.ts
8720
- import { mkdir as mkdir8, rm as rm3 } from "fs/promises";
8808
+ import { mkdir as mkdir8, rm as rm4 } from "fs/promises";
8721
8809
  import path37 from "path";
8722
8810
 
8723
8811
  // pkgs/@akanjs/devkit/frontendBuild/allRoutesBuilder.ts
@@ -10475,7 +10563,7 @@ class AllRoutesBuilder {
10475
10563
  }
10476
10564
  }
10477
10565
  // pkgs/@akanjs/devkit/frontendBuild/csrArtifactBuilder.ts
10478
- import { mkdir as mkdir5, rm, unlink } from "fs/promises";
10566
+ import { mkdir as mkdir5, rm as rm2, unlink } from "fs/promises";
10479
10567
  import path24 from "path";
10480
10568
 
10481
10569
  // pkgs/@akanjs/devkit/frontendBuild/pagesEntrySourceGenerator.ts
@@ -10605,7 +10693,7 @@ class CsrArtifactBuilder {
10605
10693
  const artifact = await this.#loadCsrArtifact();
10606
10694
  const csrBasePaths = [...akanConfig2.basePaths];
10607
10695
  const htmlEntries = csrBasePaths.length > 0 ? csrBasePaths : ["index"];
10608
- await rm(this.#outputDir, { recursive: true, force: true });
10696
+ await rm2(this.#outputDir, { recursive: true, force: true });
10609
10697
  await mkdir5(path24.join(this.#app.cwdPath, ".akan/generated/csr"), { recursive: true });
10610
10698
  const generatedHtmlFiles = Object.fromEntries(htmlEntries.map((basePath2) => this.#createHtmlFile(basePath2)));
10611
10699
  const result = await Bun.build({
@@ -11339,7 +11427,7 @@ class DevChangePlanner {
11339
11427
  }
11340
11428
  var uniqueResolved = (files) => [...new Set(files.map((file) => path27.resolve(file)))].sort();
11341
11429
  // pkgs/@akanjs/devkit/frontendBuild/devGeneratedIndexSync.ts
11342
- import { mkdir as mkdir6, readdir as readdir2, readFile, rm as rm2, stat as stat3, writeFile } from "fs/promises";
11430
+ import { mkdir as mkdir6, readdir as readdir2, readFile, rm as rm3, stat as stat3, writeFile } from "fs/promises";
11343
11431
  import path28 from "path";
11344
11432
  var BARREL_FACETS2 = new Set(["common", "srvkit", "ui", "webkit"]);
11345
11433
  var FACET_SOURCE_FILE_RE = /\.(ts|tsx)$/;
@@ -11422,7 +11510,7 @@ class DevGeneratedIndexSync {
11422
11510
  if (content === null) {
11423
11511
  if (!await exists(indexPath))
11424
11512
  return false;
11425
- await rm2(indexPath, { force: true });
11513
+ await rm3(indexPath, { force: true });
11426
11514
  return true;
11427
11515
  }
11428
11516
  const current = await readFile(indexPath, "utf8").catch(() => null);
@@ -12569,7 +12657,7 @@ class ApplicationBuildRunner {
12569
12657
  await this.#app.getPageKeys({ refresh: true });
12570
12658
  const { typecheckDir, tsconfigPath } = await this.#writeTypecheckTsconfig({ incremental });
12571
12659
  if (clean)
12572
- await rm3(path37.join(typecheckDir, "tsconfig.tsbuildinfo"), { force: true });
12660
+ await rm4(path37.join(typecheckDir, "tsconfig.tsbuildinfo"), { force: true });
12573
12661
  await this.#checkProjectInChildProcess(tsconfigPath);
12574
12662
  }
12575
12663
  async#runPhase(id, label, task, summarize, options = {}) {
@@ -12769,7 +12857,7 @@ void run().catch((error) => {
12769
12857
  }
12770
12858
  }
12771
12859
  // pkgs/@akanjs/devkit/applicationReleasePackager.ts
12772
- import { cp, mkdir as mkdir9, rm as rm4 } from "fs/promises";
12860
+ import { cp, mkdir as mkdir9, rm as rm5 } from "fs/promises";
12773
12861
  import path38 from "path";
12774
12862
 
12775
12863
  // pkgs/@akanjs/devkit/uploadRelease.ts
@@ -12876,7 +12964,7 @@ class ApplicationReleasePackager {
12876
12964
  const platformVersion = akanConfig2.mobile.version;
12877
12965
  const buildRoot = `${this.#app.workspace.workspaceRoot}/releases/builds/${this.#app.name}`;
12878
12966
  if (await FileSys.dirExists(buildRoot))
12879
- await rm4(buildRoot, { recursive: true, force: true });
12967
+ await rm5(buildRoot, { recursive: true, force: true });
12880
12968
  await mkdir9(buildRoot, { recursive: true });
12881
12969
  if (rebuild || !await FileSys.dirExists(`${this.#app.dist.cwdPath}/backend`))
12882
12970
  await this.#build();
@@ -12885,7 +12973,7 @@ class ApplicationReleasePackager {
12885
12973
  await mkdir9(buildPath, { recursive: true });
12886
12974
  await cp(`${this.#app.dist.cwdPath}/backend`, `${buildPath}/backend`, { recursive: true });
12887
12975
  await cp(this.#app.dist.cwdPath, buildRoot, { recursive: true });
12888
- await rm4(`${buildRoot}/frontend/.next`, { recursive: true, force: true });
12976
+ await rm5(`${buildRoot}/frontend/.next`, { recursive: true, force: true });
12889
12977
  const releaseRoot = this.#app.workspace.workspaceRoot;
12890
12978
  const releaseArchivePath = path38.relative(releaseRoot, `${releaseRoot}/releases/builds/${this.#app.name}-release.tar.gz`).split(path38.sep).join("/");
12891
12979
  await this.#app.workspace.spawn("tar", ["-zcf", releaseArchivePath, "-C", buildRoot, "./"], { cwd: releaseRoot });
@@ -12901,7 +12989,7 @@ class ApplicationReleasePackager {
12901
12989
  `${this.#app.workspace.workspaceRoot}/releases/builds/${this.#app.name}-appBuild.zip`,
12902
12990
  "./csr"
12903
12991
  ]);
12904
- await rm4("./csr", { recursive: true, force: true });
12992
+ await rm5("./csr", { recursive: true, force: true });
12905
12993
  }
12906
12994
  async#writeSourceArchive({ readme }) {
12907
12995
  const sourceRoot = `${this.#app.workspace.workspaceRoot}/releases/sources/${this.#app.name}`;
@@ -12912,7 +13000,7 @@ class ApplicationReleasePackager {
12912
13000
  await Promise.all([".next", "ios", "android", "public/libs"].map(async (path39) => {
12913
13001
  const targetPath = `${sourceRoot}/apps/${this.#app.name}/${path39}`;
12914
13002
  if (await FileSys.dirExists(targetPath))
12915
- await rm4(targetPath, { recursive: true, force: true });
13003
+ await rm5(targetPath, { recursive: true, force: true });
12916
13004
  }));
12917
13005
  const syncPaths = [".husky", ".gitignore", "package.json"];
12918
13006
  await Promise.all(syncPaths.map((path39) => cp(`${this.#app.workspace.cwdPath}/${path39}`, `${sourceRoot}/${path39}`, { recursive: true })));
@@ -12927,7 +13015,7 @@ class ApplicationReleasePackager {
12927
13015
  const maxRetry = 3;
12928
13016
  for (let i = 0;i < maxRetry; i++) {
12929
13017
  try {
12930
- await rm4(sourceRoot, { recursive: true, force: true });
13018
+ await rm5(sourceRoot, { recursive: true, force: true });
12931
13019
  } catch {}
12932
13020
  }
12933
13021
  }
@@ -13160,7 +13248,7 @@ class Builder {
13160
13248
  }
13161
13249
  }
13162
13250
  // pkgs/@akanjs/devkit/capacitorApp.ts
13163
- import { cp as cp2, mkdir as mkdir11, readFile as readFile2, rm as rm5, writeFile as writeFile2 } from "fs/promises";
13251
+ import { cp as cp2, mkdir as mkdir11, readFile as readFile2, rm as rm6, writeFile as writeFile2 } from "fs/promises";
13164
13252
  import os from "os";
13165
13253
  import path41 from "path";
13166
13254
  import { select as select2 } from "@inquirer/prompts";
@@ -13371,7 +13459,7 @@ var rootCapacitorConfigFilenames = [
13371
13459
  ];
13372
13460
  var rootCapacitorConfigPaths = (appRoot) => rootCapacitorConfigFilenames.map((file) => path41.join(appRoot, file));
13373
13461
  async function clearRootCapacitorConfigs(appRoot) {
13374
- await Promise.all(rootCapacitorConfigPaths(appRoot).map((file) => rm5(file, { force: true })));
13462
+ await Promise.all(rootCapacitorConfigPaths(appRoot).map((file) => rm6(file, { force: true })));
13375
13463
  }
13376
13464
  async function writeRootCapacitorConfig(appRoot, content) {
13377
13465
  await clearRootCapacitorConfigs(appRoot);
@@ -13392,6 +13480,7 @@ var getLocalIP = () => {
13392
13480
  var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
13393
13481
  var asString = (value) => typeof value === "string" ? value : undefined;
13394
13482
  var firstString = (...values) => values.find((value) => typeof value === "string");
13483
+ var resolveIosApnsEnvironment = ({ operation }) => operation === "release" ? "production" : "development";
13395
13484
  var scoreIosDeviceTarget = (target) => {
13396
13485
  const state = target.state?.toLowerCase() ?? "";
13397
13486
  return (state.includes("available") ? 4 : 0) + (state.includes("wired") ? 2 : 0) + (state.includes("paired") ? 1 : 0);
@@ -13697,6 +13786,8 @@ function materializeCapacitorConfig(target, { operation, localServerUrl, localIp
13697
13786
  const experimentalConfig = isRecord2(experimental) ? experimental : undefined;
13698
13787
  const pluginsConfig = isRecord2(plugins) ? plugins : {};
13699
13788
  const keyboardPluginConfig = isRecord2(pluginsConfig.Keyboard) ? pluginsConfig.Keyboard : {};
13789
+ const pushNotificationsPluginConfig = isRecord2(pluginsConfig.PushNotifications) ? pluginsConfig.PushNotifications : {};
13790
+ const usesPushNotifications = target.permissions?.includes("push") ?? false;
13700
13791
  const config = {
13701
13792
  ...capacitorConfig,
13702
13793
  appId,
@@ -13705,6 +13796,12 @@ function materializeCapacitorConfig(target, { operation, localServerUrl, localIp
13705
13796
  plugins: {
13706
13797
  CapacitorCookies: { enabled: true },
13707
13798
  ...pluginsConfig,
13799
+ ...usesPushNotifications || isRecord2(pluginsConfig.PushNotifications) ? {
13800
+ PushNotifications: {
13801
+ ...usesPushNotifications ? { presentationOptions: ["badge", "sound", "alert"] } : {},
13802
+ ...pushNotificationsPluginConfig
13803
+ }
13804
+ } : {},
13708
13805
  Keyboard: {
13709
13806
  resize: "none",
13710
13807
  ...keyboardPluginConfig
@@ -13776,9 +13873,9 @@ class CapacitorApp {
13776
13873
  await mkdir11(this.targetRoot, { recursive: true });
13777
13874
  if (regenerate) {
13778
13875
  if (!platform || platform === "ios")
13779
- await rm5(path41.join(this.app.cwdPath, this.iosRootPath), { recursive: true, force: true });
13876
+ await rm6(path41.join(this.app.cwdPath, this.iosRootPath), { recursive: true, force: true });
13780
13877
  if (!platform || platform === "android")
13781
- await rm5(path41.join(this.app.cwdPath, this.androidRootPath), { recursive: true, force: true });
13878
+ await rm6(path41.join(this.app.cwdPath, this.androidRootPath), { recursive: true, force: true });
13782
13879
  }
13783
13880
  const project = this.project;
13784
13881
  await this.project.load();
@@ -13800,7 +13897,7 @@ class CapacitorApp {
13800
13897
  await this.#prepareTargetAssets();
13801
13898
  await this.#prepareExternalFiles("ios");
13802
13899
  await this.#applyIosMetadata();
13803
- await this.#applyPermissions();
13900
+ await this.#applyPermissions({ operation, env });
13804
13901
  await this.#applyDeepLinks("ios", { operation, env });
13805
13902
  await this.project.commit();
13806
13903
  await this.#generateAssets({ operation, env });
@@ -13943,7 +14040,7 @@ ${error.message}`;
13943
14040
  await this.#prepareTargetAssets();
13944
14041
  await this.#prepareExternalFiles("android");
13945
14042
  await this.#applyAndroidMetadata();
13946
- await this.#applyPermissions();
14043
+ await this.#applyPermissions({ operation, env });
13947
14044
  await this.#applyDeepLinks("android", { operation, env });
13948
14045
  await this.project.commit();
13949
14046
  await this.#generateAssets({ operation, env });
@@ -14077,7 +14174,7 @@ ${error.message}`;
14077
14174
  const htmlSource = path41.join(this.app.dist.cwdPath, "csr", targetHtmlFilename(this.target));
14078
14175
  if (!await Bun.file(htmlSource).exists())
14079
14176
  throw new Error(`CSR html for mobile target '${this.target.name}' not found: ${htmlSource}`);
14080
- await rm5(this.targetWebRoot, { recursive: true, force: true });
14177
+ await rm6(this.targetWebRoot, { recursive: true, force: true });
14081
14178
  await mkdir11(this.targetWebRoot, { recursive: true });
14082
14179
  await Bun.write(path41.join(this.targetWebRoot, "index.html"), this.#injectMobileTargetMeta(await Bun.file(htmlSource).text()));
14083
14180
  }
@@ -14158,7 +14255,7 @@ ${error.message}`;
14158
14255
  await this.project.android.setVersionCode(this.target.buildNum);
14159
14256
  await this.project.android.setAppName(this.target.appName);
14160
14257
  }
14161
- async#applyPermissions() {
14258
+ async#applyPermissions({ operation, env }) {
14162
14259
  for (const permission of this.target.permissions ?? []) {
14163
14260
  if (permission === "camera")
14164
14261
  await this.addCamera();
@@ -14167,7 +14264,7 @@ ${error.message}`;
14167
14264
  else if (permission === "location")
14168
14265
  await this.addLocation();
14169
14266
  else if (permission === "push")
14170
- await this.addPush();
14267
+ await this.addPush({ operation, env });
14171
14268
  }
14172
14269
  }
14173
14270
  async#applyDeepLinks(platform, { operation, env }) {
@@ -14186,7 +14283,7 @@ ${error.message}`;
14186
14283
  await this.#setUrlSchemesInIos(schemes);
14187
14284
  }
14188
14285
  if (domains.length > 0)
14189
- await this.#setAssociatedDomainsInIos(domains);
14286
+ await this.#setAssociatedDomainsInIos(domains, { operation, env });
14190
14287
  return;
14191
14288
  }
14192
14289
  if (platform === "android") {
@@ -14280,12 +14377,64 @@ ${error.message}`;
14280
14377
  this.#setPermissionsInAndroid(["ACCESS_COARSE_LOCATION", "ACCESS_FINE_LOCATION"]);
14281
14378
  this.#setFeaturesInAndroid(["android.hardware.location.gps"]);
14282
14379
  }
14283
- async addPush() {
14380
+ async addPush({ operation, env }) {
14284
14381
  await this.#setPermissionInIos({
14285
14382
  userNotificationsUsageDescription: "$(PRODUCT_NAME) uses notifications to keep you updated."
14286
14383
  });
14384
+ await Promise.all([
14385
+ this.project.ios.updateInfoPlist(this.iosTargetName, "Debug", { UIBackgroundModes: ["remote-notification"] }),
14386
+ this.project.ios.updateInfoPlist(this.iosTargetName, "Release", { UIBackgroundModes: ["remote-notification"] }),
14387
+ this.#writeEntitlementsInIos(this.target.deepLinks?.domains ?? [], { operation, env }),
14388
+ this.#ensurePushAppDelegateInIos()
14389
+ ]);
14287
14390
  this.#setPermissionsInAndroid(["POST_NOTIFICATIONS"]);
14288
14391
  }
14392
+ async#ensurePushAppDelegateInIos() {
14393
+ const appDelegatePath = path41.join(this.app.cwdPath, this.iosProjectPath, "App/AppDelegate.swift");
14394
+ if (!await Bun.file(appDelegatePath).exists())
14395
+ return;
14396
+ const editor = await FileEditor.create(appDelegatePath);
14397
+ let content = editor.getContent();
14398
+ if (!content.includes("import FirebaseCore")) {
14399
+ content = content.replace("import Capacitor", `import Capacitor
14400
+ import FirebaseCore`);
14401
+ }
14402
+ if (!content.includes("import FirebaseMessaging")) {
14403
+ content = content.replace("import FirebaseCore", `import FirebaseCore
14404
+ import FirebaseMessaging`);
14405
+ }
14406
+ if (!content.includes("FirebaseApp.configure()")) {
14407
+ content = content.replace(/\n(\s*)return true/, `
14408
+ $1FirebaseApp.configure()
14409
+
14410
+ $1return true`);
14411
+ }
14412
+ if (!content.includes("didRegisterForRemoteNotificationsWithDeviceToken")) {
14413
+ const delegateMethods = `
14414
+ func application(_ application: UIApplication,
14415
+ didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
14416
+ Messaging.messaging().apnsToken = deviceToken
14417
+ NotificationCenter.default.post(
14418
+ name: .capacitorDidRegisterForRemoteNotifications,
14419
+ object: deviceToken
14420
+ )
14421
+ }
14422
+
14423
+ func application(_ application: UIApplication,
14424
+ didFailToRegisterForRemoteNotificationsWithError error: Error) {
14425
+ NotificationCenter.default.post(
14426
+ name: .capacitorDidFailToRegisterForRemoteNotifications,
14427
+ object: error
14428
+ )
14429
+ }
14430
+ `;
14431
+ content = content.replace(`
14432
+ func applicationWillResignActive`, `${delegateMethods}
14433
+ func applicationWillResignActive`);
14434
+ }
14435
+ if (content !== editor.getContent())
14436
+ await editor.setContent(content).save();
14437
+ }
14289
14438
  async#setPermissionInIos(permissions) {
14290
14439
  const updateNs = Object.fromEntries(Object.entries(permissions).map(([key, value]) => [`NS${capitalize5(key)}`, value]));
14291
14440
  await Promise.all([
@@ -14303,25 +14452,35 @@ ${error.message}`;
14303
14452
  this.project.ios.updateInfoPlist(this.iosTargetName, "Release", { CFBundleURLTypes: urlTypes })
14304
14453
  ]);
14305
14454
  }
14306
- async#setAssociatedDomainsInIos(domains) {
14455
+ async#setAssociatedDomainsInIos(domains, { operation, env }) {
14456
+ await this.#writeEntitlementsInIos(domains, { operation, env });
14457
+ }
14458
+ async#writeEntitlementsInIos(domains, runConfig) {
14307
14459
  const entitlementsRelPath = "App/App.entitlements";
14308
14460
  const entitlementsPath = path41.join(this.app.cwdPath, this.iosProjectPath, entitlementsRelPath);
14309
14461
  const values = domains.map((domain) => `applinks:${domain}`);
14462
+ const usesPush = this.target.permissions?.includes("push") ?? false;
14463
+ const apsEnvironment = resolveIosApnsEnvironment(runConfig);
14310
14464
  const body = [
14311
14465
  '<?xml version="1.0" encoding="UTF-8"?>',
14312
14466
  '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
14313
14467
  '<plist version="1.0">',
14314
14468
  "<dict>",
14315
- " <key>com.apple.developer.associated-domains</key>",
14316
- " <array>",
14317
- ...values.map((value) => ` <string>${value}</string>`),
14318
- " </array>",
14469
+ ...usesPush ? [" <key>aps-environment</key>", ` <string>${apsEnvironment}</string>`] : [],
14470
+ ...values.length > 0 ? [
14471
+ " <key>com.apple.developer.associated-domains</key>",
14472
+ " <array>",
14473
+ ...values.map((value) => ` <string>${value}</string>`),
14474
+ " </array>"
14475
+ ] : [],
14319
14476
  "</dict>",
14320
14477
  "</plist>",
14321
14478
  ""
14322
14479
  ].join(`
14323
14480
  `);
14324
- await writeFile2(entitlementsPath, body);
14481
+ const currentBody = await Bun.file(entitlementsPath).exists() ? await Bun.file(entitlementsPath).text() : undefined;
14482
+ if (currentBody !== body)
14483
+ await writeFile2(entitlementsPath, body);
14325
14484
  await this.#setCodeSignEntitlementsInIos(entitlementsRelPath);
14326
14485
  }
14327
14486
  async#setCodeSignEntitlementsInIos(entitlementsRelPath) {
@@ -14425,7 +14584,7 @@ ${filter}$1`);
14425
14584
  for (const permission of permissions) {
14426
14585
  if (this.#hasPermissionInAndroid(permission)) {
14427
14586
  this.app.logger.info(`${permission} already exists in android`);
14428
- return this;
14587
+ continue;
14429
14588
  }
14430
14589
  this.app.logger.info(`Adding ${permission} to android`);
14431
14590
  this.project.android.getAndroidManifest().injectFragment("manifest", `<uses-permission android:name="android.permission.${permission}" />`);
@@ -14441,7 +14600,7 @@ ${filter}$1`);
14441
14600
  return Array.from(usesPermission).map((permission) => permission.getAttribute("android:name"));
14442
14601
  }
14443
14602
  #hasPermissionInAndroid(permission) {
14444
- return this.#getPermissionsInAndroid().includes(permission);
14603
+ return this.#getPermissionsInAndroid().includes(`android.permission.${permission}`);
14445
14604
  }
14446
14605
  }
14447
14606
  // pkgs/@akanjs/devkit/commandDecorators/targetMeta.ts
@@ -21194,7 +21353,7 @@ class LibraryCommand extends command("library", [LibraryScript], ({ public: targ
21194
21353
  }
21195
21354
 
21196
21355
  // pkgs/@akanjs/cli/localRegistry/localRegistry.runner.ts
21197
- import { mkdir as mkdir13, rm as rm6 } from "fs/promises";
21356
+ import { mkdir as mkdir13, rm as rm7 } from "fs/promises";
21198
21357
  import path47 from "path";
21199
21358
  import { Logger as Logger19 } from "akanjs/common";
21200
21359
  var defaultLocalRegistryUrl = "http://127.0.0.1:4873";
@@ -21237,13 +21396,13 @@ class LocalRegistryRunner extends runner("localRegistry") {
21237
21396
  try {
21238
21397
  await workspace.spawn("docker", ["rm", "-f", containerName], { stdio: "inherit" });
21239
21398
  } catch {}
21240
- await rm6(path47.join(workspace.workspaceRoot, ".akan/verdaccio"), { recursive: true, force: true });
21399
+ await rm7(path47.join(workspace.workspaceRoot, ".akan/verdaccio"), { recursive: true, force: true });
21241
21400
  Logger19.info("Local registry storage has been reset");
21242
21401
  }
21243
21402
  async smoke(workspace, { registryUrl } = {}) {
21244
21403
  const registry = this.getRegistryUrl(registryUrl);
21245
21404
  const smokeRoot = path47.join(workspace.workspaceRoot, ".akan/e2e");
21246
- await rm6(path47.join(smokeRoot, smokeRepoName), { recursive: true, force: true });
21405
+ await rm7(path47.join(smokeRoot, smokeRepoName), { recursive: true, force: true });
21247
21406
  await workspace.spawn(process.execPath, [
21248
21407
  "dist/pkgs/create-akan-workspace/index.js",
21249
21408
  smokeRepoName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/cli",
3
- "version": "2.3.11-rc.7",
3
+ "version": "2.3.11-rc.8",
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.11-rc.7",
37
+ "akanjs": "2.3.11-rc.8",
38
38
  "chalk": "^5.6.2",
39
39
  "commander": "^14.0.3",
40
40
  "daisyui": "5.5.23",
@@ -67,7 +67,9 @@ local.properties
67
67
  **/public/fallback-*.js
68
68
  **/public/libs
69
69
  **/private/libs
70
+ **/public/firebase-messaging-sw.js
70
71
 
72
+ **/android/keystore.properties
71
73
  **/vendor/bundle/
72
74
  **/ios/App/App/public
73
75
  **/ios/App/App/Podfile.lock