@akanjs/devkit 2.4.0-rc.5 → 2.4.0-rc.7

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.
@@ -265,6 +265,8 @@ describe("AkanAppConfig", () => {
265
265
  `postgres@${runtimeDependencies.postgres}`,
266
266
  `protobufjs@${runtimeDependencies.protobufjs}`,
267
267
  ]);
268
+ // The workspace-root install covers the toolchain/runtime plus every app Capacitor plugin
269
+ // (deduped — "@capacitor/core" appears in both source lists).
268
270
  const expectedMobilePackages: string[] = [
269
271
  "firebase",
270
272
  "@capacitor/cli",
@@ -272,6 +274,18 @@ describe("AkanAppConfig", () => {
272
274
  "@capacitor/ios",
273
275
  "@capacitor/android",
274
276
  "@capacitor/assets",
277
+ "@capacitor-community/fcm",
278
+ "@capacitor/app",
279
+ "@capacitor/browser",
280
+ "@capacitor/camera",
281
+ "@capacitor/device",
282
+ "@capacitor/geolocation",
283
+ "@capacitor/haptics",
284
+ "@capacitor/inappbrowser",
285
+ "@capacitor/keyboard",
286
+ "@capacitor/preferences",
287
+ "@capacitor/push-notifications",
288
+ "capacitor-plugin-safe-area",
275
289
  ];
276
290
  expect(config.getMobileRuntimePackages() as string[]).toEqual(expectedMobilePackages);
277
291
  expect(config.getMissingMobileDependencySpecs()).toEqual(
@@ -52,11 +52,11 @@ const WORKSPACE_BARREL_FACETS = ["ui", "webkit", "common", "client", "server"] a
52
52
  const SSR_RUNTIME_PACKAGES = ["react", "react-dom", "react-server-dom-webpack"] as const;
53
53
  const NATIVE_RUNTIME_PACKAGES = ["sharp"] as const;
54
54
  const DEFAULT_BACKEND_RUNTIME_PACKAGES = ["croner"] as const;
55
- // Packages installed at the workspace root before a mobile target is built or started. All are
56
- // declared only as optional peers, so a fresh workspace never auto-installs them. Covers the
57
- // firebase client (push tokens) and the Capacitor toolchain — `@capacitor/cli` (`npx cap`),
55
+ // The firebase client (push tokens) and the Capacitor toolchain `@capacitor/cli` (`npx cap`),
58
56
  // `@capacitor/assets` (`npx @capacitor/assets`) plus the `@capacitor/core`/`ios`/`android` runtime
59
57
  // and native-platform packages that `npx cap add`/`sync` resolve from the workspace node_modules.
58
+ // All are declared only as optional peers, so a fresh workspace never auto-installs them; the mobile
59
+ // preflight installs them (together with MOBILE_APP_CAPACITOR_PLUGINS) at the workspace root.
60
60
  const MOBILE_RUNTIME_PACKAGES = [
61
61
  "firebase",
62
62
  "@capacitor/cli",
@@ -65,12 +65,13 @@ const MOBILE_RUNTIME_PACKAGES = [
65
65
  "@capacitor/android",
66
66
  "@capacitor/assets",
67
67
  ] as const;
68
- // Capacitor plugins that must be declared in the *app's* package.json (apps/<app>/package.json).
69
- // `npx cap sync` discovers plugins by scanning the app directory's dependencies and registers their
70
- // native code into the iOS/Android projects; packages present only in the workspace root are never
71
- // registered, so the JS bridge throws `Capacitor plugin "Device" is not available.` at runtime.
72
- // These are added on start-ios / start-android with a "*" range, letting bun resolve them to the
73
- // version already hoisted at the workspace root instead of pinning a second source of truth.
68
+ // Capacitor plugins that must additionally be declared in the *app's* package.json
69
+ // (apps/<app>/package.json): `npx cap sync` discovers plugins by scanning the app directory's
70
+ // dependencies and registers their native code into the iOS/Android projects; packages present only
71
+ // at the workspace root are never registered, so the JS bridge throws
72
+ // `Capacitor plugin "Device" is not available.` at runtime. They are installed at the workspace root
73
+ // alongside MOBILE_RUNTIME_PACKAGES (pinned via optional peers) and declared in the app with a "*"
74
+ // range so bun dedupes them to that hoisted version instead of pinning a second source of truth.
74
75
  const MOBILE_APP_CAPACITOR_PLUGINS = [
75
76
  "@capacitor-community/fcm",
76
77
  "@capacitor/app",
@@ -423,7 +424,9 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
423
424
  return this.#getMissingDependencySpecs(this.getDatabaseModeRuntimePackages(databaseMode));
424
425
  }
425
426
  getMobileRuntimePackages() {
426
- return [...MOBILE_RUNTIME_PACKAGES];
427
+ // The app Capacitor plugins are installed at the workspace root too, so bun can resolve the
428
+ // app's "*" declarations to a hoisted, peer-pinned version instead of fetching latest.
429
+ return [...new Set([...MOBILE_RUNTIME_PACKAGES, ...MOBILE_APP_CAPACITOR_PLUGINS])];
427
430
  }
428
431
  getMissingMobileDependencySpecs() {
429
432
  return this.#getMissingDependencySpecs(this.getMobileRuntimePackages());
@@ -4,6 +4,7 @@ import os from "node:os";
4
4
  import path from "node:path";
5
5
  import type { AkanMobileTargetConfig } from "./akanConfig";
6
6
  import {
7
+ ANDROID_MIN_SDK_VERSION,
7
8
  assertJsonSerializable,
8
9
  buildIosNativeRunCommand,
9
10
  classifyIosRunFailure,
@@ -14,6 +15,7 @@ import {
14
15
  materializeCapacitorConfig,
15
16
  parseDevicectlDevices,
16
17
  parseSimctlDevices,
18
+ raiseGradleMinSdkVersion,
17
19
  rootCapacitorConfigFilenames,
18
20
  sanitizeIosNativeRunEnv,
19
21
  writeRootCapacitorConfig,
@@ -255,3 +257,24 @@ describe("Android signing diagnostics", () => {
255
257
  ]);
256
258
  });
257
259
  });
260
+
261
+ describe("raiseGradleMinSdkVersion", () => {
262
+ test("raises the scaffold minSdkVersion to the bundled-plugin floor, preserving formatting", () => {
263
+ const scaffold = "ext {\n minSdkVersion = 24\n compileSdkVersion = 35\n}\n";
264
+ expect(raiseGradleMinSdkVersion(scaffold)).toBe(
265
+ `ext {\n minSdkVersion = ${ANDROID_MIN_SDK_VERSION}\n compileSdkVersion = 35\n}\n`,
266
+ );
267
+ });
268
+
269
+ test("returns null when nothing needs changing", () => {
270
+ // Already at the floor, already above it, and no assignment present.
271
+ expect(raiseGradleMinSdkVersion(`ext {\n minSdkVersion = ${ANDROID_MIN_SDK_VERSION}\n}\n`)).toBeNull();
272
+ expect(raiseGradleMinSdkVersion("ext {\n minSdkVersion = 34\n}\n")).toBeNull();
273
+ expect(raiseGradleMinSdkVersion("ext {\n compileSdkVersion = 35\n}\n")).toBeNull();
274
+ });
275
+
276
+ test("honors an explicit floor and leaves higher user values untouched", () => {
277
+ expect(raiseGradleMinSdkVersion("minSdkVersion = 21", 23)).toBe("minSdkVersion = 23");
278
+ expect(raiseGradleMinSdkVersion("minSdkVersion = 28", 26)).toBeNull();
279
+ });
280
+ });
package/capacitorApp.ts CHANGED
@@ -203,7 +203,7 @@ export function parseSimctlDevices(output: string): IosRunTarget[] {
203
203
  const json = JSON.parse(output) as { devices?: Record<string, unknown[]> };
204
204
  const devices = json.devices ?? {};
205
205
  return Object.values(devices)
206
- .flatMap((runtimeDevices) => runtimeDevices)
206
+ .flat()
207
207
  .filter(isRecord)
208
208
  .flatMap((device) => {
209
209
  const id = firstString(device.udid, device.UDID, device.identifier);
@@ -424,6 +424,15 @@ export function getAdbDeviceStateIssues(output: string) {
424
424
  });
425
425
  }
426
426
 
427
+ export const ANDROID_MIN_SDK_VERSION = 26;
428
+ export function raiseGradleMinSdkVersion(content: string, floor: number = ANDROID_MIN_SDK_VERSION): string | null {
429
+ const match = content.match(/minSdkVersion\s*=\s*(\d+)/);
430
+ if (!match?.[1]) return null;
431
+ const current = Number.parseInt(match[1], 10);
432
+ if (Number.isNaN(current) || current >= floor) return null;
433
+ return content.replace(/(minSdkVersion\s*=\s*)\d+/, `$1${floor}`);
434
+ }
435
+
427
436
  const mergeAllowNavigation = (configured: unknown, localIp: string | undefined) => {
428
437
  const values = Array.isArray(configured)
429
438
  ? configured.filter((value): value is string => typeof value === "string")
@@ -766,6 +775,7 @@ export class CapacitorApp {
766
775
  await this.#prepareTargetAssets();
767
776
  await this.#prepareExternalFiles("android");
768
777
  await this.#applyAndroidMetadata();
778
+ await this.#applyAndroidMinSdkVersion();
769
779
  await this.#applyPermissions({ operation, env });
770
780
  await this.#applyDeepLinks("android", { operation, env });
771
781
  await this.project.commit();
@@ -992,6 +1002,14 @@ export class CapacitorApp {
992
1002
  await this.project.android.setVersionCode(this.target.buildNum);
993
1003
  await this.project.android.setAppName(this.target.appName);
994
1004
  }
1005
+ async #applyAndroidMinSdkVersion() {
1006
+ const variablesGradlePath = path.join(this.app.cwdPath, this.androidRootPath, "variables.gradle");
1007
+ if (!(await Bun.file(variablesGradlePath).exists())) return;
1008
+ const updated = raiseGradleMinSdkVersion(await Bun.file(variablesGradlePath).text());
1009
+ if (!updated) return;
1010
+ await writeFile(variablesGradlePath, updated);
1011
+ this.app.verbose(`Raised Android minSdkVersion to ${ANDROID_MIN_SDK_VERSION} in variables.gradle`);
1012
+ }
995
1013
  async #applyPermissions({ operation, env }: Pick<RunConfig, "operation" | "env">) {
996
1014
  const plugins = await this.app.collectPlugins();
997
1015
  const nativePlugins = new Map<MobilePermission, AkanPlugin[]>();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/devkit",
3
- "version": "2.4.0-rc.5",
3
+ "version": "2.4.0-rc.7",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -32,7 +32,7 @@
32
32
  "@langchain/openai": "^1.4.6",
33
33
  "@tailwindcss/node": "^4.3.0",
34
34
  "@trapezedev/project": "^7.1.4",
35
- "akanjs": "2.4.0-rc.5",
35
+ "akanjs": "2.4.0-rc.7",
36
36
  "chalk": "^5.6.2",
37
37
  "commander": "^14.0.3",
38
38
  "daisyui": "5.5.23",