@akanjs/cli 2.3.12-rc.1 → 2.3.13-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/incrementalBuilder.proc.js +145 -184
- package/index.js +145 -184
- package/package.json +2 -2
- package/templates/facetIndex/index.ts +11 -3
- package/templates/workspaceRoot/.gitignore.template +2 -0
|
@@ -801,15 +801,17 @@ class AkanAppConfig {
|
|
|
801
801
|
secrets;
|
|
802
802
|
baseDevEnv;
|
|
803
803
|
libs;
|
|
804
|
+
plugins;
|
|
804
805
|
domains = new Set;
|
|
805
806
|
subRoutes = new Map;
|
|
806
807
|
basePaths = new Set;
|
|
807
808
|
branches = new Set(["debug", "develop", "main"]);
|
|
808
|
-
constructor(app, libs, rootPackageJson, config, baseDevEnv) {
|
|
809
|
+
constructor(app, libs, rootPackageJson, config, baseDevEnv, plugins = []) {
|
|
809
810
|
this.app = app;
|
|
810
811
|
this.rootPackageJson = rootPackageJson;
|
|
811
812
|
this.libs = libs;
|
|
812
813
|
this.baseDevEnv = baseDevEnv;
|
|
814
|
+
this.plugins = plugins;
|
|
813
815
|
this.#applyRoutes(config?.routes);
|
|
814
816
|
this.defaultDatabaseMode = config?.defaultDatabaseMode ?? "single";
|
|
815
817
|
this.externalLibs = config?.externalLibs ?? [];
|
|
@@ -991,16 +993,16 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
991
993
|
app.workspace.getLibs(),
|
|
992
994
|
app.workspace.getPackageJson()
|
|
993
995
|
]);
|
|
994
|
-
const
|
|
995
|
-
|
|
996
|
+
const resolved = typeof configImp === "function" ? configImp(app) : configImp;
|
|
997
|
+
const { plugins, ...config } = resolved ?? {};
|
|
998
|
+
return new AkanAppConfig(app, libs, rootPackageJson, config, baseDevEnv, plugins ?? []);
|
|
996
999
|
}
|
|
997
1000
|
#resolveProductionDependencyVersion(lib) {
|
|
998
1001
|
const rootVersion = this.rootPackageJson.dependencies?.[lib] ?? this.rootPackageJson.devDependencies?.[lib];
|
|
999
1002
|
if (rootVersion)
|
|
1000
1003
|
return rootVersion;
|
|
1001
1004
|
const akanPackageJson = getAkanPackageJson();
|
|
1002
|
-
|
|
1003
|
-
return akanPackageJson.dependencies?.[lib] ?? akanPackageJson.peerDependencies?.[lib];
|
|
1005
|
+
return akanPackageJson.dependencies?.[lib] ?? akanPackageJson.peerDependencies?.[lib];
|
|
1004
1006
|
}
|
|
1005
1007
|
#getProductionRuntimePackages() {
|
|
1006
1008
|
return [
|
|
@@ -1094,14 +1096,17 @@ function mergeImageConfig(config = {}) {
|
|
|
1094
1096
|
class AkanLibConfig {
|
|
1095
1097
|
lib;
|
|
1096
1098
|
externalLibs;
|
|
1097
|
-
|
|
1099
|
+
plugins;
|
|
1100
|
+
constructor(lib, config, plugins = []) {
|
|
1098
1101
|
this.lib = lib;
|
|
1099
1102
|
this.externalLibs = config?.externalLibs ?? [];
|
|
1103
|
+
this.plugins = plugins;
|
|
1100
1104
|
}
|
|
1101
1105
|
static async from(lib) {
|
|
1102
1106
|
const [configImp] = await Promise.all([import(`${lib.cwdPath}/akan.config.ts`).then((mod) => mod.default)]);
|
|
1103
|
-
const
|
|
1104
|
-
|
|
1107
|
+
const resolved = typeof configImp === "function" ? configImp(lib) : configImp;
|
|
1108
|
+
const { plugins, ...config } = resolved ?? {};
|
|
1109
|
+
return new AkanLibConfig(lib, config, plugins ?? []);
|
|
1105
1110
|
}
|
|
1106
1111
|
}
|
|
1107
1112
|
//! need to refactor
|
|
@@ -1119,63 +1124,6 @@ var decreaseBuildNum = async (app) => {
|
|
|
1119
1124
|
const akanConfigContent = akanConfig.replace(`buildNum: ${appConfig.mobile.buildNum}`, `buildNum: ${appConfig.mobile.buildNum - 1}`);
|
|
1120
1125
|
fs.writeFileSync(akanConfigPath, akanConfigContent);
|
|
1121
1126
|
};
|
|
1122
|
-
// pkgs/@akanjs/devkit/firebaseMessagingSw.ts
|
|
1123
|
-
var FIREBASE_WEB_SDK_VERSION = "12.13.0";
|
|
1124
|
-
function normalizeFirebaseClientConfig(config) {
|
|
1125
|
-
if (!config || typeof config !== "object")
|
|
1126
|
-
return null;
|
|
1127
|
-
const value = config;
|
|
1128
|
-
if (typeof value.apiKey !== "string" || typeof value.projectId !== "string" || typeof value.messagingSenderId !== "string" || typeof value.appId !== "string") {
|
|
1129
|
-
return null;
|
|
1130
|
-
}
|
|
1131
|
-
return {
|
|
1132
|
-
apiKey: value.apiKey,
|
|
1133
|
-
...typeof value.authDomain === "string" ? { authDomain: value.authDomain } : {},
|
|
1134
|
-
projectId: value.projectId,
|
|
1135
|
-
...typeof value.storageBucket === "string" ? { storageBucket: value.storageBucket } : {},
|
|
1136
|
-
messagingSenderId: value.messagingSenderId,
|
|
1137
|
-
appId: value.appId
|
|
1138
|
-
};
|
|
1139
|
-
}
|
|
1140
|
-
function createFirebaseMessagingServiceWorker(config) {
|
|
1141
|
-
const configJson = JSON.stringify(config);
|
|
1142
|
-
return `/* Generated by Akan.js. Do not edit. */
|
|
1143
|
-
const firebaseConfig = ${configJson};
|
|
1144
|
-
|
|
1145
|
-
if (firebaseConfig) {
|
|
1146
|
-
importScripts("https://www.gstatic.com/firebasejs/${FIREBASE_WEB_SDK_VERSION}/firebase-app-compat.js");
|
|
1147
|
-
importScripts("https://www.gstatic.com/firebasejs/${FIREBASE_WEB_SDK_VERSION}/firebase-messaging-compat.js");
|
|
1148
|
-
|
|
1149
|
-
firebase.initializeApp(firebaseConfig);
|
|
1150
|
-
const messaging = firebase.messaging();
|
|
1151
|
-
|
|
1152
|
-
const notificationUrl = (payload) =>
|
|
1153
|
-
payload?.data?.url || payload?.fcmOptions?.link || payload?.notification?.click_action;
|
|
1154
|
-
|
|
1155
|
-
messaging.onBackgroundMessage((payload) => {
|
|
1156
|
-
const title = payload?.notification?.title || "";
|
|
1157
|
-
const options = {
|
|
1158
|
-
body: payload?.notification?.body,
|
|
1159
|
-
icon: payload?.notification?.icon,
|
|
1160
|
-
image: payload?.notification?.image,
|
|
1161
|
-
data: {
|
|
1162
|
-
url: notificationUrl(payload),
|
|
1163
|
-
FCM_MSG: payload,
|
|
1164
|
-
},
|
|
1165
|
-
};
|
|
1166
|
-
self.registration.showNotification(title, options);
|
|
1167
|
-
});
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
self.addEventListener("notificationclick", (event) => {
|
|
1171
|
-
const url = event.notification?.data?.url || event.notification?.data?.FCM_MSG?.data?.url;
|
|
1172
|
-
event.notification?.close();
|
|
1173
|
-
if (!url) return;
|
|
1174
|
-
event.waitUntil(clients.openWindow(url));
|
|
1175
|
-
});
|
|
1176
|
-
`;
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
1127
|
// pkgs/@akanjs/devkit/getDirname.ts
|
|
1180
1128
|
import path2 from "path";
|
|
1181
1129
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -3413,7 +3361,7 @@ class WorkspaceExecutor extends Executor {
|
|
|
3413
3361
|
return viewExampleFiles;
|
|
3414
3362
|
}
|
|
3415
3363
|
}
|
|
3416
|
-
var scanFacetDirs = ["ui", "webkit", "srvkit", "common"];
|
|
3364
|
+
var scanFacetDirs = ["ui", "webkit", "srvkit", "common", "plugin"];
|
|
3417
3365
|
|
|
3418
3366
|
class SysExecutor extends Executor {
|
|
3419
3367
|
workspace;
|
|
@@ -3796,28 +3744,69 @@ class AppExecutor extends SysExecutor {
|
|
|
3796
3744
|
if (write)
|
|
3797
3745
|
await this.syncAssets(scanInfo.getScanResult().libDeps);
|
|
3798
3746
|
if (write)
|
|
3799
|
-
await this.#
|
|
3747
|
+
await this.#runPluginSyncAssets();
|
|
3800
3748
|
return scanInfo;
|
|
3801
3749
|
}
|
|
3802
|
-
async#
|
|
3803
|
-
const
|
|
3804
|
-
if (
|
|
3805
|
-
return;
|
|
3806
|
-
const envClientPath = path7.join(this.cwdPath, "env", "env.client.ts");
|
|
3807
|
-
if (!await FileSys.fileExists(envClientPath))
|
|
3808
|
-
return;
|
|
3809
|
-
let firebaseConfig = null;
|
|
3810
|
-
try {
|
|
3811
|
-
const envUrl = pathToFileURL(envClientPath);
|
|
3812
|
-
envUrl.searchParams.set("t", String(Date.now()));
|
|
3813
|
-
const envModule = await import(envUrl.href);
|
|
3814
|
-
firebaseConfig = normalizeFirebaseClientConfig(envModule.env?.firebase);
|
|
3815
|
-
} catch {
|
|
3750
|
+
async#runPluginSyncAssets() {
|
|
3751
|
+
const plugins = await this.collectPlugins();
|
|
3752
|
+
if (!plugins.some((plugin) => plugin.syncAssets))
|
|
3816
3753
|
return;
|
|
3754
|
+
const ctx = this.#makeSyncContext();
|
|
3755
|
+
for (const plugin of plugins)
|
|
3756
|
+
await plugin.syncAssets?.(ctx);
|
|
3757
|
+
}
|
|
3758
|
+
#makeSyncContext() {
|
|
3759
|
+
return {
|
|
3760
|
+
appName: this.name,
|
|
3761
|
+
appPath: this.cwdPath,
|
|
3762
|
+
executor: this,
|
|
3763
|
+
getPath: (rel) => this.getPath(rel),
|
|
3764
|
+
fileExists: (rel) => FileSys.fileExists(this.getPath(rel)),
|
|
3765
|
+
writeFile: async (rel, content, opts) => {
|
|
3766
|
+
await this.writeFile(rel, content, opts);
|
|
3767
|
+
},
|
|
3768
|
+
readEnvClient: async () => {
|
|
3769
|
+
const envClientPath = path7.join(this.cwdPath, "env", "env.client.ts");
|
|
3770
|
+
if (!await FileSys.fileExists(envClientPath))
|
|
3771
|
+
return null;
|
|
3772
|
+
try {
|
|
3773
|
+
const envUrl = pathToFileURL(envClientPath);
|
|
3774
|
+
envUrl.searchParams.set("t", String(Date.now()));
|
|
3775
|
+
const envModule = await import(envUrl.href);
|
|
3776
|
+
return envModule.env ?? null;
|
|
3777
|
+
} catch {
|
|
3778
|
+
return null;
|
|
3779
|
+
}
|
|
3780
|
+
}
|
|
3781
|
+
};
|
|
3782
|
+
}
|
|
3783
|
+
async collectPlugins() {
|
|
3784
|
+
const scanInfo = await this.scan({ write: false });
|
|
3785
|
+
const libDeps = scanInfo.getLibs();
|
|
3786
|
+
const appConfig = await this.getConfig();
|
|
3787
|
+
const collected = [...appConfig.plugins];
|
|
3788
|
+
for (const libName of libDeps) {
|
|
3789
|
+
const libConfig = await LibExecutor.from(this, libName).getConfig().catch(() => null);
|
|
3790
|
+
if (libConfig)
|
|
3791
|
+
collected.push(...libConfig.plugins);
|
|
3817
3792
|
}
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3793
|
+
const seen = new Set;
|
|
3794
|
+
return collected.filter((plugin) => {
|
|
3795
|
+
if (seen.has(plugin.name))
|
|
3796
|
+
return false;
|
|
3797
|
+
seen.add(plugin.name);
|
|
3798
|
+
return true;
|
|
3799
|
+
});
|
|
3800
|
+
}
|
|
3801
|
+
async getPluginRuntimePackages() {
|
|
3802
|
+
const plugins = await this.collectPlugins();
|
|
3803
|
+
const appConfig = await this.getConfig();
|
|
3804
|
+
const ctx = {
|
|
3805
|
+
appName: this.name,
|
|
3806
|
+
mobile: appConfig.mobile,
|
|
3807
|
+
hasMobilePermission: (permission) => Object.values(appConfig.mobile.targets).some((target) => target.permissions?.includes(permission) ?? false)
|
|
3808
|
+
};
|
|
3809
|
+
return [...new Set(plugins.flatMap((plugin) => plugin.runtimePackages?.(ctx) ?? []))];
|
|
3821
3810
|
}
|
|
3822
3811
|
async increaseBuildNum() {
|
|
3823
3812
|
await increaseBuildNum(this);
|
|
@@ -11352,7 +11341,7 @@ function getPageKeyBasePath(pageKey, basePaths) {
|
|
|
11352
11341
|
import path27 from "path";
|
|
11353
11342
|
var SOURCE_EXTS4 = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
11354
11343
|
var CONFIG_BASENAMES = new Set(["akan.config.ts", "bunfig.toml", "tsconfig.json", "package.json"]);
|
|
11355
|
-
var BARREL_FACETS = new Set(["common", "srvkit", "ui", "webkit"]);
|
|
11344
|
+
var BARREL_FACETS = new Set(["common", "srvkit", "ui", "webkit", "plugin"]);
|
|
11356
11345
|
var CLIENT_SUFFIXES = [".Template.tsx", ".Unit.tsx", ".Util.tsx", ".View.tsx", ".Zone.tsx", ".store.ts"];
|
|
11357
11346
|
var SHARED_SUFFIXES2 = [".constant.ts", ".dictionary.ts", ".signal.ts"];
|
|
11358
11347
|
var SERVER_SUFFIXES2 = [".service.ts", ".document.ts"];
|
|
@@ -11498,7 +11487,7 @@ var uniqueResolved = (files) => [...new Set(files.map((file) => path27.resolve(f
|
|
|
11498
11487
|
// pkgs/@akanjs/devkit/frontendBuild/devGeneratedIndexSync.ts
|
|
11499
11488
|
import { mkdir as mkdir6, readdir as readdir2, readFile, rm as rm3, stat as stat3, writeFile } from "fs/promises";
|
|
11500
11489
|
import path28 from "path";
|
|
11501
|
-
var BARREL_FACETS2 = new Set(["common", "srvkit", "ui", "webkit"]);
|
|
11490
|
+
var BARREL_FACETS2 = new Set(["common", "srvkit", "ui", "webkit", "plugin"]);
|
|
11502
11491
|
var FACET_SOURCE_FILE_RE = /\.(ts|tsx)$/;
|
|
11503
11492
|
var FACET_EXCLUDED_FILE_RE = /(^index\.tsx?$|\.d\.ts$|\.(test|spec)\.(ts|tsx)$|\.css$|\.scss$|\.sass$)/;
|
|
11504
11493
|
var FACET_PASCAL_CASE_RE = /^[A-Z][A-Za-z0-9]*$/;
|
|
@@ -13553,7 +13542,6 @@ var getLocalIP = () => {
|
|
|
13553
13542
|
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13554
13543
|
var asString = (value) => typeof value === "string" ? value : undefined;
|
|
13555
13544
|
var firstString = (...values) => values.find((value) => typeof value === "string");
|
|
13556
|
-
var resolveIosApnsEnvironment = ({ operation }) => operation === "release" ? "production" : "development";
|
|
13557
13545
|
var scoreIosDeviceTarget = (target) => {
|
|
13558
13546
|
const state = target.state?.toLowerCase() ?? "";
|
|
13559
13547
|
return (state.includes("available") ? 4 : 0) + (state.includes("wired") ? 2 : 0) + (state.includes("paired") ? 1 : 0);
|
|
@@ -13925,6 +13913,7 @@ class CapacitorApp {
|
|
|
13925
13913
|
iosProjectPath = "ios/App";
|
|
13926
13914
|
androidRootPath = "android";
|
|
13927
13915
|
androidAssetsPath = "android/app/src/main/assets";
|
|
13916
|
+
#iosEntitlements = {};
|
|
13928
13917
|
constructor(app, target) {
|
|
13929
13918
|
this.app = app;
|
|
13930
13919
|
this.target = target;
|
|
@@ -13972,6 +13961,7 @@ class CapacitorApp {
|
|
|
13972
13961
|
await this.#applyIosMetadata();
|
|
13973
13962
|
await this.#applyPermissions({ operation, env });
|
|
13974
13963
|
await this.#applyDeepLinks("ios", { operation, env });
|
|
13964
|
+
await this.#flushIosEntitlements();
|
|
13975
13965
|
await this.project.commit();
|
|
13976
13966
|
await this.#generateAssets({ operation, env });
|
|
13977
13967
|
this.app.verbose(`syncing iOS`);
|
|
@@ -14329,17 +14319,51 @@ ${error.message}`;
|
|
|
14329
14319
|
await this.project.android.setAppName(this.target.appName);
|
|
14330
14320
|
}
|
|
14331
14321
|
async#applyPermissions({ operation, env }) {
|
|
14322
|
+
const plugins = await this.app.collectPlugins();
|
|
14323
|
+
const nativePlugins = new Map;
|
|
14324
|
+
for (const plugin of plugins) {
|
|
14325
|
+
const permission = plugin.capacitor?.permission;
|
|
14326
|
+
if (!permission || !plugin.capacitor?.configureNative)
|
|
14327
|
+
continue;
|
|
14328
|
+
const claimants = nativePlugins.get(permission) ?? [];
|
|
14329
|
+
claimants.push(plugin);
|
|
14330
|
+
nativePlugins.set(permission, claimants);
|
|
14331
|
+
}
|
|
14332
14332
|
for (const permission of this.target.permissions ?? []) {
|
|
14333
|
-
|
|
14334
|
-
|
|
14335
|
-
|
|
14336
|
-
|
|
14337
|
-
|
|
14338
|
-
|
|
14339
|
-
|
|
14340
|
-
await
|
|
14333
|
+
const claimants = nativePlugins.get(permission);
|
|
14334
|
+
if (!claimants?.length) {
|
|
14335
|
+
this.app.logger.warn(`Mobile target '${this.target.name}' declares permission '${permission}' but no plugin configures it. ` + `Depend on a lib (e.g. @libs/util) whose akan.config declares a plugin with capacitor.permission '${permission}'.`);
|
|
14336
|
+
continue;
|
|
14337
|
+
}
|
|
14338
|
+
const ctx = this.#makeNativeContext({ operation, env });
|
|
14339
|
+
for (const plugin of claimants)
|
|
14340
|
+
await plugin.capacitor?.configureNative?.(ctx);
|
|
14341
14341
|
}
|
|
14342
14342
|
}
|
|
14343
|
+
#makeNativeContext({ operation, env }) {
|
|
14344
|
+
return {
|
|
14345
|
+
appPath: this.app.cwdPath,
|
|
14346
|
+
executor: this.app,
|
|
14347
|
+
target: this.target,
|
|
14348
|
+
operation,
|
|
14349
|
+
env,
|
|
14350
|
+
setIosUsageDescriptions: (descriptions) => this.#setPermissionInIos(descriptions),
|
|
14351
|
+
updateIosInfoPlist: async (values) => {
|
|
14352
|
+
await Promise.all([
|
|
14353
|
+
this.project.ios.updateInfoPlist(this.iosTargetName, "Debug", values),
|
|
14354
|
+
this.project.ios.updateInfoPlist(this.iosTargetName, "Release", values)
|
|
14355
|
+
]);
|
|
14356
|
+
},
|
|
14357
|
+
addIosEntitlements: (entitlements) => this.#addIosEntitlements(entitlements),
|
|
14358
|
+
editIosAppDelegate: (transform) => this.#editIosAppDelegate(transform),
|
|
14359
|
+
addAndroidPermissions: (permissions) => {
|
|
14360
|
+
this.#setPermissionsInAndroid(permissions);
|
|
14361
|
+
},
|
|
14362
|
+
addAndroidFeatures: (features) => {
|
|
14363
|
+
this.#setFeaturesInAndroid(features);
|
|
14364
|
+
}
|
|
14365
|
+
};
|
|
14366
|
+
}
|
|
14343
14367
|
async#applyDeepLinks(platform, { operation, env }) {
|
|
14344
14368
|
const deepLinks = this.target.deepLinks;
|
|
14345
14369
|
if (!deepLinks)
|
|
@@ -14356,7 +14380,9 @@ ${error.message}`;
|
|
|
14356
14380
|
await this.#setUrlSchemesInIos(schemes);
|
|
14357
14381
|
}
|
|
14358
14382
|
if (domains.length > 0)
|
|
14359
|
-
|
|
14383
|
+
this.#addIosEntitlements({
|
|
14384
|
+
"com.apple.developer.associated-domains": domains.map((domain) => `applinks:${domain}`)
|
|
14385
|
+
});
|
|
14360
14386
|
return;
|
|
14361
14387
|
}
|
|
14362
14388
|
if (platform === "android") {
|
|
@@ -14428,85 +14454,18 @@ ${error.message}`;
|
|
|
14428
14454
|
await this.#clearRootCapacitorConfigs();
|
|
14429
14455
|
}
|
|
14430
14456
|
}
|
|
14431
|
-
|
|
14432
|
-
|
|
14433
|
-
cameraUsageDescription: "$(PRODUCT_NAME) requires access to the camera to take photos.",
|
|
14434
|
-
photoAddUsageDescription: "$(PRODUCT_NAME) requires access to the photo library to take photos.",
|
|
14435
|
-
photoUsageDescription: "$(PRODUCT_NAME) requires access to the photo library to take photos."
|
|
14436
|
-
});
|
|
14437
|
-
this.#setPermissionsInAndroid(["READ_MEDIA_IMAGES", "READ_EXTERNAL_STORAGE", "WRITE_EXTERNAL_STORAGE"]);
|
|
14438
|
-
}
|
|
14439
|
-
async addContact() {
|
|
14440
|
-
await this.#setPermissionInIos({
|
|
14441
|
-
contactsUsageDescription: "$(PRODUCT_NAME) requires access to the contacts to add new contacts."
|
|
14442
|
-
});
|
|
14443
|
-
this.#setPermissionsInAndroid(["READ_CONTACTS", "WRITE_CONTACTS"]);
|
|
14444
|
-
}
|
|
14445
|
-
async addLocation() {
|
|
14446
|
-
await this.#setPermissionInIos({
|
|
14447
|
-
locationAlwaysUsageDescription: "$(PRODUCT_NAME) requires access to the location to get the user's location.",
|
|
14448
|
-
locationWhenInUseUsageDescription: "$(PRODUCT_NAME) requires access to the location to get the user's location."
|
|
14449
|
-
});
|
|
14450
|
-
this.#setPermissionsInAndroid(["ACCESS_COARSE_LOCATION", "ACCESS_FINE_LOCATION"]);
|
|
14451
|
-
this.#setFeaturesInAndroid(["android.hardware.location.gps"]);
|
|
14452
|
-
}
|
|
14453
|
-
async addPush({ operation, env }) {
|
|
14454
|
-
await this.#setPermissionInIos({
|
|
14455
|
-
userNotificationsUsageDescription: "$(PRODUCT_NAME) uses notifications to keep you updated."
|
|
14456
|
-
});
|
|
14457
|
-
await Promise.all([
|
|
14458
|
-
this.project.ios.updateInfoPlist(this.iosTargetName, "Debug", { UIBackgroundModes: ["remote-notification"] }),
|
|
14459
|
-
this.project.ios.updateInfoPlist(this.iosTargetName, "Release", { UIBackgroundModes: ["remote-notification"] }),
|
|
14460
|
-
this.#writeEntitlementsInIos(this.target.deepLinks?.domains ?? [], { operation, env }),
|
|
14461
|
-
this.#ensurePushAppDelegateInIos()
|
|
14462
|
-
]);
|
|
14463
|
-
this.#setPermissionsInAndroid(["POST_NOTIFICATIONS"]);
|
|
14457
|
+
#addIosEntitlements(entitlements) {
|
|
14458
|
+
Object.assign(this.#iosEntitlements, entitlements);
|
|
14464
14459
|
}
|
|
14465
|
-
async#
|
|
14460
|
+
async#editIosAppDelegate(transform) {
|
|
14466
14461
|
const appDelegatePath = path41.join(this.app.cwdPath, this.iosProjectPath, "App/AppDelegate.swift");
|
|
14467
14462
|
if (!await Bun.file(appDelegatePath).exists())
|
|
14468
14463
|
return;
|
|
14469
14464
|
const editor = await FileEditor.create(appDelegatePath);
|
|
14470
|
-
|
|
14471
|
-
|
|
14472
|
-
|
|
14473
|
-
|
|
14474
|
-
}
|
|
14475
|
-
if (!content.includes("import FirebaseMessaging")) {
|
|
14476
|
-
content = content.replace("import FirebaseCore", `import FirebaseCore
|
|
14477
|
-
import FirebaseMessaging`);
|
|
14478
|
-
}
|
|
14479
|
-
if (!content.includes("FirebaseApp.configure()")) {
|
|
14480
|
-
content = content.replace(/\n(\s*)return true/, `
|
|
14481
|
-
$1FirebaseApp.configure()
|
|
14482
|
-
|
|
14483
|
-
$1return true`);
|
|
14484
|
-
}
|
|
14485
|
-
if (!content.includes("didRegisterForRemoteNotificationsWithDeviceToken")) {
|
|
14486
|
-
const delegateMethods = `
|
|
14487
|
-
func application(_ application: UIApplication,
|
|
14488
|
-
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
|
14489
|
-
Messaging.messaging().apnsToken = deviceToken
|
|
14490
|
-
NotificationCenter.default.post(
|
|
14491
|
-
name: .capacitorDidRegisterForRemoteNotifications,
|
|
14492
|
-
object: deviceToken
|
|
14493
|
-
)
|
|
14494
|
-
}
|
|
14495
|
-
|
|
14496
|
-
func application(_ application: UIApplication,
|
|
14497
|
-
didFailToRegisterForRemoteNotificationsWithError error: Error) {
|
|
14498
|
-
NotificationCenter.default.post(
|
|
14499
|
-
name: .capacitorDidFailToRegisterForRemoteNotifications,
|
|
14500
|
-
object: error
|
|
14501
|
-
)
|
|
14502
|
-
}
|
|
14503
|
-
`;
|
|
14504
|
-
content = content.replace(`
|
|
14505
|
-
func applicationWillResignActive`, `${delegateMethods}
|
|
14506
|
-
func applicationWillResignActive`);
|
|
14507
|
-
}
|
|
14508
|
-
if (content !== editor.getContent())
|
|
14509
|
-
await editor.setContent(content).save();
|
|
14465
|
+
const content = editor.getContent();
|
|
14466
|
+
const next = transform(content);
|
|
14467
|
+
if (next !== content)
|
|
14468
|
+
await editor.setContent(next).save();
|
|
14510
14469
|
}
|
|
14511
14470
|
async#setPermissionInIos(permissions) {
|
|
14512
14471
|
const updateNs = Object.fromEntries(Object.entries(permissions).map(([key, value]) => [`NS${capitalize5(key)}`, value]));
|
|
@@ -14525,27 +14484,29 @@ $1return true`);
|
|
|
14525
14484
|
this.project.ios.updateInfoPlist(this.iosTargetName, "Release", { CFBundleURLTypes: urlTypes })
|
|
14526
14485
|
]);
|
|
14527
14486
|
}
|
|
14528
|
-
|
|
14529
|
-
|
|
14487
|
+
#serializeIosEntitlements(entitlements) {
|
|
14488
|
+
const lines = [];
|
|
14489
|
+
for (const [key, value] of Object.entries(entitlements)) {
|
|
14490
|
+
lines.push(` <key>${key}</key>`);
|
|
14491
|
+
if (Array.isArray(value)) {
|
|
14492
|
+
lines.push(" <array>", ...value.map((item) => ` <string>${item}</string>`), " </array>");
|
|
14493
|
+
} else {
|
|
14494
|
+
lines.push(` <string>${value}</string>`);
|
|
14495
|
+
}
|
|
14496
|
+
}
|
|
14497
|
+
return lines;
|
|
14530
14498
|
}
|
|
14531
|
-
async#
|
|
14499
|
+
async#flushIosEntitlements() {
|
|
14500
|
+
if (Object.keys(this.#iosEntitlements).length === 0)
|
|
14501
|
+
return;
|
|
14532
14502
|
const entitlementsRelPath = "App/App.entitlements";
|
|
14533
14503
|
const entitlementsPath = path41.join(this.app.cwdPath, this.iosProjectPath, entitlementsRelPath);
|
|
14534
|
-
const values = domains.map((domain) => `applinks:${domain}`);
|
|
14535
|
-
const usesPush = this.target.permissions?.includes("push") ?? false;
|
|
14536
|
-
const apsEnvironment = resolveIosApnsEnvironment(runConfig);
|
|
14537
14504
|
const body = [
|
|
14538
14505
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
14539
14506
|
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
14540
14507
|
'<plist version="1.0">',
|
|
14541
14508
|
"<dict>",
|
|
14542
|
-
...
|
|
14543
|
-
...values.length > 0 ? [
|
|
14544
|
-
" <key>com.apple.developer.associated-domains</key>",
|
|
14545
|
-
" <array>",
|
|
14546
|
-
...values.map((value) => ` <string>${value}</string>`),
|
|
14547
|
-
" </array>"
|
|
14548
|
-
] : [],
|
|
14509
|
+
...this.#serializeIosEntitlements(this.#iosEntitlements),
|
|
14549
14510
|
"</dict>",
|
|
14550
14511
|
"</plist>",
|
|
14551
14512
|
""
|
|
@@ -15733,7 +15694,7 @@ var SUGGESTED_RULES = [
|
|
|
15733
15694
|
"Dictionary text should not contain scaffold wording, misspellings, or stale copied domain nouns.",
|
|
15734
15695
|
"Warn earlier on very long Akan files: 500 lines for services, 800 lines for Template/Zone files, and 1000 lines for Util files.",
|
|
15735
15696
|
"Global declarations, Window augmentation, and prototype mutation should stay in explicitly approved low-level integration files.",
|
|
15736
|
-
"Keep app root folders small and conventional: application code belongs under common, env, lib, page, private, public, script, srvkit, ui, or webkit.",
|
|
15697
|
+
"Keep app root folders small and conventional: application code belongs under common, env, lib, page, plugin, private, public, script, srvkit, ui, or webkit.",
|
|
15737
15698
|
"Keep apps/*/lib and libs/*/lib root files limited to generated support facets such as cnst.ts, db.ts, dict.ts, sig.ts, srv.ts, st.ts, useClient.ts, and useServer.ts.",
|
|
15738
15699
|
"Use domain module folders consistently: lib/<model> for database modules, lib/_<service> for service modules, and lib/__scalar/<scalar> for scalar modules.",
|
|
15739
15700
|
"Keep module UI filenames predictable: database modules use <Model>.Template/Unit/Util/View/Zone.tsx, service modules use <Service>.Util/Zone.tsx, and scalar modules use <Scalar>.Template/Unit.tsx.",
|
package/index.js
CHANGED
|
@@ -799,15 +799,17 @@ class AkanAppConfig {
|
|
|
799
799
|
secrets;
|
|
800
800
|
baseDevEnv;
|
|
801
801
|
libs;
|
|
802
|
+
plugins;
|
|
802
803
|
domains = new Set;
|
|
803
804
|
subRoutes = new Map;
|
|
804
805
|
basePaths = new Set;
|
|
805
806
|
branches = new Set(["debug", "develop", "main"]);
|
|
806
|
-
constructor(app, libs, rootPackageJson, config, baseDevEnv) {
|
|
807
|
+
constructor(app, libs, rootPackageJson, config, baseDevEnv, plugins = []) {
|
|
807
808
|
this.app = app;
|
|
808
809
|
this.rootPackageJson = rootPackageJson;
|
|
809
810
|
this.libs = libs;
|
|
810
811
|
this.baseDevEnv = baseDevEnv;
|
|
812
|
+
this.plugins = plugins;
|
|
811
813
|
this.#applyRoutes(config?.routes);
|
|
812
814
|
this.defaultDatabaseMode = config?.defaultDatabaseMode ?? "single";
|
|
813
815
|
this.externalLibs = config?.externalLibs ?? [];
|
|
@@ -989,16 +991,16 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
|
|
|
989
991
|
app.workspace.getLibs(),
|
|
990
992
|
app.workspace.getPackageJson()
|
|
991
993
|
]);
|
|
992
|
-
const
|
|
993
|
-
|
|
994
|
+
const resolved = typeof configImp === "function" ? configImp(app) : configImp;
|
|
995
|
+
const { plugins, ...config } = resolved ?? {};
|
|
996
|
+
return new AkanAppConfig(app, libs, rootPackageJson, config, baseDevEnv, plugins ?? []);
|
|
994
997
|
}
|
|
995
998
|
#resolveProductionDependencyVersion(lib) {
|
|
996
999
|
const rootVersion = this.rootPackageJson.dependencies?.[lib] ?? this.rootPackageJson.devDependencies?.[lib];
|
|
997
1000
|
if (rootVersion)
|
|
998
1001
|
return rootVersion;
|
|
999
1002
|
const akanPackageJson = getAkanPackageJson();
|
|
1000
|
-
|
|
1001
|
-
return akanPackageJson.dependencies?.[lib] ?? akanPackageJson.peerDependencies?.[lib];
|
|
1003
|
+
return akanPackageJson.dependencies?.[lib] ?? akanPackageJson.peerDependencies?.[lib];
|
|
1002
1004
|
}
|
|
1003
1005
|
#getProductionRuntimePackages() {
|
|
1004
1006
|
return [
|
|
@@ -1092,14 +1094,17 @@ function mergeImageConfig(config = {}) {
|
|
|
1092
1094
|
class AkanLibConfig {
|
|
1093
1095
|
lib;
|
|
1094
1096
|
externalLibs;
|
|
1095
|
-
|
|
1097
|
+
plugins;
|
|
1098
|
+
constructor(lib, config, plugins = []) {
|
|
1096
1099
|
this.lib = lib;
|
|
1097
1100
|
this.externalLibs = config?.externalLibs ?? [];
|
|
1101
|
+
this.plugins = plugins;
|
|
1098
1102
|
}
|
|
1099
1103
|
static async from(lib) {
|
|
1100
1104
|
const [configImp] = await Promise.all([import(`${lib.cwdPath}/akan.config.ts`).then((mod) => mod.default)]);
|
|
1101
|
-
const
|
|
1102
|
-
|
|
1105
|
+
const resolved = typeof configImp === "function" ? configImp(lib) : configImp;
|
|
1106
|
+
const { plugins, ...config } = resolved ?? {};
|
|
1107
|
+
return new AkanLibConfig(lib, config, plugins ?? []);
|
|
1103
1108
|
}
|
|
1104
1109
|
}
|
|
1105
1110
|
//! need to refactor
|
|
@@ -1117,63 +1122,6 @@ var decreaseBuildNum = async (app) => {
|
|
|
1117
1122
|
const akanConfigContent = akanConfig.replace(`buildNum: ${appConfig.mobile.buildNum}`, `buildNum: ${appConfig.mobile.buildNum - 1}`);
|
|
1118
1123
|
fs.writeFileSync(akanConfigPath, akanConfigContent);
|
|
1119
1124
|
};
|
|
1120
|
-
// pkgs/@akanjs/devkit/firebaseMessagingSw.ts
|
|
1121
|
-
var FIREBASE_WEB_SDK_VERSION = "12.13.0";
|
|
1122
|
-
function normalizeFirebaseClientConfig(config) {
|
|
1123
|
-
if (!config || typeof config !== "object")
|
|
1124
|
-
return null;
|
|
1125
|
-
const value = config;
|
|
1126
|
-
if (typeof value.apiKey !== "string" || typeof value.projectId !== "string" || typeof value.messagingSenderId !== "string" || typeof value.appId !== "string") {
|
|
1127
|
-
return null;
|
|
1128
|
-
}
|
|
1129
|
-
return {
|
|
1130
|
-
apiKey: value.apiKey,
|
|
1131
|
-
...typeof value.authDomain === "string" ? { authDomain: value.authDomain } : {},
|
|
1132
|
-
projectId: value.projectId,
|
|
1133
|
-
...typeof value.storageBucket === "string" ? { storageBucket: value.storageBucket } : {},
|
|
1134
|
-
messagingSenderId: value.messagingSenderId,
|
|
1135
|
-
appId: value.appId
|
|
1136
|
-
};
|
|
1137
|
-
}
|
|
1138
|
-
function createFirebaseMessagingServiceWorker(config) {
|
|
1139
|
-
const configJson = JSON.stringify(config);
|
|
1140
|
-
return `/* Generated by Akan.js. Do not edit. */
|
|
1141
|
-
const firebaseConfig = ${configJson};
|
|
1142
|
-
|
|
1143
|
-
if (firebaseConfig) {
|
|
1144
|
-
importScripts("https://www.gstatic.com/firebasejs/${FIREBASE_WEB_SDK_VERSION}/firebase-app-compat.js");
|
|
1145
|
-
importScripts("https://www.gstatic.com/firebasejs/${FIREBASE_WEB_SDK_VERSION}/firebase-messaging-compat.js");
|
|
1146
|
-
|
|
1147
|
-
firebase.initializeApp(firebaseConfig);
|
|
1148
|
-
const messaging = firebase.messaging();
|
|
1149
|
-
|
|
1150
|
-
const notificationUrl = (payload) =>
|
|
1151
|
-
payload?.data?.url || payload?.fcmOptions?.link || payload?.notification?.click_action;
|
|
1152
|
-
|
|
1153
|
-
messaging.onBackgroundMessage((payload) => {
|
|
1154
|
-
const title = payload?.notification?.title || "";
|
|
1155
|
-
const options = {
|
|
1156
|
-
body: payload?.notification?.body,
|
|
1157
|
-
icon: payload?.notification?.icon,
|
|
1158
|
-
image: payload?.notification?.image,
|
|
1159
|
-
data: {
|
|
1160
|
-
url: notificationUrl(payload),
|
|
1161
|
-
FCM_MSG: payload,
|
|
1162
|
-
},
|
|
1163
|
-
};
|
|
1164
|
-
self.registration.showNotification(title, options);
|
|
1165
|
-
});
|
|
1166
|
-
}
|
|
1167
|
-
|
|
1168
|
-
self.addEventListener("notificationclick", (event) => {
|
|
1169
|
-
const url = event.notification?.data?.url || event.notification?.data?.FCM_MSG?.data?.url;
|
|
1170
|
-
event.notification?.close();
|
|
1171
|
-
if (!url) return;
|
|
1172
|
-
event.waitUntil(clients.openWindow(url));
|
|
1173
|
-
});
|
|
1174
|
-
`;
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
1125
|
// pkgs/@akanjs/devkit/getDirname.ts
|
|
1178
1126
|
import path2 from "path";
|
|
1179
1127
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -3411,7 +3359,7 @@ class WorkspaceExecutor extends Executor {
|
|
|
3411
3359
|
return viewExampleFiles;
|
|
3412
3360
|
}
|
|
3413
3361
|
}
|
|
3414
|
-
var scanFacetDirs = ["ui", "webkit", "srvkit", "common"];
|
|
3362
|
+
var scanFacetDirs = ["ui", "webkit", "srvkit", "common", "plugin"];
|
|
3415
3363
|
|
|
3416
3364
|
class SysExecutor extends Executor {
|
|
3417
3365
|
workspace;
|
|
@@ -3794,28 +3742,69 @@ class AppExecutor extends SysExecutor {
|
|
|
3794
3742
|
if (write)
|
|
3795
3743
|
await this.syncAssets(scanInfo.getScanResult().libDeps);
|
|
3796
3744
|
if (write)
|
|
3797
|
-
await this.#
|
|
3745
|
+
await this.#runPluginSyncAssets();
|
|
3798
3746
|
return scanInfo;
|
|
3799
3747
|
}
|
|
3800
|
-
async#
|
|
3801
|
-
const
|
|
3802
|
-
if (
|
|
3803
|
-
return;
|
|
3804
|
-
const envClientPath = path7.join(this.cwdPath, "env", "env.client.ts");
|
|
3805
|
-
if (!await FileSys.fileExists(envClientPath))
|
|
3806
|
-
return;
|
|
3807
|
-
let firebaseConfig = null;
|
|
3808
|
-
try {
|
|
3809
|
-
const envUrl = pathToFileURL(envClientPath);
|
|
3810
|
-
envUrl.searchParams.set("t", String(Date.now()));
|
|
3811
|
-
const envModule = await import(envUrl.href);
|
|
3812
|
-
firebaseConfig = normalizeFirebaseClientConfig(envModule.env?.firebase);
|
|
3813
|
-
} catch {
|
|
3748
|
+
async#runPluginSyncAssets() {
|
|
3749
|
+
const plugins = await this.collectPlugins();
|
|
3750
|
+
if (!plugins.some((plugin) => plugin.syncAssets))
|
|
3814
3751
|
return;
|
|
3752
|
+
const ctx = this.#makeSyncContext();
|
|
3753
|
+
for (const plugin of plugins)
|
|
3754
|
+
await plugin.syncAssets?.(ctx);
|
|
3755
|
+
}
|
|
3756
|
+
#makeSyncContext() {
|
|
3757
|
+
return {
|
|
3758
|
+
appName: this.name,
|
|
3759
|
+
appPath: this.cwdPath,
|
|
3760
|
+
executor: this,
|
|
3761
|
+
getPath: (rel) => this.getPath(rel),
|
|
3762
|
+
fileExists: (rel) => FileSys.fileExists(this.getPath(rel)),
|
|
3763
|
+
writeFile: async (rel, content, opts) => {
|
|
3764
|
+
await this.writeFile(rel, content, opts);
|
|
3765
|
+
},
|
|
3766
|
+
readEnvClient: async () => {
|
|
3767
|
+
const envClientPath = path7.join(this.cwdPath, "env", "env.client.ts");
|
|
3768
|
+
if (!await FileSys.fileExists(envClientPath))
|
|
3769
|
+
return null;
|
|
3770
|
+
try {
|
|
3771
|
+
const envUrl = pathToFileURL(envClientPath);
|
|
3772
|
+
envUrl.searchParams.set("t", String(Date.now()));
|
|
3773
|
+
const envModule = await import(envUrl.href);
|
|
3774
|
+
return envModule.env ?? null;
|
|
3775
|
+
} catch {
|
|
3776
|
+
return null;
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3779
|
+
};
|
|
3780
|
+
}
|
|
3781
|
+
async collectPlugins() {
|
|
3782
|
+
const scanInfo = await this.scan({ write: false });
|
|
3783
|
+
const libDeps = scanInfo.getLibs();
|
|
3784
|
+
const appConfig = await this.getConfig();
|
|
3785
|
+
const collected = [...appConfig.plugins];
|
|
3786
|
+
for (const libName of libDeps) {
|
|
3787
|
+
const libConfig = await LibExecutor.from(this, libName).getConfig().catch(() => null);
|
|
3788
|
+
if (libConfig)
|
|
3789
|
+
collected.push(...libConfig.plugins);
|
|
3815
3790
|
}
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3791
|
+
const seen = new Set;
|
|
3792
|
+
return collected.filter((plugin) => {
|
|
3793
|
+
if (seen.has(plugin.name))
|
|
3794
|
+
return false;
|
|
3795
|
+
seen.add(plugin.name);
|
|
3796
|
+
return true;
|
|
3797
|
+
});
|
|
3798
|
+
}
|
|
3799
|
+
async getPluginRuntimePackages() {
|
|
3800
|
+
const plugins = await this.collectPlugins();
|
|
3801
|
+
const appConfig = await this.getConfig();
|
|
3802
|
+
const ctx = {
|
|
3803
|
+
appName: this.name,
|
|
3804
|
+
mobile: appConfig.mobile,
|
|
3805
|
+
hasMobilePermission: (permission) => Object.values(appConfig.mobile.targets).some((target) => target.permissions?.includes(permission) ?? false)
|
|
3806
|
+
};
|
|
3807
|
+
return [...new Set(plugins.flatMap((plugin) => plugin.runtimePackages?.(ctx) ?? []))];
|
|
3819
3808
|
}
|
|
3820
3809
|
async increaseBuildNum() {
|
|
3821
3810
|
await increaseBuildNum(this);
|
|
@@ -11350,7 +11339,7 @@ function getPageKeyBasePath(pageKey, basePaths) {
|
|
|
11350
11339
|
import path27 from "path";
|
|
11351
11340
|
var SOURCE_EXTS4 = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"]);
|
|
11352
11341
|
var CONFIG_BASENAMES = new Set(["akan.config.ts", "bunfig.toml", "tsconfig.json", "package.json"]);
|
|
11353
|
-
var BARREL_FACETS = new Set(["common", "srvkit", "ui", "webkit"]);
|
|
11342
|
+
var BARREL_FACETS = new Set(["common", "srvkit", "ui", "webkit", "plugin"]);
|
|
11354
11343
|
var CLIENT_SUFFIXES = [".Template.tsx", ".Unit.tsx", ".Util.tsx", ".View.tsx", ".Zone.tsx", ".store.ts"];
|
|
11355
11344
|
var SHARED_SUFFIXES2 = [".constant.ts", ".dictionary.ts", ".signal.ts"];
|
|
11356
11345
|
var SERVER_SUFFIXES2 = [".service.ts", ".document.ts"];
|
|
@@ -11496,7 +11485,7 @@ var uniqueResolved = (files) => [...new Set(files.map((file) => path27.resolve(f
|
|
|
11496
11485
|
// pkgs/@akanjs/devkit/frontendBuild/devGeneratedIndexSync.ts
|
|
11497
11486
|
import { mkdir as mkdir6, readdir as readdir2, readFile, rm as rm3, stat as stat3, writeFile } from "fs/promises";
|
|
11498
11487
|
import path28 from "path";
|
|
11499
|
-
var BARREL_FACETS2 = new Set(["common", "srvkit", "ui", "webkit"]);
|
|
11488
|
+
var BARREL_FACETS2 = new Set(["common", "srvkit", "ui", "webkit", "plugin"]);
|
|
11500
11489
|
var FACET_SOURCE_FILE_RE = /\.(ts|tsx)$/;
|
|
11501
11490
|
var FACET_EXCLUDED_FILE_RE = /(^index\.tsx?$|\.d\.ts$|\.(test|spec)\.(ts|tsx)$|\.css$|\.scss$|\.sass$)/;
|
|
11502
11491
|
var FACET_PASCAL_CASE_RE = /^[A-Z][A-Za-z0-9]*$/;
|
|
@@ -13551,7 +13540,6 @@ var getLocalIP = () => {
|
|
|
13551
13540
|
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13552
13541
|
var asString = (value) => typeof value === "string" ? value : undefined;
|
|
13553
13542
|
var firstString = (...values) => values.find((value) => typeof value === "string");
|
|
13554
|
-
var resolveIosApnsEnvironment = ({ operation }) => operation === "release" ? "production" : "development";
|
|
13555
13543
|
var scoreIosDeviceTarget = (target) => {
|
|
13556
13544
|
const state = target.state?.toLowerCase() ?? "";
|
|
13557
13545
|
return (state.includes("available") ? 4 : 0) + (state.includes("wired") ? 2 : 0) + (state.includes("paired") ? 1 : 0);
|
|
@@ -13923,6 +13911,7 @@ class CapacitorApp {
|
|
|
13923
13911
|
iosProjectPath = "ios/App";
|
|
13924
13912
|
androidRootPath = "android";
|
|
13925
13913
|
androidAssetsPath = "android/app/src/main/assets";
|
|
13914
|
+
#iosEntitlements = {};
|
|
13926
13915
|
constructor(app, target) {
|
|
13927
13916
|
this.app = app;
|
|
13928
13917
|
this.target = target;
|
|
@@ -13970,6 +13959,7 @@ class CapacitorApp {
|
|
|
13970
13959
|
await this.#applyIosMetadata();
|
|
13971
13960
|
await this.#applyPermissions({ operation, env });
|
|
13972
13961
|
await this.#applyDeepLinks("ios", { operation, env });
|
|
13962
|
+
await this.#flushIosEntitlements();
|
|
13973
13963
|
await this.project.commit();
|
|
13974
13964
|
await this.#generateAssets({ operation, env });
|
|
13975
13965
|
this.app.verbose(`syncing iOS`);
|
|
@@ -14327,17 +14317,51 @@ ${error.message}`;
|
|
|
14327
14317
|
await this.project.android.setAppName(this.target.appName);
|
|
14328
14318
|
}
|
|
14329
14319
|
async#applyPermissions({ operation, env }) {
|
|
14320
|
+
const plugins = await this.app.collectPlugins();
|
|
14321
|
+
const nativePlugins = new Map;
|
|
14322
|
+
for (const plugin of plugins) {
|
|
14323
|
+
const permission = plugin.capacitor?.permission;
|
|
14324
|
+
if (!permission || !plugin.capacitor?.configureNative)
|
|
14325
|
+
continue;
|
|
14326
|
+
const claimants = nativePlugins.get(permission) ?? [];
|
|
14327
|
+
claimants.push(plugin);
|
|
14328
|
+
nativePlugins.set(permission, claimants);
|
|
14329
|
+
}
|
|
14330
14330
|
for (const permission of this.target.permissions ?? []) {
|
|
14331
|
-
|
|
14332
|
-
|
|
14333
|
-
|
|
14334
|
-
|
|
14335
|
-
|
|
14336
|
-
|
|
14337
|
-
|
|
14338
|
-
await
|
|
14331
|
+
const claimants = nativePlugins.get(permission);
|
|
14332
|
+
if (!claimants?.length) {
|
|
14333
|
+
this.app.logger.warn(`Mobile target '${this.target.name}' declares permission '${permission}' but no plugin configures it. ` + `Depend on a lib (e.g. @libs/util) whose akan.config declares a plugin with capacitor.permission '${permission}'.`);
|
|
14334
|
+
continue;
|
|
14335
|
+
}
|
|
14336
|
+
const ctx = this.#makeNativeContext({ operation, env });
|
|
14337
|
+
for (const plugin of claimants)
|
|
14338
|
+
await plugin.capacitor?.configureNative?.(ctx);
|
|
14339
14339
|
}
|
|
14340
14340
|
}
|
|
14341
|
+
#makeNativeContext({ operation, env }) {
|
|
14342
|
+
return {
|
|
14343
|
+
appPath: this.app.cwdPath,
|
|
14344
|
+
executor: this.app,
|
|
14345
|
+
target: this.target,
|
|
14346
|
+
operation,
|
|
14347
|
+
env,
|
|
14348
|
+
setIosUsageDescriptions: (descriptions) => this.#setPermissionInIos(descriptions),
|
|
14349
|
+
updateIosInfoPlist: async (values) => {
|
|
14350
|
+
await Promise.all([
|
|
14351
|
+
this.project.ios.updateInfoPlist(this.iosTargetName, "Debug", values),
|
|
14352
|
+
this.project.ios.updateInfoPlist(this.iosTargetName, "Release", values)
|
|
14353
|
+
]);
|
|
14354
|
+
},
|
|
14355
|
+
addIosEntitlements: (entitlements) => this.#addIosEntitlements(entitlements),
|
|
14356
|
+
editIosAppDelegate: (transform) => this.#editIosAppDelegate(transform),
|
|
14357
|
+
addAndroidPermissions: (permissions) => {
|
|
14358
|
+
this.#setPermissionsInAndroid(permissions);
|
|
14359
|
+
},
|
|
14360
|
+
addAndroidFeatures: (features) => {
|
|
14361
|
+
this.#setFeaturesInAndroid(features);
|
|
14362
|
+
}
|
|
14363
|
+
};
|
|
14364
|
+
}
|
|
14341
14365
|
async#applyDeepLinks(platform, { operation, env }) {
|
|
14342
14366
|
const deepLinks = this.target.deepLinks;
|
|
14343
14367
|
if (!deepLinks)
|
|
@@ -14354,7 +14378,9 @@ ${error.message}`;
|
|
|
14354
14378
|
await this.#setUrlSchemesInIos(schemes);
|
|
14355
14379
|
}
|
|
14356
14380
|
if (domains.length > 0)
|
|
14357
|
-
|
|
14381
|
+
this.#addIosEntitlements({
|
|
14382
|
+
"com.apple.developer.associated-domains": domains.map((domain) => `applinks:${domain}`)
|
|
14383
|
+
});
|
|
14358
14384
|
return;
|
|
14359
14385
|
}
|
|
14360
14386
|
if (platform === "android") {
|
|
@@ -14426,85 +14452,18 @@ ${error.message}`;
|
|
|
14426
14452
|
await this.#clearRootCapacitorConfigs();
|
|
14427
14453
|
}
|
|
14428
14454
|
}
|
|
14429
|
-
|
|
14430
|
-
|
|
14431
|
-
cameraUsageDescription: "$(PRODUCT_NAME) requires access to the camera to take photos.",
|
|
14432
|
-
photoAddUsageDescription: "$(PRODUCT_NAME) requires access to the photo library to take photos.",
|
|
14433
|
-
photoUsageDescription: "$(PRODUCT_NAME) requires access to the photo library to take photos."
|
|
14434
|
-
});
|
|
14435
|
-
this.#setPermissionsInAndroid(["READ_MEDIA_IMAGES", "READ_EXTERNAL_STORAGE", "WRITE_EXTERNAL_STORAGE"]);
|
|
14436
|
-
}
|
|
14437
|
-
async addContact() {
|
|
14438
|
-
await this.#setPermissionInIos({
|
|
14439
|
-
contactsUsageDescription: "$(PRODUCT_NAME) requires access to the contacts to add new contacts."
|
|
14440
|
-
});
|
|
14441
|
-
this.#setPermissionsInAndroid(["READ_CONTACTS", "WRITE_CONTACTS"]);
|
|
14442
|
-
}
|
|
14443
|
-
async addLocation() {
|
|
14444
|
-
await this.#setPermissionInIos({
|
|
14445
|
-
locationAlwaysUsageDescription: "$(PRODUCT_NAME) requires access to the location to get the user's location.",
|
|
14446
|
-
locationWhenInUseUsageDescription: "$(PRODUCT_NAME) requires access to the location to get the user's location."
|
|
14447
|
-
});
|
|
14448
|
-
this.#setPermissionsInAndroid(["ACCESS_COARSE_LOCATION", "ACCESS_FINE_LOCATION"]);
|
|
14449
|
-
this.#setFeaturesInAndroid(["android.hardware.location.gps"]);
|
|
14450
|
-
}
|
|
14451
|
-
async addPush({ operation, env }) {
|
|
14452
|
-
await this.#setPermissionInIos({
|
|
14453
|
-
userNotificationsUsageDescription: "$(PRODUCT_NAME) uses notifications to keep you updated."
|
|
14454
|
-
});
|
|
14455
|
-
await Promise.all([
|
|
14456
|
-
this.project.ios.updateInfoPlist(this.iosTargetName, "Debug", { UIBackgroundModes: ["remote-notification"] }),
|
|
14457
|
-
this.project.ios.updateInfoPlist(this.iosTargetName, "Release", { UIBackgroundModes: ["remote-notification"] }),
|
|
14458
|
-
this.#writeEntitlementsInIos(this.target.deepLinks?.domains ?? [], { operation, env }),
|
|
14459
|
-
this.#ensurePushAppDelegateInIos()
|
|
14460
|
-
]);
|
|
14461
|
-
this.#setPermissionsInAndroid(["POST_NOTIFICATIONS"]);
|
|
14455
|
+
#addIosEntitlements(entitlements) {
|
|
14456
|
+
Object.assign(this.#iosEntitlements, entitlements);
|
|
14462
14457
|
}
|
|
14463
|
-
async#
|
|
14458
|
+
async#editIosAppDelegate(transform) {
|
|
14464
14459
|
const appDelegatePath = path41.join(this.app.cwdPath, this.iosProjectPath, "App/AppDelegate.swift");
|
|
14465
14460
|
if (!await Bun.file(appDelegatePath).exists())
|
|
14466
14461
|
return;
|
|
14467
14462
|
const editor = await FileEditor.create(appDelegatePath);
|
|
14468
|
-
|
|
14469
|
-
|
|
14470
|
-
|
|
14471
|
-
|
|
14472
|
-
}
|
|
14473
|
-
if (!content.includes("import FirebaseMessaging")) {
|
|
14474
|
-
content = content.replace("import FirebaseCore", `import FirebaseCore
|
|
14475
|
-
import FirebaseMessaging`);
|
|
14476
|
-
}
|
|
14477
|
-
if (!content.includes("FirebaseApp.configure()")) {
|
|
14478
|
-
content = content.replace(/\n(\s*)return true/, `
|
|
14479
|
-
$1FirebaseApp.configure()
|
|
14480
|
-
|
|
14481
|
-
$1return true`);
|
|
14482
|
-
}
|
|
14483
|
-
if (!content.includes("didRegisterForRemoteNotificationsWithDeviceToken")) {
|
|
14484
|
-
const delegateMethods = `
|
|
14485
|
-
func application(_ application: UIApplication,
|
|
14486
|
-
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
|
14487
|
-
Messaging.messaging().apnsToken = deviceToken
|
|
14488
|
-
NotificationCenter.default.post(
|
|
14489
|
-
name: .capacitorDidRegisterForRemoteNotifications,
|
|
14490
|
-
object: deviceToken
|
|
14491
|
-
)
|
|
14492
|
-
}
|
|
14493
|
-
|
|
14494
|
-
func application(_ application: UIApplication,
|
|
14495
|
-
didFailToRegisterForRemoteNotificationsWithError error: Error) {
|
|
14496
|
-
NotificationCenter.default.post(
|
|
14497
|
-
name: .capacitorDidFailToRegisterForRemoteNotifications,
|
|
14498
|
-
object: error
|
|
14499
|
-
)
|
|
14500
|
-
}
|
|
14501
|
-
`;
|
|
14502
|
-
content = content.replace(`
|
|
14503
|
-
func applicationWillResignActive`, `${delegateMethods}
|
|
14504
|
-
func applicationWillResignActive`);
|
|
14505
|
-
}
|
|
14506
|
-
if (content !== editor.getContent())
|
|
14507
|
-
await editor.setContent(content).save();
|
|
14463
|
+
const content = editor.getContent();
|
|
14464
|
+
const next = transform(content);
|
|
14465
|
+
if (next !== content)
|
|
14466
|
+
await editor.setContent(next).save();
|
|
14508
14467
|
}
|
|
14509
14468
|
async#setPermissionInIos(permissions) {
|
|
14510
14469
|
const updateNs = Object.fromEntries(Object.entries(permissions).map(([key, value]) => [`NS${capitalize5(key)}`, value]));
|
|
@@ -14523,27 +14482,29 @@ $1return true`);
|
|
|
14523
14482
|
this.project.ios.updateInfoPlist(this.iosTargetName, "Release", { CFBundleURLTypes: urlTypes })
|
|
14524
14483
|
]);
|
|
14525
14484
|
}
|
|
14526
|
-
|
|
14527
|
-
|
|
14485
|
+
#serializeIosEntitlements(entitlements) {
|
|
14486
|
+
const lines = [];
|
|
14487
|
+
for (const [key, value] of Object.entries(entitlements)) {
|
|
14488
|
+
lines.push(` <key>${key}</key>`);
|
|
14489
|
+
if (Array.isArray(value)) {
|
|
14490
|
+
lines.push(" <array>", ...value.map((item) => ` <string>${item}</string>`), " </array>");
|
|
14491
|
+
} else {
|
|
14492
|
+
lines.push(` <string>${value}</string>`);
|
|
14493
|
+
}
|
|
14494
|
+
}
|
|
14495
|
+
return lines;
|
|
14528
14496
|
}
|
|
14529
|
-
async#
|
|
14497
|
+
async#flushIosEntitlements() {
|
|
14498
|
+
if (Object.keys(this.#iosEntitlements).length === 0)
|
|
14499
|
+
return;
|
|
14530
14500
|
const entitlementsRelPath = "App/App.entitlements";
|
|
14531
14501
|
const entitlementsPath = path41.join(this.app.cwdPath, this.iosProjectPath, entitlementsRelPath);
|
|
14532
|
-
const values = domains.map((domain) => `applinks:${domain}`);
|
|
14533
|
-
const usesPush = this.target.permissions?.includes("push") ?? false;
|
|
14534
|
-
const apsEnvironment = resolveIosApnsEnvironment(runConfig);
|
|
14535
14502
|
const body = [
|
|
14536
14503
|
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
14537
14504
|
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
14538
14505
|
'<plist version="1.0">',
|
|
14539
14506
|
"<dict>",
|
|
14540
|
-
...
|
|
14541
|
-
...values.length > 0 ? [
|
|
14542
|
-
" <key>com.apple.developer.associated-domains</key>",
|
|
14543
|
-
" <array>",
|
|
14544
|
-
...values.map((value) => ` <string>${value}</string>`),
|
|
14545
|
-
" </array>"
|
|
14546
|
-
] : [],
|
|
14507
|
+
...this.#serializeIosEntitlements(this.#iosEntitlements),
|
|
14547
14508
|
"</dict>",
|
|
14548
14509
|
"</plist>",
|
|
14549
14510
|
""
|
|
@@ -15731,7 +15692,7 @@ var SUGGESTED_RULES = [
|
|
|
15731
15692
|
"Dictionary text should not contain scaffold wording, misspellings, or stale copied domain nouns.",
|
|
15732
15693
|
"Warn earlier on very long Akan files: 500 lines for services, 800 lines for Template/Zone files, and 1000 lines for Util files.",
|
|
15733
15694
|
"Global declarations, Window augmentation, and prototype mutation should stay in explicitly approved low-level integration files.",
|
|
15734
|
-
"Keep app root folders small and conventional: application code belongs under common, env, lib, page, private, public, script, srvkit, ui, or webkit.",
|
|
15695
|
+
"Keep app root folders small and conventional: application code belongs under common, env, lib, page, plugin, private, public, script, srvkit, ui, or webkit.",
|
|
15735
15696
|
"Keep apps/*/lib and libs/*/lib root files limited to generated support facets such as cnst.ts, db.ts, dict.ts, sig.ts, srv.ts, st.ts, useClient.ts, and useServer.ts.",
|
|
15736
15697
|
"Use domain module folders consistently: lib/<model> for database modules, lib/_<service> for service modules, and lib/__scalar/<scalar> for scalar modules.",
|
|
15737
15698
|
"Keep module UI filenames predictable: database modules use <Model>.Template/Unit/Util/View/Zone.tsx, service modules use <Service>.Util/Zone.tsx, and scalar modules use <Scalar>.Template/Unit.tsx.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akanjs/cli",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.13-rc.0",
|
|
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.13-rc.0",
|
|
38
38
|
"chalk": "^5.6.2",
|
|
39
39
|
"commander": "^14.0.3",
|
|
40
40
|
"daisyui": "5.5.23",
|
|
@@ -14,22 +14,30 @@ const sourceFilePattern = /\.(ts|tsx)$/;
|
|
|
14
14
|
const excludedFilePattern = /(^index\.tsx?$|\.d\.ts$|\.(test|spec)\.(ts|tsx)$|\.css$|\.scss$|\.sass$)/;
|
|
15
15
|
// `ui` exports PascalCase names only; `common`/`srvkit`/`webkit` export camelCase names only. Names with
|
|
16
16
|
// dots, underscores, or hyphens (e.g. `foo.helper`, `Globe_Dynamic`, `kebab-case`) match neither and are skipped.
|
|
17
|
+
// `plugin` additionally exports `<camelCase>.plugin` files (the plugin-facet file convention, e.g.
|
|
18
|
+
// `pushNotification.plugin.ts`) alongside plain camelCase helpers.
|
|
17
19
|
const pascalCasePattern = /^[A-Z][A-Za-z0-9]*$/;
|
|
18
20
|
const camelCasePattern = /^[a-z][A-Za-z0-9]*$/;
|
|
19
|
-
const
|
|
21
|
+
const pluginNamePattern = /^[a-z][A-Za-z0-9]*\.plugin$/;
|
|
22
|
+
const matchesFacetNameConvention = (facet: string, name: string) => {
|
|
23
|
+
if (facet === "ui") return pascalCasePattern.test(name);
|
|
24
|
+
if (facet === "plugin") return camelCasePattern.test(name) || pluginNamePattern.test(name);
|
|
25
|
+
return camelCasePattern.test(name);
|
|
26
|
+
};
|
|
20
27
|
|
|
21
28
|
export default async function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dict = {}, options: Options = {}) {
|
|
22
29
|
const { exec, facet } = options;
|
|
23
30
|
if (!exec || !facet || !(await exec.exists(facet))) return null;
|
|
24
31
|
|
|
25
|
-
const nameCasePattern = nameCasePatternForFacet(facet);
|
|
26
32
|
const { files, dirs } = await exec.getFilesAndDirs(facet);
|
|
27
33
|
const exportNames = [
|
|
28
34
|
...files
|
|
29
35
|
.filter((filename) => sourceFilePattern.test(filename) && !excludedFilePattern.test(filename))
|
|
30
36
|
.map((filename) => filename.replace(sourceFilePattern, "")),
|
|
31
37
|
...dirs.filter((dirname) => !dirname.startsWith(".")),
|
|
32
|
-
]
|
|
38
|
+
]
|
|
39
|
+
.filter((name) => matchesFacetNameConvention(facet, name))
|
|
40
|
+
.sort();
|
|
33
41
|
|
|
34
42
|
if (exportNames.length === 0) return null;
|
|
35
43
|
return {
|
|
@@ -99,6 +99,7 @@ apps/*/ui/index.ts
|
|
|
99
99
|
apps/*/webkit/index.ts
|
|
100
100
|
apps/*/srvkit/index.ts
|
|
101
101
|
apps/*/common/index.ts
|
|
102
|
+
apps/*/plugin/index.ts
|
|
102
103
|
apps/*/client.ts
|
|
103
104
|
apps/*/server.ts
|
|
104
105
|
libs/*/lib/cnst.ts
|
|
@@ -116,6 +117,7 @@ libs/*/ui/index.ts
|
|
|
116
117
|
libs/*/webkit/index.ts
|
|
117
118
|
libs/*/srvkit/index.ts
|
|
118
119
|
libs/*/common/index.ts
|
|
120
|
+
libs/*/plugin/index.ts
|
|
119
121
|
libs/*/client.ts
|
|
120
122
|
libs/*/server.ts
|
|
121
123
|
libs/*/index.ts
|