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.
- package/client/translator.ts +26 -0
- package/index.ts +19 -3
- package/package.json +1 -1
- package/server/rscWorker.tsx +12 -1
- package/server/rscWorkerHost.ts +20 -1
- package/server/webRouter.ts +4 -1
- package/types/client/translator.d.ts +1 -0
- package/types/index.d.ts +18 -3
- package/types/server/rscWorkerHost.d.ts +1 -0
- package/types/vendor/@akanjs/native/packages/cli/src/api.d.ts +20 -4
- package/types/vendor/@akanjs/native/packages/cli/src/config.d.ts +7 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/addons.d.ts +19 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/macossigning.d.ts +95 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/prepare.d.ts +21 -1
- package/types/vendor/@akanjs/native/packages/cli/src/lib/signing.d.ts +2 -2
- package/types/vendor/@akanjs/native/packages/cli/src/lib/toolchains.d.ts +9 -0
- package/types/vendor/@akanjs/native/packages/cli/src/lib/windowssigning.d.ts +30 -0
- package/types/vendor/@akanjs/native/packages/cli/src/platforms/desktop.d.ts +8 -2
- package/types/vendor/@akanjs/native/packages/cli/src/platforms/linux-appimage.d.ts +11 -0
- package/types/vendor/@akanjs/native/packages/cli/src/platforms/windows-installer.d.ts +2 -0
- package/ui/System/Client.tsx +9 -16
- package/ui/System/SSR.tsx +1 -1
- package/vendor/@akanjs/native/packages/cli/src/api.ts +53 -7
- package/vendor/@akanjs/native/packages/cli/src/commands/build.ts +38 -4
- package/vendor/@akanjs/native/packages/cli/src/config.ts +7 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/addons.ts +217 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/configkeys.ts +1 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/macossigning.ts +357 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/prepare.ts +34 -1
- package/vendor/@akanjs/native/packages/cli/src/lib/project.ts +1 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/signing.ts +4 -3
- package/vendor/@akanjs/native/packages/cli/src/lib/toolchains.ts +24 -0
- package/vendor/@akanjs/native/packages/cli/src/lib/windowssigning.ts +155 -0
- package/vendor/@akanjs/native/packages/cli/src/platforms/desktop.ts +30 -13
- package/vendor/@akanjs/native/packages/cli/src/platforms/linux-appimage.ts +84 -0
- package/vendor/@akanjs/native/packages/cli/src/platforms/linux.ts +4 -1
- package/vendor/@akanjs/native/packages/cli/src/platforms/macos.ts +115 -43
- package/vendor/@akanjs/native/packages/cli/src/platforms/windows-installer.ts +29 -2
- package/vendor/@akanjs/native/packages/cli/src/platforms/windows.ts +12 -1
package/client/translator.ts
CHANGED
|
@@ -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
|
|
184
|
-
*
|
|
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
package/server/rscWorker.tsx
CHANGED
|
@@ -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:
|
|
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
|
}
|
package/server/rscWorkerHost.ts
CHANGED
|
@@ -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({
|
|
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;
|
package/server/webRouter.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
167
|
-
*
|
|
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
|
|
6
|
-
import { type
|
|
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.
|
|
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
|
-
/**
|
|
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
|
/**
|