akanjs 3.0.0-beta.31 → 3.0.0-beta.33

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 +19 -3
  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 +18 -3
  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
@@ -175,15 +175,25 @@ export interface AkanNativeAndroidConfig {
175
175
  files?: Record<string, string>;
176
176
  }
177
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
+
178
187
  export interface AkanNativeDesktopConfig {
179
188
  /** Where the desktop app starts, over the section's `indexPath`. */
180
189
  indexPath?: string;
181
190
  /**
182
191
  * The desktop app carries the app's server (API only, database mode single, on loopback) and its pages call
183
- * nothing else. `build-desktop`, `start-desktop` and `publish-update` all read it, and an installed app refuses
184
- * 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.
185
195
  */
186
- server?: boolean;
196
+ server?: boolean | AkanNativeDesktopServerConfig;
187
197
  /**
188
198
  * `"reload"`: a window whose page's process ends (a crash, a hang) loads it again every time, waiting
189
199
  * longer after each end in a row, and the app relaunches when the webview's browser process ends — for an
@@ -197,6 +207,12 @@ export interface AkanNativeDesktopConfig {
197
207
  * support on an unattended screen; it covers every media request, so not for an app that asks for a camera.
198
208
  */
199
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>;
200
216
  }
201
217
 
202
218
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akanjs",
3
- "version": "3.0.0-beta.31",
3
+ "version": "3.0.0-beta.33",
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
@@ -158,15 +158,24 @@ export interface AkanNativeAndroidConfig {
158
158
  /** Files copied into the app, keyed `res/<type>/<file>` or `assets/<path>`; the value is the source. */
159
159
  files?: Record<string, string>;
160
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
+ }
161
169
  export interface AkanNativeDesktopConfig {
162
170
  /** Where the desktop app starts, over the section's `indexPath`. */
163
171
  indexPath?: string;
164
172
  /**
165
173
  * The desktop app carries the app's server (API only, database mode single, on loopback) and its pages call
166
- * nothing else. `build-desktop`, `start-desktop` and `publish-update` all read it, and an installed app refuses
167
- * 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.
168
177
  */
169
- server?: boolean;
178
+ server?: boolean | AkanNativeDesktopServerConfig;
170
179
  /**
171
180
  * `"reload"`: a window whose page's process ends (a crash, a hang) loads it again every time, waiting
172
181
  * longer after each end in a row, and the app relaunches when the webview's browser process ends — for an
@@ -183,6 +192,12 @@ export interface AkanNativeDesktopConfig {
183
192
  * support on an unattended screen; it covers every media request, so not for an app that asks for a camera.
184
193
  */
185
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>;
186
201
  }
187
202
  /**
188
203
  * What a native app (iOS, Android, desktop) is: the app's `native` section and each of its targets have this shape. A
@@ -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;
@@ -1,6 +1,7 @@
1
1
  import type { ParsedArgs } from "../lib/args.ts";
2
2
  import { type Launched, type LaunchOptions } from "../lib/launch.ts";
3
3
  import type { BuildContext } from "../lib/prepare.ts";
4
+ import { type DesktopArch } from "../lib/updates.ts";
4
5
  export declare const NATIVE_DIR: string;
5
6
  export type DesktopOs = "macos" | "windows" | "linux";
6
7
  /** The cdylib cargo builds, per OS. */
@@ -17,14 +18,19 @@ export declare function resourcesOf(os: DesktopOs, artifact: string): string;
17
18
  */
18
19
  export declare function cargoTargetDir(): string;
19
20
  /** Builds (incrementally) the Rust cdylib and returns its path. */
20
- export declare function buildNativeLibrary(os: DesktopOs, release: boolean): Promise<string>;
21
+ export declare function buildNativeLibrary(os: DesktopOs, release: boolean, arch?: DesktopArch): Promise<string>;
21
22
  /**
22
23
  * The app never reads a .env or bunfig.toml from the folder it is started in: Bun's standalone
23
24
  * executables do by default, which would let a file next to the app set its environment and
24
25
  * runtime options.
25
26
  */
26
27
  export declare const COMPILE_FLAGS: string[];
27
- /** bun build --compile of the main thread entry and the plugin host Worker into `outfile`. */
28
+ /** The CPU a desktop build makes code for. */
29
+ export declare function targetArch(ctx: Pick<BuildContext, "arch">): DesktopArch;
30
+ /**
31
+ * bun build --compile of the main thread entry and the plugin host Worker into `outfile`, for the build's CPU: another
32
+ * CPU's Bun is a cross-compilation target Bun downloads once.
33
+ */
28
34
  export declare function compileExecutable(ctx: BuildContext, outfile: string, extra?: string[]): Promise<void>;
29
35
  /** The resources folder every desktop app reads (see the top of this file). */
30
36
  export declare function writeDesktopResources(ctx: BuildContext, resources: string, os: DesktopOs, shellExtra?: Record<string, unknown>): void;
@@ -0,0 +1,11 @@
1
+ import type { BuildContext } from "../lib/prepare.ts";
2
+ /** The launcher entry (freedesktop Desktop Entry): what a menu shows and which link schemes open the app. */
3
+ export declare function desktopEntry(input: {
4
+ name: string;
5
+ fileName: string;
6
+ schemes: string[];
7
+ }): string;
8
+ /** AppRun: the runtime starts it from the mounted image; it starts the app from beside itself. */
9
+ export declare function appRun(exe: string): string;
10
+ /** Writes the AppImage beside the app folder and answers its path. */
11
+ export declare function buildAppImage(ctx: BuildContext, folder: string): Promise<string>;
@@ -27,6 +27,8 @@ export interface InstallerInput {
27
27
  bootstrapper: string;
28
28
  stopScript: string;
29
29
  outFile: string;
30
+ /** A command that signs the uninstaller makensis writes into the setup program, `%1` its path. */
31
+ signUninstaller?: string;
30
32
  }
31
33
  export declare function installerScript(input: InstallerInput): string;
32
34
  /**