akanjs 3.0.0-beta.30 → 3.0.0-beta.32

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.
Files changed (39) hide show
  1. package/client/translator.ts +26 -0
  2. package/index.ts +29 -4
  3. package/package.json +1 -1
  4. package/server/rscWorker.tsx +12 -1
  5. package/server/rscWorkerHost.ts +20 -1
  6. package/server/webRouter.ts +4 -1
  7. package/types/client/translator.d.ts +1 -0
  8. package/types/index.d.ts +28 -4
  9. package/types/server/rscWorkerHost.d.ts +1 -0
  10. package/types/vendor/@akanjs/native/packages/cli/src/api.d.ts +20 -4
  11. package/types/vendor/@akanjs/native/packages/cli/src/config.d.ts +7 -0
  12. package/types/vendor/@akanjs/native/packages/cli/src/lib/addons.d.ts +19 -0
  13. package/types/vendor/@akanjs/native/packages/cli/src/lib/macossigning.d.ts +95 -0
  14. package/types/vendor/@akanjs/native/packages/cli/src/lib/prepare.d.ts +21 -1
  15. package/types/vendor/@akanjs/native/packages/cli/src/lib/signing.d.ts +2 -2
  16. package/types/vendor/@akanjs/native/packages/cli/src/lib/toolchains.d.ts +9 -0
  17. package/types/vendor/@akanjs/native/packages/cli/src/lib/windowssigning.d.ts +30 -0
  18. package/types/vendor/@akanjs/native/packages/cli/src/platforms/desktop.d.ts +8 -2
  19. package/types/vendor/@akanjs/native/packages/cli/src/platforms/linux-appimage.d.ts +11 -0
  20. package/types/vendor/@akanjs/native/packages/cli/src/platforms/windows-installer.d.ts +2 -0
  21. package/ui/System/Client.tsx +9 -16
  22. package/ui/System/SSR.tsx +1 -1
  23. package/vendor/@akanjs/native/packages/cli/src/api.ts +53 -7
  24. package/vendor/@akanjs/native/packages/cli/src/commands/build.ts +38 -4
  25. package/vendor/@akanjs/native/packages/cli/src/config.ts +7 -0
  26. package/vendor/@akanjs/native/packages/cli/src/lib/addons.ts +217 -0
  27. package/vendor/@akanjs/native/packages/cli/src/lib/configkeys.ts +1 -0
  28. package/vendor/@akanjs/native/packages/cli/src/lib/macossigning.ts +357 -0
  29. package/vendor/@akanjs/native/packages/cli/src/lib/prepare.ts +34 -1
  30. package/vendor/@akanjs/native/packages/cli/src/lib/project.ts +1 -0
  31. package/vendor/@akanjs/native/packages/cli/src/lib/signing.ts +4 -3
  32. package/vendor/@akanjs/native/packages/cli/src/lib/toolchains.ts +24 -0
  33. package/vendor/@akanjs/native/packages/cli/src/lib/windowssigning.ts +155 -0
  34. package/vendor/@akanjs/native/packages/cli/src/platforms/desktop.ts +30 -13
  35. package/vendor/@akanjs/native/packages/cli/src/platforms/linux-appimage.ts +84 -0
  36. package/vendor/@akanjs/native/packages/cli/src/platforms/linux.ts +4 -1
  37. package/vendor/@akanjs/native/packages/cli/src/platforms/macos.ts +115 -43
  38. package/vendor/@akanjs/native/packages/cli/src/platforms/windows-installer.ts +29 -2
  39. package/vendor/@akanjs/native/packages/cli/src/platforms/windows.ts +12 -1
@@ -34,6 +34,7 @@ const getTranslatorState = (): TranslatorState => {
34
34
  };
35
35
 
36
36
  export class Translator {
37
+ static readonly #filledDicts = new WeakMap<AllDictionary, Map<string, Dictionary>>();
37
38
  constructor(dictionary: Record<string, Record<string, Record<string, unknown>>>) {
38
39
  Object.entries(dictionary).forEach(([lang, dict]) => {
39
40
  Translator.seed(lang, dict as Dictionary);
@@ -75,6 +76,31 @@ export class Translator {
75
76
  return defaultLocale === lang ? undefined : Translator.#lookup(defaultLocale, key);
76
77
  }
77
78
 
79
+ static withDefaultLocale(allDictionary: AllDictionary, lang: string): Dictionary | undefined {
80
+ const { defaultLocale } = parseAkanI18nEnv();
81
+ const fallback = allDictionary[defaultLocale];
82
+ if (lang === defaultLocale || !fallback) return allDictionary[lang];
83
+ const filledByLocale = Translator.#filledDicts.get(allDictionary) ?? new Map<string, Dictionary>();
84
+ Translator.#filledDicts.set(allDictionary, filledByLocale);
85
+ const filled =
86
+ filledByLocale.get(lang) ?? (Translator.#fillGaps(allDictionary[lang] ?? {}, fallback) as Dictionary);
87
+ filledByLocale.set(lang, filled);
88
+ return filled;
89
+ }
90
+ static #fillGaps(own: Record<string, unknown>, fallback: Record<string, unknown>): Record<string, unknown> {
91
+ const filled = { ...own };
92
+ for (const [key, value] of Object.entries(fallback)) {
93
+ const ownValue = filled[key];
94
+ if (ownValue === undefined) filled[key] = value;
95
+ else if (Translator.#isNode(ownValue) && Translator.#isNode(value))
96
+ filled[key] = Translator.#fillGaps(ownValue, value);
97
+ }
98
+ return filled;
99
+ }
100
+ static #isNode(value: unknown): value is Record<string, unknown> {
101
+ return typeof value === "object" && value !== null && !Array.isArray(value);
102
+ }
103
+
78
104
  static seed(lang: string, dict: Dictionary | undefined) {
79
105
  if (!dict) return;
80
106
  const state = getTranslatorState();
package/index.ts CHANGED
@@ -142,6 +142,8 @@ export interface AkanNativeUpdatesConfig {
142
142
  }
143
143
 
144
144
  export interface AkanNativeIosConfig {
145
+ /** Where the iOS app starts, over the section's `indexPath`. */
146
+ indexPath?: string;
145
147
  /** The Apple team id: the universal links of `deepLinks.domains` (apple-app-site-association). */
146
148
  teamId?: string;
147
149
  infoPlist?: Record<string, AkanNativeValue>;
@@ -153,6 +155,8 @@ export interface AkanNativeIosConfig {
153
155
  }
154
156
 
155
157
  export interface AkanNativeAndroidConfig {
158
+ /** Where the Android app starts, over the section's `indexPath`. */
159
+ indexPath?: string;
156
160
  /** The signing certificates' SHA-256 fingerprints: the app links of `deepLinks.domains` (assetlinks.json). */
157
161
  sha256CertFingerprints?: string[];
158
162
  /** The Firebase project's google-services.json, relative to the app folder, for FCM push. */
@@ -171,13 +175,25 @@ export interface AkanNativeAndroidConfig {
171
175
  files?: Record<string, string>;
172
176
  }
173
177
 
178
+ export interface AkanNativeDesktopServerConfig {
179
+ /**
180
+ * Packages the image installs and the desktop app's server goes without, along with what only they pull in: an
181
+ * addon that needs the image's system (a ROS install), code only a process the desktop app never starts loads.
182
+ * The server's own bundle must not import them. The image is unchanged.
183
+ */
184
+ omit?: string[];
185
+ }
186
+
174
187
  export interface AkanNativeDesktopConfig {
188
+ /** Where the desktop app starts, over the section's `indexPath`. */
189
+ indexPath?: string;
175
190
  /**
176
191
  * The desktop app carries the app's server (API only, database mode single, on loopback) and its pages call
177
- * nothing else. `build-desktop`, `start-desktop` and `publish-update` all read it, and an installed app refuses
178
- * a release that carries a server when it has none, or none when it has one.
192
+ * nothing else: `true`, or `{ omit }` to carry it without some of the image's packages. `build-desktop`,
193
+ * `start-desktop` and `publish-update` all read it, and an installed app refuses a release that carries a server
194
+ * when it has none, or none when it has one.
179
195
  */
180
- server?: boolean;
196
+ server?: boolean | AkanNativeDesktopServerConfig;
181
197
  /**
182
198
  * `"reload"`: a window whose page's process ends (a crash, a hang) loads it again every time, waiting
183
199
  * longer after each end in a row, and the app relaunches when the webview's browser process ends — for an
@@ -191,6 +207,12 @@ export interface AkanNativeDesktopConfig {
191
207
  * support on an unattended screen; it covers every media request, so not for an app that asks for a camera.
192
208
  */
193
209
  screenCapture?: "picker" | "auto";
210
+ /**
211
+ * macOS: entitlements of the app's executable beyond what it gets by itself — the JIT's under the hardened runtime and
212
+ * the camera's or microphone's when a usage text asks for it — e.g. `com.apple.security.cs.disable-library-validation`
213
+ * for a native addon signed by another team. Read when the app is signed with a team (a Developer ID).
214
+ */
215
+ entitlements?: Record<string, AkanNativeValue>;
194
216
  }
195
217
 
196
218
  /**
@@ -201,7 +223,10 @@ export interface AkanNativeDesktopConfig {
201
223
  export interface AkanNativeSettings {
202
224
  /** The client the app opens, a basePath the routes declare; an app without basePaths leaves it out. */
203
225
  basePath?: string;
204
- /** Where the app starts, and falls back to for a deep link's stack and a back with no history. Default `/`. */
226
+ /**
227
+ * Where the app starts, and falls back to for a deep link's stack and a back with no history. Default `/`. A platform
228
+ * section's own `indexPath` (`desktop.indexPath`) wins on that platform.
229
+ */
205
230
  indexPath?: string;
206
231
  /** Default: the app's folder name. */
207
232
  appName?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akanjs",
3
- "version": "3.0.0-beta.30",
3
+ "version": "3.0.0-beta.32",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -9,6 +9,7 @@ import type {
9
9
  } from "akanjs/client";
10
10
  import { type AkanI18nConfig, DEFAULT_AKAN_I18N, getBasePathFromPathname, Logger } from "akanjs/common";
11
11
  import {
12
+ type AkanTheme,
12
13
  getRequestDynamicUsage,
13
14
  getRequestFrameState,
14
15
  getRequestPolicy,
@@ -860,6 +861,7 @@ export class RscRenderer {
860
861
  let sentChunk = false;
861
862
  let lateControlSent = false;
862
863
  const chunks: Uint8Array[] = [];
864
+ let reportedTheme: AkanTheme | undefined;
863
865
  const sendMeta = () => {
864
866
  if (!options.requestId || sentMeta) return;
865
867
  sentMeta = true;
@@ -867,14 +869,22 @@ export class RscRenderer {
867
869
  const trace = getCurrentTrace();
868
870
  if (trace) trace.status = options.status;
869
871
  }
872
+ reportedTheme = getRequestTheme();
870
873
  this.#send({
871
874
  type: "meta",
872
875
  requestId: options.requestId,
873
- theme: getRequestTheme(),
876
+ theme: reportedTheme,
874
877
  status: options.status,
875
878
  trace: options.trace,
876
879
  });
877
880
  };
881
+
882
+ const sendLateTheme = () => {
883
+ const theme = getRequestTheme();
884
+ if (!options.requestId || !sentMeta || theme === reportedTheme) return;
885
+ reportedTheme = theme;
886
+ this.#send({ type: "theme", requestId: options.requestId, theme });
887
+ };
878
888
  const sendLateRedirect = () => {
879
889
  if (!options.requestId || lateControlSent || controlRef.current?.type !== "redirect") return;
880
890
 
@@ -906,6 +916,7 @@ export class RscRenderer {
906
916
  if (options.collectChunks) chunks.push(chunk);
907
917
  if (options.requestId) {
908
918
  sendMeta();
919
+ sendLateTheme();
909
920
  this.#send({ type: "chunk", requestId: options.requestId, data: chunk });
910
921
  sentChunk = true;
911
922
  }
@@ -19,6 +19,7 @@ export interface RscPending {
19
19
  onEnd: () => void;
20
20
  onError: (message: string) => void;
21
21
  onMeta?: (meta: { theme?: AkanTheme; status?: number; trace?: RscTraceMetadata }) => void;
22
+ onTheme?: (theme: AkanTheme | undefined) => void;
22
23
  onCacheState?: (state: RouteCacheRenderState) => void;
23
24
  onRedirect?: (location: string, method: RscRedirectMethod, status: RscRedirectStatus) => void;
24
25
  onLateRedirect?: (location: string, method: RscRedirectMethod, status: RscRedirectStatus) => void;
@@ -209,7 +210,18 @@ export function createRscHostRenderStream(input: {
209
210
  const settleStream = () => {
210
211
  if (settled) return;
211
212
  settled = true;
212
- resolve({ type: "stream", stream, theme, status, trace, lateControl, cacheState, cancel: cancelRender });
213
+ resolve({
214
+ type: "stream",
215
+ stream,
216
+ get theme() {
217
+ return theme;
218
+ },
219
+ status,
220
+ trace,
221
+ lateControl,
222
+ cacheState,
223
+ cancel: cancelRender,
224
+ });
213
225
  };
214
226
  input.setPending({
215
227
  onMeta: (meta) => {
@@ -218,6 +230,9 @@ export function createRscHostRenderStream(input: {
218
230
  trace = meta.trace;
219
231
  settleStream();
220
232
  },
233
+ onTheme: (next) => {
234
+ theme = next;
235
+ },
221
236
  onChunk: (data) => {
222
237
  settleStream();
223
238
  pendingChunks = nextRscHostPendingChunkCount(pendingChunks, controller.desiredSize);
@@ -299,6 +314,7 @@ type RscInMsg =
299
314
  | { type: "ready" }
300
315
  | { type: "reloaded"; buildId: number; reloadId?: number; pagesBundlePath?: string }
301
316
  | { type: "meta"; requestId: string; theme?: AkanTheme; status?: number; trace?: RscTraceMetadata }
317
+ | { type: "theme"; requestId: string; theme?: AkanTheme }
302
318
  | { type: "cache-state"; requestId: string; state: RouteCacheRenderState }
303
319
  | { type: "chunk"; requestId: string; data: Uint8Array }
304
320
  | { type: "end"; requestId: string }
@@ -773,6 +789,9 @@ export class RscWorker {
773
789
  .get(message.requestId)
774
790
  ?.onMeta?.({ theme: message.theme, status: message.status, trace: message.trace });
775
791
  return;
792
+ case "theme":
793
+ this.#pending.get(message.requestId)?.onTheme?.(message.theme);
794
+ return;
776
795
  case "end":
777
796
  this.#resolvePending(message.requestId, (p) => p.onEnd());
778
797
  return;
@@ -637,7 +637,10 @@ export class WebRouter {
637
637
  bootstrapModules: [this.#artifact.rscClientUrl],
638
638
  extraBootstrapInline: extraBootstrapInline || undefined,
639
639
  importmap: this.#artifact.vendorMap,
640
- theme: cookieTheme ?? rscResult.theme ?? "system",
640
+
641
+ get theme() {
642
+ return cookieTheme ?? rscResult.theme ?? "system";
643
+ },
641
644
  lateControl: rscResult.lateControl,
642
645
  waitForAllReady: rscResult.trace?.ssrBlocking ?? false,
643
646
  onCancel: (reason: unknown) => {
@@ -16,6 +16,7 @@ export declare class Translator {
16
16
  static getActivePath(): string | undefined;
17
17
  static markHydrated(): void;
18
18
  static translateByLocale(lang: string, key: string, param?: Record<string, string | number>): string;
19
+ static withDefaultLocale(allDictionary: AllDictionary, lang: string): Dictionary | undefined;
19
20
  static seed(lang: string, dict: Dictionary | undefined): void;
20
21
  static replace(lang: string, dict: Dictionary | undefined): void;
21
22
  translate(lang: string, key: string, param?: Record<string, string | number>): string;
package/types/index.d.ts CHANGED
@@ -127,6 +127,8 @@ export interface AkanNativeUpdatesConfig {
127
127
  readyTimeout?: number;
128
128
  }
129
129
  export interface AkanNativeIosConfig {
130
+ /** Where the iOS app starts, over the section's `indexPath`. */
131
+ indexPath?: string;
130
132
  /** The Apple team id: the universal links of `deepLinks.domains` (apple-app-site-association). */
131
133
  teamId?: string;
132
134
  infoPlist?: Record<string, AkanNativeValue>;
@@ -137,6 +139,8 @@ export interface AkanNativeIosConfig {
137
139
  files?: Record<string, string>;
138
140
  }
139
141
  export interface AkanNativeAndroidConfig {
142
+ /** Where the Android app starts, over the section's `indexPath`. */
143
+ indexPath?: string;
140
144
  /** The signing certificates' SHA-256 fingerprints: the app links of `deepLinks.domains` (assetlinks.json). */
141
145
  sha256CertFingerprints?: string[];
142
146
  /** The Firebase project's google-services.json, relative to the app folder, for FCM push. */
@@ -154,13 +158,24 @@ export interface AkanNativeAndroidConfig {
154
158
  /** Files copied into the app, keyed `res/<type>/<file>` or `assets/<path>`; the value is the source. */
155
159
  files?: Record<string, string>;
156
160
  }
161
+ export interface AkanNativeDesktopServerConfig {
162
+ /**
163
+ * Packages the image installs and the desktop app's server goes without, along with what only they pull in: an
164
+ * addon that needs the image's system (a ROS install), code only a process the desktop app never starts loads.
165
+ * The server's own bundle must not import them. The image is unchanged.
166
+ */
167
+ omit?: string[];
168
+ }
157
169
  export interface AkanNativeDesktopConfig {
170
+ /** Where the desktop app starts, over the section's `indexPath`. */
171
+ indexPath?: string;
158
172
  /**
159
173
  * The desktop app carries the app's server (API only, database mode single, on loopback) and its pages call
160
- * nothing else. `build-desktop`, `start-desktop` and `publish-update` all read it, and an installed app refuses
161
- * a release that carries a server when it has none, or none when it has one.
174
+ * nothing else: `true`, or `{ omit }` to carry it without some of the image's packages. `build-desktop`,
175
+ * `start-desktop` and `publish-update` all read it, and an installed app refuses a release that carries a server
176
+ * when it has none, or none when it has one.
162
177
  */
163
- server?: boolean;
178
+ server?: boolean | AkanNativeDesktopServerConfig;
164
179
  /**
165
180
  * `"reload"`: a window whose page's process ends (a crash, a hang) loads it again every time, waiting
166
181
  * longer after each end in a row, and the app relaunches when the webview's browser process ends — for an
@@ -177,6 +192,12 @@ export interface AkanNativeDesktopConfig {
177
192
  * support on an unattended screen; it covers every media request, so not for an app that asks for a camera.
178
193
  */
179
194
  screenCapture?: "picker" | "auto";
195
+ /**
196
+ * macOS: entitlements of the app's executable beyond what it gets by itself — the JIT's under the hardened runtime and
197
+ * the camera's or microphone's when a usage text asks for it — e.g. `com.apple.security.cs.disable-library-validation`
198
+ * for a native addon signed by another team. Read when the app is signed with a team (a Developer ID).
199
+ */
200
+ entitlements?: Record<string, AkanNativeValue>;
180
201
  }
181
202
  /**
182
203
  * What a native app (iOS, Android, desktop) is: the app's `native` section and each of its targets have this shape. A
@@ -186,7 +207,10 @@ export interface AkanNativeDesktopConfig {
186
207
  export interface AkanNativeSettings {
187
208
  /** The client the app opens, a basePath the routes declare; an app without basePaths leaves it out. */
188
209
  basePath?: string;
189
- /** Where the app starts, and falls back to for a deep link's stack and a back with no history. Default `/`. */
210
+ /**
211
+ * Where the app starts, and falls back to for a deep link's stack and a back with no history. Default `/`. A platform
212
+ * section's own `indexPath` (`desktop.indexPath`) wins on that platform.
213
+ */
190
214
  indexPath?: string;
191
215
  /** Default: the app's folder name. */
192
216
  appName?: string;
@@ -15,6 +15,7 @@ export interface RscPending {
15
15
  status?: number;
16
16
  trace?: RscTraceMetadata;
17
17
  }) => void;
18
+ onTheme?: (theme: AkanTheme | undefined) => void;
18
19
  onCacheState?: (state: RouteCacheRenderState) => void;
19
20
  onRedirect?: (location: string, method: RscRedirectMethod, status: RscRedirectStatus) => void;
20
21
  onLateRedirect?: (location: string, method: RscRedirectMethod, status: RscRedirectStatus) => void;
@@ -2,12 +2,20 @@ import { type DoctorCheck } from "./commands/doctor.ts";
2
2
  import type { AkanNativeConfig } from "./config.ts";
3
3
  import type { Device, DeviceSelector } from "./lib/launch.ts";
4
4
  import { type LogEvent, type LogSink } from "./lib/log.ts";
5
- import { type AndroidSigning, type BuildProfile, type IosBuild, type IosSigning, type IosSigningResult, type WindowsBuild } from "./lib/prepare.ts";
6
- import { type UpdateManifest } from "./lib/updates.ts";
5
+ import { type MacosBuild, type MacosNotarization, type MacosSigning, macosDistributionFromEnv } from "./lib/macossigning.ts";
6
+ import { type AndroidSigning, type BuildProfile, type IosBuild, type IosSigning, type IosSigningResult, type LinuxBuild, type WindowsBuild } from "./lib/prepare.ts";
7
+ import { type DesktopArch, type UpdateManifest } from "./lib/updates.ts";
8
+ import { type WindowsSigning, windowsSigningFromEnv } from "./lib/windowssigning.ts";
7
9
  import { type TargetPlatform } from "./platforms/index.ts";
8
- export type { AkanNativeConfig, AndroidSigning, Device, DeviceSelector, DoctorCheck, IosBuild, IosSigning, IosSigningResult, LogEvent, TargetPlatform, WindowsBuild, };
10
+ export type { AkanNativeConfig, AndroidSigning, DesktopArch, Device, DeviceSelector, DoctorCheck, IosBuild, IosSigning, IosSigningResult, LinuxBuild, LogEvent, MacosBuild, MacosNotarization, MacosSigning, TargetPlatform, WindowsBuild, WindowsSigning, };
11
+ /**
12
+ * The macOS signing and notarization the AKAN_NATIVE_MACOS_* variables name, for a caller that keeps them in its
13
+ * environment (the API itself reads no variable): pass the result as `macos`.
14
+ */
15
+ /** The Authenticode settings the AKAN_NATIVE_WINDOWS_* variables name: pass the result as `windows.signing`. */
16
+ export { macosDistributionFromEnv, windowsSigningFromEnv };
9
17
  /** semver of this API. A caller checks the major before it relies on anything here. */
10
- export declare const API_VERSION = "0.9.0";
18
+ export declare const API_VERSION = "0.10.0";
11
19
  export type AkanNativeErrorCode = "CONFIG_INVALID" | "TOOLCHAIN_MISSING" | "WEB_INPUT_INVALID" | "WEB_BUILD_FAILED" | "NATIVE_BUILD_FAILED" | "SIGNING_FAILED" | "DEVICE_FAILED" | "CANCELLED" | "INTERNAL";
12
20
  export declare class AkanNativeError extends Error {
13
21
  readonly code: AkanNativeErrorCode;
@@ -89,6 +97,10 @@ export declare function build(options: TaskOptions & {
89
97
  profile?: BuildProfile;
90
98
  ios?: IosBuild;
91
99
  windows?: WindowsBuild;
100
+ macos?: MacosBuild;
101
+ linux?: LinuxBuild;
102
+ /** Desktop: the CPU the app runs on, of the same OS; a macOS app is arm64 only. Default this computer's. */
103
+ arch?: DesktopArch;
92
104
  }): Promise<BuildResult>;
93
105
  /**
94
106
  * Store files: a release build signed for distribution. Android: the .aab (and, with formats, the
@@ -150,6 +162,10 @@ export declare function publishUpdate(options: TaskOptions & {
150
162
  platform: Exclude<TargetPlatform, "web">;
151
163
  channel?: string;
152
164
  out?: string;
165
+ /** The signature a macOS release carries: the installed app's own, or the updater refuses it. No dmg is made. */
166
+ macos?: Pick<MacosBuild, "signing" | "notarize">;
167
+ /** Authenticode for a Windows release's files. No setup program is made. */
168
+ windows?: Pick<WindowsBuild, "signing">;
153
169
  }): Promise<PublishResult>;
154
170
  /**
155
171
  * Whether a web bundle can run in the binary a store release shipped (UP-3): `shipped` is that build's bundle.json,
@@ -148,6 +148,13 @@ export interface AkanNativeConfig {
148
148
  /** Entitlements (e.g. keychain-access-groups). associated-domains comes from deepLinks.domains. */
149
149
  entitlements?: Record<string, NativeValue>;
150
150
  };
151
+ macos?: {
152
+ /**
153
+ * Entitlements of the main executable, over the hardened runtime's (allow-jit, allow-unsigned-executable-memory)
154
+ * and those the usage texts ask for (the camera's, the microphone's). Read when the app is signed with a team.
155
+ */
156
+ entitlements?: Record<string, NativeValue>;
157
+ };
151
158
  android?: {
152
159
  /** XML at the <manifest> level (uses-permission, queries, …). ${applicationId} is replaced. */
153
160
  manifest?: string[];
@@ -0,0 +1,19 @@
1
+ export type AddonOs = "macos" | "windows" | "linux";
2
+ export type AddonArch = "arm64" | "x64";
3
+ export interface BinaryInfo {
4
+ format: "mach-o" | "elf" | "pe";
5
+ /** Every architecture the file holds (a fat Mach-O holds several). */
6
+ archs: (AddonArch | "other")[];
7
+ /** Libraries it loads by an absolute path, and the absolute search paths it names (rpath, runpath). */
8
+ absoluteLinks: string[];
9
+ }
10
+ export interface AddonReport {
11
+ problems: string[];
12
+ warnings: string[];
13
+ }
14
+ /** What a desktop build refuses and what it warns about in `serverDir`'s `.node` files, by package. */
15
+ export declare function addonReport(serverDir: string, os: AddonOs, arch: AddonArch): AddonReport;
16
+ /** The folder of the package a file belongs to: the deepest node_modules/<name> (or <@scope>/<name>) above it. */
17
+ export declare function packageRoot(base: string, file: string): string;
18
+ /** The format, CPUs and absolute links of a Mach-O, ELF or PE file; null for anything else. */
19
+ export declare function binaryInfo(bytes: Buffer): BinaryInfo | null;
@@ -0,0 +1,95 @@
1
+ import { type PlistValue } from "./plist.ts";
2
+ import { type SigningIdentity } from "./signing.ts";
3
+ export interface MacosSigning {
4
+ /** A certificate name ("Developer ID Application: Name (TEAMID)") or its SHA-1, in a keychain already. */
5
+ identity?: string;
6
+ /** A .p12 holding the identity: imported into a keychain of its own for the build, then removed. */
7
+ certificate?: {
8
+ path: string;
9
+ password: string;
10
+ };
11
+ }
12
+ /** App Store Connect API key (key, keyId, issuer), or a profile `xcrun notarytool store-credentials` saved. */
13
+ export interface MacosNotarization {
14
+ /** The AuthKey_<id>.p8 file. */
15
+ key?: string;
16
+ keyId?: string;
17
+ /** The issuer id; left out for an individual API key. */
18
+ issuer?: string;
19
+ profile?: string;
20
+ }
21
+ export interface MacosBuild {
22
+ signing?: MacosSigning;
23
+ /** Release builds signed with a Developer ID only. */
24
+ notarize?: MacosNotarization;
25
+ /** A .dmg beside the app, with an Applications link; signed and notarized as the app is. */
26
+ dmg?: boolean;
27
+ }
28
+ export type MacosSignature = "adhoc" | "identity" | "development" | "distribution";
29
+ export interface ResolvedMacosIdentity {
30
+ /** The certificate's SHA-1, or "-" for ad hoc. */
31
+ sign: string;
32
+ name: string;
33
+ kind: MacosSignature;
34
+ teamId?: string;
35
+ /** Set when the identity lives in a keychain made for this build. */
36
+ keychain?: string;
37
+ }
38
+ /**
39
+ * Entitlements the hardened runtime needs for the app's own code: Bun's JavaScriptCore writes JIT code, and the
40
+ * TCC resources the Info.plist asks for are refused to a hardened app that does not claim them.
41
+ */
42
+ export declare const RUNTIME_ENTITLEMENTS: Record<string, PlistValue>;
43
+ /** The main executable's entitlements: the runtime's, those of the usage texts in `infoPlist`, then the app's own. */
44
+ export declare function appEntitlements(infoPlist: Record<string, PlistValue>, extra?: Record<string, PlistValue>): Record<string, PlistValue>;
45
+ /**
46
+ * The kind of a certificate by its name. A Developer ID's parentheses hold the team id; an Apple Development
47
+ * certificate's hold the member's own id, so its team is not read from the name.
48
+ */
49
+ export declare function identityKind(name: string): {
50
+ kind: MacosSignature;
51
+ teamId?: string;
52
+ };
53
+ /** The identity `wanted` names (a name or a SHA-1) among `identities`. */
54
+ export declare function pickMacosIdentity(identities: SigningIdentity[], wanted: string): SigningIdentity;
55
+ /**
56
+ * Runs `fn` with the identity `signing` names, or ad hoc without one. A certificate file goes into a keychain made for
57
+ * this build (on the user's search list meanwhile, so codesign finds its chain), deleted afterwards.
58
+ */
59
+ export declare function withMacosIdentity<T>(signing: MacosSigning | undefined, fn: (identity: ResolvedMacosIdentity) => Promise<T>, fallback: () => Promise<ResolvedMacosIdentity>): Promise<T>;
60
+ export interface CodesignOptions {
61
+ /** The hardened runtime and a secure timestamp, what notarization requires of every Mach-O file. */
62
+ hardened: boolean;
63
+ /** A secure timestamp without the runtime (a disk image). Default: `hardened`. */
64
+ timestamp?: boolean;
65
+ entitlements?: string;
66
+ }
67
+ /** codesign arguments for one file. Apple's timestamp server only stamps certificates Apple issued. */
68
+ export declare function codesignArgs(identity: ResolvedMacosIdentity, file: string, options: CodesignOptions): string[];
69
+ export declare function codesign(identity: ResolvedMacosIdentity, file: string, options: CodesignOptions): Promise<void>;
70
+ export declare function writeEntitlements(dir: string, entitlements: Record<string, PlistValue>): string;
71
+ export declare function notarytoolAuth(notarization: MacosNotarization): string[];
72
+ interface NotarySubmission {
73
+ id?: string;
74
+ status?: string;
75
+ message?: string;
76
+ }
77
+ /** Submits `file` (a .zip, .dmg or .pkg), waits for Apple's verdict and refuses anything but Accepted. */
78
+ export declare function notarize(file: string, notarization: MacosNotarization): Promise<string>;
79
+ export declare function parseSubmission(stdout: string): NotarySubmission;
80
+ /** One line per issue of a notarytool log: the file and Apple's message. */
81
+ export declare function notaryLogIssues(stdout: string): string[];
82
+ export declare function staple(file: string): Promise<void>;
83
+ /** The .app as notarytool takes it: a zip that keeps the bundle's symlinks and signature. */
84
+ export declare function zipForNotary(appPath: string, zip: string): Promise<void>;
85
+ /** A compressed disk image of the app beside a link to /Applications, the window people drag it into. */
86
+ export declare function buildDmg(appPath: string, dmgPath: string, volumeName: string, workDir: string): Promise<void>;
87
+ /** What Gatekeeper says of a notarized app or disk image, as a downloaded copy would be judged. */
88
+ export declare function assessGatekeeper(file: string, type: "execute" | "open"): Promise<void>;
89
+ /**
90
+ * Settings from the environment for the command line (the API takes them as options only):
91
+ * AKAN_NATIVE_MACOS_IDENTITY, AKAN_NATIVE_MACOS_CERTIFICATE + _PASSWORD, and AKAN_NATIVE_MACOS_NOTARY_KEY +
92
+ * _KEY_ID + _ISSUER or AKAN_NATIVE_MACOS_NOTARY_PROFILE.
93
+ */
94
+ export declare function macosDistributionFromEnv(env?: Record<string, string | undefined>): Pick<MacosBuild, "signing" | "notarize">;
95
+ export {};
@@ -1,6 +1,9 @@
1
1
  import type { Platform } from "../../../core/src/index.ts";
2
2
  import { CliError } from "./log.ts";
3
+ import type { MacosBuild } from "./macossigning.ts";
3
4
  import { type Project } from "./project.ts";
5
+ import { type DesktopArch } from "./updates.ts";
6
+ import type { WindowsSigning } from "./windowssigning.ts";
4
7
  /**
5
8
  * debug: inspectable webview, dev signing, runtime env overrides, the dev server (akan-native run, dev,
6
9
  * test). release: none of that (akan-native build, update publish). Independent of `mode`, which only
@@ -9,6 +12,8 @@ import { type Project } from "./project.ts";
9
12
  export type BuildProfile = "debug" | "release";
10
13
  export interface WindowsBuild {
11
14
  installer?: boolean;
15
+ /** Authenticode for every PE file, the setup program and its uninstaller (lib/windowssigning.ts). */
16
+ signing?: WindowsSigning;
12
17
  }
13
18
  export interface BuildOptions {
14
19
  mode: string;
@@ -43,6 +48,15 @@ export interface BuildOptions {
43
48
  ios?: IosBuild;
44
49
  /** Windows: an NSIS setup program next to the app folder (platforms/windows-installer.ts). */
45
50
  windows?: WindowsBuild;
51
+ /** macOS: Developer ID signing, notarization and a dmg (lib/macossigning.ts). */
52
+ macos?: MacosBuild;
53
+ /** Linux: an AppImage beside the app folder (platforms/linux-appimage.ts). */
54
+ linux?: LinuxBuild;
55
+ /** Desktop builds: the CPU the app runs on, of the same OS; a macOS app is arm64 only. Default this computer's. */
56
+ arch?: DesktopArch;
57
+ }
58
+ export interface LinuxBuild {
59
+ appImage?: boolean;
46
60
  }
47
61
  /** Which identity and profile an iOS device build signs with; unset: found in the keychain and Xcode's profiles. */
48
62
  export interface IosSigning {
@@ -117,13 +131,17 @@ export interface BuildContext {
117
131
  android?: AndroidRelease;
118
132
  ios?: IosBuild;
119
133
  windows?: WindowsBuild;
134
+ macos?: MacosBuild;
135
+ linux?: LinuxBuild;
136
+ /** Desktop builds: see BuildOptions.arch, resolved. */
137
+ arch?: DesktopArch;
120
138
  /** Artifacts besides the one the builder returns (an .aab next to the .apk, a Windows setup program). */
121
139
  artifacts: {
122
140
  kind: "aab" | "ipa" | "installer";
123
141
  path: string;
124
142
  }[];
125
143
  /** What the builder signed with, when it signs (Android: the app's release key or the debug key). */
126
- signedAs?: "debug" | "development" | "distribution";
144
+ signedAs?: "adhoc" | "debug" | "development" | "distribution";
127
145
  /** Called through the API (BuildOptions.api). */
128
146
  api: boolean;
129
147
  /** Android builds with pinned libraries: the license notices file written next to the app (BuildResult.licenses). */
@@ -140,5 +158,7 @@ export declare class WebBuildError extends CliError {
140
158
  export declare class WebInputError extends CliError {
141
159
  }
142
160
  export declare function prepare(project: Project, platform: Platform, options: BuildOptions): Promise<BuildContext>;
161
+ /** The arch a build makes: the one asked for, checked against the platform, else this computer's for a desktop. */
162
+ export declare function desktopArch(platform: Platform, arch: DesktopArch | undefined): DesktopArch | undefined;
143
163
  /** https, or http to this machine (a local test server, `akan-native update serve`). */
144
164
  export declare function secureUpdateUrl(url: string): boolean;
@@ -4,8 +4,8 @@ export interface SigningIdentity {
4
4
  name: string;
5
5
  }
6
6
  export declare const identityName: () => string;
7
- /** Valid (trusted) code-signing identities in the keychains. */
8
- export declare function validIdentities(): Promise<SigningIdentity[]>;
7
+ /** Valid (trusted) code-signing identities in the keychains, or in `keychain` only. */
8
+ export declare function validIdentities(keychain?: string): Promise<SigningIdentity[]>;
9
9
  /** The identity `akan-native signing setup` made, if it is still valid; null means ad-hoc signing. */
10
10
  export declare function devIdentity(env?: NodeJS.ProcessEnv): Promise<SigningIdentity | null>;
11
11
  export declare function setupSigning(): Promise<{
@@ -40,6 +40,15 @@ export declare const TOOLCHAIN: {
40
40
  root: string;
41
41
  };
42
42
  };
43
+ /**
44
+ * The AppImage type 2 runtime (CLI-9), the ELF a Linux AppImage starts with: it mounts the squashfs appended to it
45
+ * and runs its AppRun. A dated release, since `continuous` moves; the digests are the ones GitHub publishes.
46
+ */
47
+ readonly appimageRuntime: {
48
+ readonly version: "20251108";
49
+ readonly license: "MIT";
50
+ readonly downloads: Record<string, Download>;
51
+ };
43
52
  readonly android: {
44
53
  readonly buildTools: "37.0.0";
45
54
  /** Older build-tools still work (with a warning); below this they do not. */
@@ -0,0 +1,30 @@
1
+ export interface WindowsSigning {
2
+ /** A .pfx holding the code-signing certificate and its key. */
3
+ certificate?: {
4
+ path: string;
5
+ password: string;
6
+ };
7
+ /** A certificate in the user's or the machine's store, by its SHA-1 thumbprint (a token or an HSM keeps its key there). */
8
+ thumbprint?: string;
9
+ /** Any other signer, run once per file with `{file}` replaced, e.g. signtool with Azure Trusted Signing's dlib. */
10
+ command?: string[];
11
+ /** RFC 3161 timestamp server. Default http://timestamp.digicert.com. */
12
+ timestampUrl?: string;
13
+ }
14
+ export declare const DEFAULT_TIMESTAMP_URL = "http://timestamp.digicert.com";
15
+ /** signtool: AKAN_NATIVE_SIGNTOOL, PATH, then the newest Windows 10/11 SDK's for this CPU. */
16
+ export declare function findSigntool(env?: Record<string, string | undefined>): string | null;
17
+ /** signtool's arguments after the executable, for `files` in one call. */
18
+ export declare function signtoolArgs(signing: WindowsSigning, files: string[], description: string): string[];
19
+ /** The command a `command` signer runs for one file. */
20
+ export declare function signerCommand(command: string[], file: string): string[];
21
+ /** Signs `files` (Authenticode, SHA-256, timestamped), then verifies each against the system's trusted roots. */
22
+ export declare function signWindowsFiles(signing: WindowsSigning, files: string[], description: string): Promise<void>;
23
+ /** Every PE file under `dir` (programs, DLLs, native addons), whatever its name. */
24
+ export declare function peFiles(dir: string): string[];
25
+ /**
26
+ * Settings from the environment (the API takes them as options only): AKAN_NATIVE_WINDOWS_CERTIFICATE + _PASSWORD,
27
+ * AKAN_NATIVE_WINDOWS_THUMBPRINT, or AKAN_NATIVE_WINDOWS_SIGN_COMMAND (a JSON array of arguments, `{file}` replaced),
28
+ * and AKAN_NATIVE_WINDOWS_TIMESTAMP_URL.
29
+ */
30
+ export declare function windowsSigningFromEnv(env?: Record<string, string | undefined>): WindowsSigning | undefined;