@zogien/native-sdk 0.11.0-fork.2 → 0.11.0-fork.4
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/README.md +1 -1
- package/build/app.zig +47 -23
- package/build/cli.zig +22 -0
- package/native-sdk.d.ts +2 -0
- package/package.json +10 -10
- package/packages/core/package-lock.json +2 -2
- package/packages/core/package.json +1 -1
- package/schemas/app.schema.json +9 -1
- package/skill-data/ts-core/SKILL.md +2 -0
- package/src/app_runner/app_assets.zig +85 -1
- package/src/app_runner/root.zig +63 -3
- package/src/app_runner/ts_core_main.zig +8 -0
- package/src/platform/macos/appkit_host.h +9 -0
- package/src/platform/macos/appkit_host.m +366 -0
- package/src/platform/macos/root.zig +29 -6
- package/src/platform/null_platform.zig +66 -0
- package/src/platform/root.zig +1 -0
- package/src/platform/types.zig +46 -0
- package/src/platform/windows/root.zig +174 -0
- package/src/platform/windows/scriptc_mingw_compat.c +248 -0
- package/src/platform/windows/webview2_host.cpp +500 -26
- package/src/primitives/app_manifest/root.zig +1 -0
- package/src/primitives/app_manifest/tests.zig +35 -0
- package/src/primitives/app_manifest/types.zig +12 -0
- package/src/primitives/app_manifest/validation.zig +44 -6
- package/src/root.zig +1 -0
- package/src/runtime/bridge_responses.zig +2 -0
- package/src/runtime/channel_sources.zig +49 -1
- package/src/runtime/core.zig +5 -0
- package/src/runtime/effects.zig +12 -1
- package/src/runtime/flow.zig +6 -0
- package/src/runtime/flow_reference.zig +6 -0
- package/src/runtime/root.zig +4 -0
- package/src/runtime/single_instance.zig +893 -0
- package/src/runtime/tests.zig +1 -0
- package/src/runtime/window_controls.zig +73 -0
- package/src/runtime/window_controls_tests.zig +192 -0
- package/src/runtime/window_storage.zig +3 -0
- package/src/tooling/manifest.zig +111 -3
- package/src/tooling/package.zig +98 -8
- package/src/tooling/raw_manifest.zig +7 -0
- package/src/tooling/root.zig +2 -0
- package/src/tooling/templates.zig +53 -3
- package/src/tooling/update.zig +228 -10
- package/src/tooling/windows_installer.zig +245 -0
- package/src/tooling/zip_writer.zig +211 -0
- package/src/updater/engine.zig +936 -0
- package/src/updater/feed.zig +261 -12
- package/src/updater/macos.zig +187 -0
- package/src/updater/root.zig +41 -0
- package/src/updater/windows.zig +470 -0
- package/src/windows_install/config.zig +247 -0
- package/src/windows_install/main.zig +690 -0
- package/src/windows_install/payload.zig +146 -0
- package/src/windows_install/switch.zig +184 -0
- package/src/windows_install/tests.zig +11 -0
- package/src/windows_install/win32.zig +248 -0
- package/tools/native-sdk/main.zig +10 -9
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ The default app core uses a checked TypeScript subset and compiles to native cod
|
|
|
11
11
|
Requires Node.js 24 or later. The CLI command remains `native`.
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npm install -g @zogien/native-sdk
|
|
14
|
+
npm install -g @zogien/native-sdk
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
The `native` binary arrives as a per-platform optional dependency (`@zogien/native-sdk-<platform>`). Installation also downloads the exact scriptc release, verifies its checksum, and installs the matching compiler API and runtime packs into `~/.native/toolchains/scriptc/`. This package carries the SDK source your apps build against, so `native init` and `native dev` work offline after installation. If npm install scripts are disabled, run `node packages/core/scripts/install_scriptc.mjs` from the installed CLI package before building. `NATIVE_SDK_SCRIPTC_CACHE` selects another toolchain cache directory. The pinned Zig toolchain is fetched into `~/.native/toolchains/` on first build unless a compatible `zig` is already on your PATH.
|
package/build/app.zig
CHANGED
|
@@ -2017,8 +2017,14 @@ pub fn addAppArtifacts(b: *std.Build, dep: *std.Build.Dependency, app_options: A
|
|
|
2017
2017
|
// on GUI exes (handles inherit; only console AUTO-allocation is
|
|
2018
2018
|
// gated by the subsystem), so automation harnesses that pipe
|
|
2019
2019
|
// `app.exe > log 2>&1` keep their logs either way.
|
|
2020
|
+
//
|
|
2021
|
+
// MSVC's CRT enters a GUI-subsystem image through WinMainCRTStartup,
|
|
2022
|
+
// which calls WinMain, but Zig apps export only `main`; MinGW's startup
|
|
2023
|
+
// calls `main` under either subsystem. MSVC targets therefore keep the
|
|
2024
|
+
// GUI subsystem and enter through the console CRT entry instead.
|
|
2020
2025
|
if (target.result.os.tag == .windows and app_optimize != .Debug) {
|
|
2021
2026
|
exe.subsystem = .windows;
|
|
2027
|
+
if (target.result.abi == .msvc) exe.entry = .{ .symbol_name = "mainCRTStartup" };
|
|
2022
2028
|
}
|
|
2023
2029
|
linkPlatform(b, dep, target, app_mod, exe, selected_platform, web_engine, web_layer, cef_dir, cef_auto_install);
|
|
2024
2030
|
const install = b.addInstallArtifact(exe, .{});
|
|
@@ -2096,32 +2102,25 @@ pub fn addAppArtifacts(b: *std.Build, dep: *std.Build.Dependency, app_options: A
|
|
|
2096
2102
|
// not just its test failures. Test builds never analyze `main` (the
|
|
2097
2103
|
// test runner replaces the entry point), so rules that fire inside it
|
|
2098
2104
|
// — UiApp.create's Model-defaults rule above all — used to ambush at
|
|
2099
|
-
// `native build`, the LAST step in the loop.
|
|
2100
|
-
//
|
|
2101
|
-
//
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
const analysis_mod = b.createModule(.{
|
|
2114
|
-
.root_source_file = analysis_root,
|
|
2115
|
-
.target = target,
|
|
2116
|
-
.optimize = optimize,
|
|
2117
|
-
});
|
|
2118
|
-
analysis_mod.addImport("app", test_app_mod);
|
|
2119
|
-
const analysis_obj = b.addObject(.{
|
|
2105
|
+
// `native build`, the LAST step in the loop. This executable roots at
|
|
2106
|
+
// the test app module itself, so std.start analyzes `main` exactly as
|
|
2107
|
+
// the app's own entry build does: root-level decls, the body, and the
|
|
2108
|
+
// entry signature. Test time is therefore never stricter, nor laxer,
|
|
2109
|
+
// than the build it fronts. Nothing requests its binary, so Zig
|
|
2110
|
+
// compiles it with -fno-emit-bin: nothing links or runs, and nothing
|
|
2111
|
+
// may ever install it or ask for its emitted bin.
|
|
2112
|
+
//
|
|
2113
|
+
// It must stay an executable. Zig refuses any COFF object compile with
|
|
2114
|
+
// more than one object, archive, or C input, -fno-emit-bin included,
|
|
2115
|
+
// and Windows app modules routinely carry several: the compiled core
|
|
2116
|
+
// archive beside the Debug markup data object, the platform hosts'
|
|
2117
|
+
// C++ sources, and MSVC system import libraries.
|
|
2118
|
+
const entry_analysis = b.addExecutable(.{
|
|
2120
2119
|
.name = b.fmt("{s}-analysis", .{app_options.name}),
|
|
2121
|
-
.root_module =
|
|
2120
|
+
.root_module = test_app_mod,
|
|
2122
2121
|
.use_llvm = useLlvmWorkaround(target),
|
|
2123
2122
|
});
|
|
2124
|
-
test_step.dependOn(&
|
|
2123
|
+
test_step.dependOn(&entry_analysis.step);
|
|
2125
2124
|
|
|
2126
2125
|
// `zig build model-contract`: reflect the app's Model/Msg into
|
|
2127
2126
|
// zig-out/model-contract.zon so `native check` can verify markup
|
|
@@ -2212,6 +2211,9 @@ pub fn addAppArtifacts(b: *std.Build, dep: *std.Build.Dependency, app_options: A
|
|
|
2212
2211
|
package_run.addArgs(&.{ "--web-engine", @tagName(web_engine), "--cef-dir", cef_dir });
|
|
2213
2212
|
if (cef_auto_install) package_run.addArg("--cef-auto-install");
|
|
2214
2213
|
if (app_config.updates_enabled and host_os == .macos) package_run.addArg("--update-archive");
|
|
2214
|
+
// Windows updates reach only installed apps, so an app with updates
|
|
2215
|
+
// ships the per-user setup program beside the archive its feed names.
|
|
2216
|
+
if (app_config.updates_enabled and host_os == .windows) package_run.addArgs(&.{ "--installer", "--update-archive" });
|
|
2215
2217
|
package_run.has_side_effects = true;
|
|
2216
2218
|
const package_step = b.step("package", "Create a distributable package via the native CLI");
|
|
2217
2219
|
package_step.dependOn(&package_run.step);
|
|
@@ -2314,6 +2316,9 @@ fn appModule(b: *std.Build, dep: *std.Build.Dependency, target: std.Build.Resolv
|
|
|
2314
2316
|
if (link_ts_archives) {
|
|
2315
2317
|
if (stage.service_archive) |service_archive| app_mod.addObjectFile(service_archive);
|
|
2316
2318
|
}
|
|
2319
|
+
if (link_ts_archives and target.result.os.tag == .windows and target.result.abi == .msvc) {
|
|
2320
|
+
linkScriptcMsvcRuntime(dep, app_mod);
|
|
2321
|
+
}
|
|
2317
2322
|
}
|
|
2318
2323
|
if (app_config.sqlite_capability) {
|
|
2319
2324
|
// `store` and the relational `sqlite` tier share this exact object.
|
|
@@ -2331,6 +2336,25 @@ fn appModule(b: *std.Build, dep: *std.Build.Dependency, target: std.Build.Resolv
|
|
|
2331
2336
|
return app_mod;
|
|
2332
2337
|
}
|
|
2333
2338
|
|
|
2339
|
+
/// The Windows libraries scriptc's runtime pack declares (runtime-pack.json
|
|
2340
|
+
/// system_libraries; bcrypt and crypt32 back its TLS). An unused import
|
|
2341
|
+
/// library adds no DLL dependency, so they are linked unconditionally.
|
|
2342
|
+
const scriptc_windows_system_libraries = [_][]const u8{ "advapi32", "iphlpapi", "ws2_32", "bcrypt", "crypt32" };
|
|
2343
|
+
|
|
2344
|
+
/// The pinned scriptc builds its Windows runtime against Zig's MinGW sysroot,
|
|
2345
|
+
/// and an MSVC image links the static UCRT without MinGW's default libraries.
|
|
2346
|
+
/// Restate what MinGW would have supplied on the module that carries the core
|
|
2347
|
+
/// and service archives, so the app exe and every test-side artifact that
|
|
2348
|
+
/// imports it (unit tests, model contract, test host) link alike: the pack's
|
|
2349
|
+
/// system libraries, MSVC's oldnames.lib for the POSIX names (open, read,
|
|
2350
|
+
/// close, ...), and scriptc_mingw_compat.c for the rest of MinGW's startup
|
|
2351
|
+
/// and CRT layer (running the runtime's .ctors constructors above all).
|
|
2352
|
+
fn linkScriptcMsvcRuntime(dep: *std.Build.Dependency, mod: *std.Build.Module) void {
|
|
2353
|
+
for (scriptc_windows_system_libraries) |name| mod.linkSystemLibrary(name, .{});
|
|
2354
|
+
mod.linkSystemLibrary("oldnames", .{});
|
|
2355
|
+
mod.addCSourceFile(.{ .file = dep.path("src/platform/windows/scriptc_mingw_compat.c"), .flags = &.{} });
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2334
2358
|
/// Compile primary markup bytes independently from the Zig app graph. The
|
|
2335
2359
|
/// final artifact sees this as an object-file input, so a markup edit dirties
|
|
2336
2360
|
/// only this tiny C compile and the final link.
|
package/build/cli.zig
CHANGED
|
@@ -43,6 +43,12 @@ pub fn createModules(b: *std.Build, target: std.Build.ResolvedTarget, optimize:
|
|
|
43
43
|
tooling.addImport("android_host", module(b, target, optimize, "src/platform/android/files.zig"));
|
|
44
44
|
tooling.addImport("sqlite_engine", module(b, target, optimize, "src/runtime/sqlite_engine.zig"));
|
|
45
45
|
tooling.addImport("update_feed", module(b, target, optimize, "src/updater/feed.zig"));
|
|
46
|
+
tooling.addImport("windows_install_config", module(b, target, optimize, "src/windows_install/config.zig"));
|
|
47
|
+
tooling.addImport("windows_install_payload", module(b, target, optimize, "src/windows_install/payload.zig"));
|
|
48
|
+
// `native package --installer` writes the Windows setup program and
|
|
49
|
+
// launcher from these prebuilt copies, so packaging needs no compiler.
|
|
50
|
+
tooling.addAnonymousImport("windows_launcher_x86_64", .{ .root_source_file = windowsLauncher(b, .x86_64).getEmittedBin() });
|
|
51
|
+
tooling.addAnonymousImport("windows_launcher_aarch64", .{ .root_source_file = windowsLauncher(b, .aarch64).getEmittedBin() });
|
|
46
52
|
tooling.addIncludePath(b.path("third_party/sqlite"));
|
|
47
53
|
tooling.addCSourceFile(.{
|
|
48
54
|
.file = b.path("third_party/sqlite/sqlite3.c"),
|
|
@@ -101,6 +107,22 @@ pub fn buildCommit(b: *std.Build) []const u8 {
|
|
|
101
107
|
return trimmed;
|
|
102
108
|
}
|
|
103
109
|
|
|
110
|
+
/// The Windows setup program, launcher and uninstaller of installed apps
|
|
111
|
+
/// (src/windows_install), built for one Windows architecture.
|
|
112
|
+
fn windowsLauncher(b: *std.Build, arch: std.Target.Cpu.Arch) *std.Build.Step.Compile {
|
|
113
|
+
const launcher = b.addExecutable(.{
|
|
114
|
+
.name = "native-launcher",
|
|
115
|
+
.root_module = b.createModule(.{
|
|
116
|
+
.root_source_file = b.path("src/windows_install/main.zig"),
|
|
117
|
+
.target = b.resolveTargetQuery(.{ .cpu_arch = arch, .os_tag = .windows }),
|
|
118
|
+
.optimize = .ReleaseSmall,
|
|
119
|
+
.strip = true,
|
|
120
|
+
}),
|
|
121
|
+
});
|
|
122
|
+
launcher.subsystem = .windows;
|
|
123
|
+
return launcher;
|
|
124
|
+
}
|
|
125
|
+
|
|
104
126
|
fn module(b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.OptimizeMode, path: []const u8) *std.Build.Module {
|
|
105
127
|
return b.createModule(.{
|
|
106
128
|
.root_source_file = b.path(path),
|
package/native-sdk.d.ts
CHANGED
|
@@ -32,6 +32,8 @@ export interface NativeSdkWindowInfo {
|
|
|
32
32
|
width: number;
|
|
33
33
|
height: number;
|
|
34
34
|
scale: number;
|
|
35
|
+
/** Maximized, whoever changed it; `window.zero.on("window:state", ...)` reports each change. */
|
|
36
|
+
maximized: boolean;
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
export type NativeSdkWindowTitlebarStyle =
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zogien/native-sdk",
|
|
3
|
-
"version": "0.11.0-fork.
|
|
3
|
+
"version": "0.11.0-fork.4",
|
|
4
4
|
"description": "Community-maintained fork of Native SDK: TypeScript, Native markup, and native desktop tooling",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -41,14 +41,14 @@
|
|
|
41
41
|
"tar": "7.5.22"
|
|
42
42
|
},
|
|
43
43
|
"optionalDependencies": {
|
|
44
|
-
"@zogien/native-sdk-darwin-arm64": "0.11.0-fork.
|
|
45
|
-
"@zogien/native-sdk-darwin-x64": "0.11.0-fork.
|
|
46
|
-
"@zogien/native-sdk-linux-arm64-gnu": "0.11.0-fork.
|
|
47
|
-
"@zogien/native-sdk-linux-arm64-musl": "0.11.0-fork.
|
|
48
|
-
"@zogien/native-sdk-linux-x64-gnu": "0.11.0-fork.
|
|
49
|
-
"@zogien/native-sdk-linux-x64-musl": "0.11.0-fork.
|
|
50
|
-
"@zogien/native-sdk-win32-arm64": "0.11.0-fork.
|
|
51
|
-
"@zogien/native-sdk-win32-x64": "0.11.0-fork.
|
|
44
|
+
"@zogien/native-sdk-darwin-arm64": "0.11.0-fork.4",
|
|
45
|
+
"@zogien/native-sdk-darwin-x64": "0.11.0-fork.4",
|
|
46
|
+
"@zogien/native-sdk-linux-arm64-gnu": "0.11.0-fork.4",
|
|
47
|
+
"@zogien/native-sdk-linux-arm64-musl": "0.11.0-fork.4",
|
|
48
|
+
"@zogien/native-sdk-linux-x64-gnu": "0.11.0-fork.4",
|
|
49
|
+
"@zogien/native-sdk-linux-x64-musl": "0.11.0-fork.4",
|
|
50
|
+
"@zogien/native-sdk-win32-arm64": "0.11.0-fork.4",
|
|
51
|
+
"@zogien/native-sdk-win32-x64": "0.11.0-fork.4"
|
|
52
52
|
},
|
|
53
53
|
"repository": {
|
|
54
54
|
"type": "git",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
},
|
|
73
73
|
"publishConfig": {
|
|
74
74
|
"access": "public",
|
|
75
|
-
"tag": "
|
|
75
|
+
"tag": "latest",
|
|
76
76
|
"registry": "https://registry.npmjs.org/"
|
|
77
77
|
}
|
|
78
78
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@native-sdk/core",
|
|
3
|
-
"version": "0.11.0-fork.
|
|
3
|
+
"version": "0.11.0-fork.4",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@native-sdk/core",
|
|
9
|
-
"version": "0.11.0-fork.
|
|
9
|
+
"version": "0.11.0-fork.4",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@scriptc/types": "0.3.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@native-sdk/core",
|
|
3
|
-
"version": "0.11.0-fork.
|
|
3
|
+
"version": "0.11.0-fork.4",
|
|
4
4
|
"description": "The TypeScript authoring tier: the app-core subset, its checker and contract frontend, the exact-pinned core compiler dependency, and the SDK module cores import",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
package/schemas/app.schema.json
CHANGED
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"dock_visible": { "type": "boolean", "default": true },
|
|
37
|
+
"single_instance": { "type": "boolean", "default": false },
|
|
37
38
|
"persist": { "$ref": "#/$defs/persist" },
|
|
38
39
|
"images": { "$ref": "#/$defs/images" },
|
|
39
40
|
"service_packages": {
|
|
@@ -138,7 +139,14 @@
|
|
|
138
139
|
"properties": {
|
|
139
140
|
"feed_url": { "type": "string", "pattern": "^https://", "maxLength": 4096 },
|
|
140
141
|
"public_key": { "type": "string", "pattern": "^[A-Za-z0-9+/]{43}=$" },
|
|
141
|
-
"
|
|
142
|
+
"public_keys": {
|
|
143
|
+
"type": "array",
|
|
144
|
+
"maxItems": 3,
|
|
145
|
+
"uniqueItems": true,
|
|
146
|
+
"items": { "type": "string", "pattern": "^[A-Za-z0-9+/]{43}=$" }
|
|
147
|
+
},
|
|
148
|
+
"check_on_start": { "type": "boolean", "default": false },
|
|
149
|
+
"mode": { "type": "string", "enum": ["host", "app"], "default": "host" }
|
|
142
150
|
}
|
|
143
151
|
},
|
|
144
152
|
"bridge": {
|
|
@@ -172,6 +172,8 @@ Four effect families deliver MANY results from one command — a keyed stream th
|
|
|
172
172
|
- `Cmd.channelOpen(key, { event })` — open an EXTERNAL-SOURCE channel under the app's numeric key: the host stages a long-lived, thread-safe posting seam its NATIVE side feeds — embedders and platform-services extensions post bytes from their own threads (sockets, watchers, workers), and each accepted post dispatches the `event` arm as one `"data"` event. Posting is deliberately not a TS verb — compiled cores are single-threaded, so the core opens, closes, and receives while the posting handle lives on the native side (`Effects.channelHandle(key)`). `key` may be any number expression, a positive integer below 2^53 (a certain-to-be-refused literal stops the build, NS1030). The `event` arm is a five-field record matched by NAME: `key` (the channel key echoed verbatim, so concurrent channels sharing one arm stay distinguishable; a key the wire cannot carry exactly echoes 0), `state` (the `ChannelState` union — import it from `@native-sdk/core` or declare an alias with exactly the three members `"data" | "closed" | "rejected"` in any order; checked BOTH directions, since a narrower union would silently drop states the host emits), `bytes` (`Uint8Array` — the post's payload on `"data"` events, empty otherwise), and `droppedPending`/`droppedTotal` (numbers — the honest back-pressure counters: posts the native handle refused since the previous delivered event, and over the channel's whole life; refused posts count, never silence). One channel per key at a time — a duplicate live key dispatches `"rejected"` — and the key shares the engine's effect-key space (a same-key fetch is blocked while the channel lives). No timer polling anywhere: the source wakes the loop itself. Channel events journal at the effect boundary, so recorded sessions replay the whole stream from the journal — the native posting side is never needed at replay (a native producer that consults `ChannelHandle.live()` before launching keeps replay fully offline; one that launches unconditionally is stopped at its first post, which answers `.closed`).
|
|
173
173
|
- `Cmd.channelClose(key)` — close the open channel under the key, if any: staged posts flush, exactly one `"closed"` event (final drop totals aboard) dispatches the event arm, and the key frees. A key with no open channel no-ops.
|
|
174
174
|
- `Cmd.channelOpenSource(key, source, payload, { event, started })` opens a channel and starts an explicit native producer only for that newly accepted occupancy. The `started` arm has exactly `key: number`, `state: "started" | "skipped" | "failed"`, and `bytes: Uint8Array`; it runs in command order before the settled view. A refused open skips startup and still delivers the ordinary channel rejection. Startup failure carries the native error name; close that channel with `Cmd.channelClose`. Source names are 1–255 UTF-8 bytes and arguments at most 4096 bytes. The built-in `"native-sdk.process.samples"` source takes empty arguments and posts process readings every 500 ms. Native embedders can inject `Effects.channel_sources`; starters copy borrowed arguments before returning and must be quick and nonreentrant. Native recording journals startup results and replay never invokes the producer. The fake and virtual hosts report `UnsupportedChannelSource` unless a native test injects a starter; virtual-host recording of external producers requires the native runtime. See `examples/channel-monitor`.
|
|
175
|
+
- The built-in `"native-sdk.app.launches"` source delivers later launches of a `"single_instance": true` app (manifest; Windows today). The owning process's core opens it with empty arguments, usually from `initialModel`; each later launch arrives as one `"data"` event whose `bytes` are the launch's working directory, then each argument (argv[0] excluded), NUL-separated. Split with `.indexOf(0)`/`.slice`, then decide what to do, typically `Cmd.showWindow(label)` (the later process has already granted the owner foreground permission). Launches that arrive before the open are held for up to 5 seconds. A process that does not own the app gets `started` `"failed"` with `SingleInstanceNotOwned` (`UnsupportedChannelSource` on hosts without single-instance support). The later process never runs core code: it exits with code 0 once the owner accepts the launch, or a distinct non-zero code (see the manifest docs' Single instance section).
|
|
176
|
+
- The built-in `"native-sdk.updates"` source follows the app-driven updater (an `updates` manifest block; Windows installs from the SDK setup program, and macOS apps whose manifest sets `"mode": "app"`). Open it with empty arguments from `initialModel`; it posts the current status at open, then at every change and about every percent of a download. A status is `name value` lines — `state` (`unsupported`/`idle`/`checking`/`up_to_date`/`available`/`downloading`/`ready`/`error`), `installed`, then `version`, `size`, `received`, `percent`, `reason`, `error`, `last updated` when they apply; read lines by name with a byte loop (`status[i] === 10` ends a line, `subarray` slices the value). Drive it with `Cmd.request("native-sdk.updates.<verb>", new Uint8Array(0), { ok, err })` — `status`, `check`, `download`, `cancel`, `discard`, `restart` (quits the app; the staged release starts in its place), `notes` (plain-text release notes): each answers at once — `status` with the status, `notes` with the notes, the rest with an empty ok once accepted — or with an err reason (`unsupported`, `busy`, `not_available`, `not_ready`, `not_downloading`, `invalid_request`, `restart_failed`). Keep the model's status from the channel only: it delivers every change in order, while a request's answer may arrive after a newer post. The updater never shows UI or restarts on its own; the core decides. Elsewhere (macOS with the default host-owned flow, Linux) `state` is `unsupported` with `reason platform`. See the Updates docs.
|
|
175
177
|
- `Cmd.audioCaptureStart(key, { source, sampleRate?, channels? }, { event })` — start a native `"microphone"` or `"system"` audio stream. The host converts into interleaved signed-16 little-endian PCM at 16/24/48 kHz and mono/stereo (default 48 kHz mono), in chunks no larger than 20 ms. The ten-field event arm is matched by NAME: `key`, `state` (exactly `"started" | "data" | "failed" | "stopped" | "rejected"`), `source` (exactly `"microphone" | "system"`), `sampleRate`, `channels`, `timestampMs`, `frames`, `pcm`, `droppedPending`, and `droppedTotal`. PCM is dispatch-lifetime bytes; the commit walker copies it when stored in the model. The bounded queue, wake behavior, drop accounting, journaling, and offline replay are the channel transport's. Microphone and system may run concurrently; a second start for one source replaces that source's prior key.
|
|
176
178
|
- `Cmd.audioCaptureStop(key)` — synchronously quiesce native callbacks, flush already accepted chunks, deliver exactly one `"stopped"` terminal, and free the key. The key remains occupied until that terminal is delivered, so wait for `"stopped"` before reusing it; an earlier restart is rejected. A missing key no-ops. Declare `"microphone"` and/or `"system_audio"` in `app.zon` permissions so macOS dev executables and packaged apps carry the required usage descriptions.
|
|
177
179
|
- `Cmd.imageLoad(id, { path?, url?, cachePath?, expectedBytes? }, { event })` — load an image at runtime under the model-owned NUMERIC ImageId your markup binds (`<image image="{cover}"/>`, `<avatar image="{avatar}"/>`); `id` may be any number expression (ids are model data), a positive integer below 2^53 (a certain-to-be-refused literal like 0 stops the build, NS1030). The source cascade is `audioPlay`'s exactly: local `path` first, a missing file falls through to `url` (fetched whole, installed at the cache path and integrity-gated by `expectedBytes`); at least one of `path`/`url` (NS1029), and prefer OMITTING `cachePath` — the wiring's caches directory (`TsUiApp`'s `image_cache_dir`) derives the content-addressed path from the URL. Exactly ONE `event` arm dispatches per load — a five-field record matched by NAME: `id` (the requested ImageId echoed verbatim, so concurrent loads sharing one arm stay distinguishable; an id the wire cannot carry exactly echoes 0), `state` (the `ImageState` union — exactly the fifteen members `"loaded" | "rejected" | "not_found" | "io_failed" | "connect_failed" | "tls_failed" | "protocol_failed" | "timed_out" | "http_status" | "cancelled" | "too_large" | "unsupported" | "decode_failed" | "registry_full" | "alloc_failed"`, any order; `"alloc_failed"` is resource exhaustion at registration — the host refused the memory, the bytes may be fine, retry when memory frees), `width`/`height` (the actual registered dimensions on `"loaded"`, 0 otherwise), and `status` (the HTTP status for url loads that performed an exchange; 0 when none occurred — local paths, cache hits — so a cached `"loaded"` is distinguishable from a network one). On `"loaded"` the pixels are already registered under the id — store the id in the model then (the store-on-success discipline keeps a fallback rendering until the load lands). One load per id at a time: a duplicate live id dispatches `"rejected"` (the spawn discipline — a load in flight is never replaced implicitly), and image loads are not the string-keyed `Cmd.cancel`'s to end — `Cmd.imageCancel(id)` is their cancel, LOUD like spawn's: the one terminal still arrives as the event arm's `"cancelled"`, and the id frees for a fresh load once it lands (an id with no live load no-ops; the same NS1030 literal gate as `imageLoad`). The default registry has 16 slots and a 1 MiB decoded-pixel target per slot: platform codecs downscale photo-size sources at decode time, preserving aspect, instead of rejecting them. Image-centric apps may declare `.images = .{ .max_image_pixel_bytes = 8388608 }` in app.zon (1–8 MiB accepted); allocation stays lazy per used slot, but filling 16 ceiling-sized slots is a declared 128 MiB high-water. Encoded sources have their own flat 8 MiB bound; only over-bound source bytes report `"too_large"` in normal operation, never a cut decode. `Cmd.imageUnregister(id)` releases a loaded image's registry slot — the gallery eviction move when the 17th distinct image would answer `"registry_full"`: views bound to the id fall back, and the slot accepts the next load. Unregister is synchronous registry surgery, NOT an effect — no result Msg, an unregistered id no-ops (the same NS1030 literal gate) — and it frees only the CURRENT registration: a load in flight under the id still registers at its terminal, so cancel the load first (`Cmd.imageCancel`) to keep the slot free.
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
//! Relative paths keep their app-directory meaning during development. In a
|
|
4
4
|
//! packaged macOS app, the same tree lives below `Contents/Resources`; Finder
|
|
5
5
|
//! and `open` do not preserve a useful working directory, so resolve that tree
|
|
6
|
-
//! from the executable's bundle layout before reading it.
|
|
6
|
+
//! from the executable's bundle layout before reading it. A packaged Windows
|
|
7
|
+
//! or Linux artifact keeps the tree in `resources/` beside the executable's
|
|
8
|
+
//! `bin/`, and a shortcut or file manager launch starts in some other
|
|
9
|
+
//! directory, so resolve that tree the same way.
|
|
7
10
|
|
|
8
11
|
const std = @import("std");
|
|
9
12
|
const builtin = @import("builtin");
|
|
@@ -26,10 +29,56 @@ pub fn readFileAlloc(
|
|
|
26
29
|
limit,
|
|
27
30
|
);
|
|
28
31
|
}
|
|
32
|
+
if (comptime builtin.os.tag == .windows or builtin.os.tag == .linux) {
|
|
33
|
+
var executable_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
|
|
34
|
+
const executable_len = std.process.executablePath(io, &executable_buffer) catch
|
|
35
|
+
return std.Io.Dir.cwd().readFileAlloc(io, path, allocator, limit);
|
|
36
|
+
return readFileAllocFromDesktopExecutable(
|
|
37
|
+
io,
|
|
38
|
+
executable_buffer[0..executable_len],
|
|
39
|
+
path,
|
|
40
|
+
allocator,
|
|
41
|
+
limit,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
29
44
|
|
|
30
45
|
return std.Io.Dir.cwd().readFileAlloc(io, path, allocator, limit);
|
|
31
46
|
}
|
|
32
47
|
|
|
48
|
+
pub fn readFileAllocFromDesktopExecutable(
|
|
49
|
+
io: std.Io,
|
|
50
|
+
executable_path: []const u8,
|
|
51
|
+
path: []const u8,
|
|
52
|
+
allocator: std.mem.Allocator,
|
|
53
|
+
limit: std.Io.Limit,
|
|
54
|
+
) std.Io.Dir.ReadFileAllocError![]u8 {
|
|
55
|
+
var packaged_path_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
|
|
56
|
+
if (desktopPackagedResourcePath(executable_path, path, &packaged_path_buffer) catch null) |packaged_path| {
|
|
57
|
+
return std.Io.Dir.cwd().readFileAlloc(io, packaged_path, allocator, limit) catch |err| switch (err) {
|
|
58
|
+
// As on macOS: a package miss keeps the cwd-relative development
|
|
59
|
+
// behavior, while a real read failure is not hidden.
|
|
60
|
+
error.FileNotFound => std.Io.Dir.cwd().readFileAlloc(io, path, allocator, limit),
|
|
61
|
+
else => |read_err| read_err,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
return std.Io.Dir.cwd().readFileAlloc(io, path, allocator, limit);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// The packaged copy of a relative asset path when the executable runs
|
|
68
|
+
/// from a Windows or Linux artifact's `bin/`: `<artifact>/resources/<path>`.
|
|
69
|
+
pub fn desktopPackagedResourcePath(
|
|
70
|
+
executable_path: []const u8,
|
|
71
|
+
asset_path: []const u8,
|
|
72
|
+
output: []u8,
|
|
73
|
+
) error{NoSpaceLeft}!?[]const u8 {
|
|
74
|
+
if (asset_path.len == 0 or std.fs.path.isAbsolute(asset_path)) return null;
|
|
75
|
+
const executable_dir = std.fs.path.dirname(executable_path) orelse return null;
|
|
76
|
+
if (!std.ascii.eqlIgnoreCase(std.fs.path.basename(executable_dir), "bin")) return null;
|
|
77
|
+
const artifact_dir = std.fs.path.dirname(executable_dir) orelse return null;
|
|
78
|
+
return std.fmt.bufPrint(output, "{s}{c}resources{c}{s}", .{ artifact_dir, std.fs.path.sep, std.fs.path.sep, asset_path }) catch
|
|
79
|
+
error.NoSpaceLeft;
|
|
80
|
+
}
|
|
81
|
+
|
|
33
82
|
pub fn readFileAllocFromMacosExecutable(
|
|
34
83
|
io: std.Io,
|
|
35
84
|
executable_path: []const u8,
|
|
@@ -136,3 +185,38 @@ test "packaged macOS startup assets read from Contents Resources" {
|
|
|
136
185
|
defer std.testing.allocator.free(bytes);
|
|
137
186
|
try std.testing.expectEqualStrings("packaged-avatar", bytes);
|
|
138
187
|
}
|
|
188
|
+
|
|
189
|
+
test "desktop packaged resource path mirrors the app-relative asset tree beside bin" {
|
|
190
|
+
var buffer: [256]u8 = undefined;
|
|
191
|
+
const sep = std.fs.path.sep_str;
|
|
192
|
+
const executable = "pkg" ++ sep ++ "demo" ++ sep ++ "bin" ++ sep ++ "demo.exe";
|
|
193
|
+
try std.testing.expectEqualStrings(
|
|
194
|
+
"pkg" ++ sep ++ "demo" ++ sep ++ "resources" ++ sep ++ "assets/fonts/mono.ttf",
|
|
195
|
+
(try desktopPackagedResourcePath(executable, "assets/fonts/mono.ttf", &buffer)).?,
|
|
196
|
+
);
|
|
197
|
+
const uppercase = "pkg" ++ sep ++ "demo" ++ sep ++ "BIN" ++ sep ++ "demo.exe";
|
|
198
|
+
try std.testing.expect((try desktopPackagedResourcePath(uppercase, "assets/logo.png", &buffer)) != null);
|
|
199
|
+
// A development binary outside bin/ keeps reading relative to the
|
|
200
|
+
// working directory, and absolute paths are never redirected.
|
|
201
|
+
try std.testing.expectEqual(null, try desktopPackagedResourcePath("zig-cache" ++ sep ++ "o" ++ sep ++ "demo.exe", "assets/logo.png", &buffer));
|
|
202
|
+
try std.testing.expectEqual(null, try desktopPackagedResourcePath(executable, "", &buffer));
|
|
203
|
+
const absolute = if (builtin.os.tag == .windows) "C:\\logo.png" else "/tmp/logo.png";
|
|
204
|
+
try std.testing.expectEqual(null, try desktopPackagedResourcePath(executable, absolute, &buffer));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
test "packaged desktop startup assets read from resources beside bin" {
|
|
208
|
+
var tmp = std.testing.tmpDir(.{});
|
|
209
|
+
defer tmp.cleanup();
|
|
210
|
+
try tmp.dir.createDirPath(std.testing.io, "demo/bin");
|
|
211
|
+
try tmp.dir.createDirPath(std.testing.io, "demo/resources/assets/fonts");
|
|
212
|
+
try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "demo/resources/assets/fonts/mono.ttf", .data = "packaged-font" });
|
|
213
|
+
|
|
214
|
+
var executable_buffer: [std.Io.Dir.max_path_bytes]u8 = undefined;
|
|
215
|
+
const executable_path = try std.fmt.bufPrint(&executable_buffer, ".zig-cache/tmp/{s}/demo/bin/demo.exe", .{tmp.sub_path[0..]});
|
|
216
|
+
const bytes = try readFileAllocFromDesktopExecutable(std.testing.io, executable_path, "assets/fonts/mono.ttf", std.testing.allocator, .limited(1024));
|
|
217
|
+
defer std.testing.allocator.free(bytes);
|
|
218
|
+
try std.testing.expectEqualStrings("packaged-font", bytes);
|
|
219
|
+
|
|
220
|
+
// A package miss falls back to the working directory.
|
|
221
|
+
try std.testing.expectError(error.FileNotFound, readFileAllocFromDesktopExecutable(std.testing.io, executable_path, "assets/fonts/missing.ttf", std.testing.allocator, .limited(1024)));
|
|
222
|
+
}
|
package/src/app_runner/root.zig
CHANGED
|
@@ -98,8 +98,9 @@ pub const RunOptions = struct {
|
|
|
98
98
|
.bundle_id = self.bundle_id,
|
|
99
99
|
.icon_path = self.icon_path,
|
|
100
100
|
.update_feed_url = manifestUpdateString("feed_url"),
|
|
101
|
-
.update_public_key =
|
|
101
|
+
.update_public_key = manifestUpdatePublicKeys(),
|
|
102
102
|
.update_check_on_start = manifestUpdateCheckOnStart(),
|
|
103
|
+
.update_mode = manifestUpdateMode(),
|
|
103
104
|
.main_window = .{
|
|
104
105
|
.id = 1,
|
|
105
106
|
.label = "main",
|
|
@@ -172,12 +173,30 @@ fn manifestUpdateString(comptime field: []const u8) []const u8 {
|
|
|
172
173
|
};
|
|
173
174
|
}
|
|
174
175
|
|
|
176
|
+
/// The trusted update keys the updater verifies feeds against:
|
|
177
|
+
/// `public_key` followed by any `public_keys`, comma-joined.
|
|
178
|
+
fn manifestUpdatePublicKeys() []const u8 {
|
|
179
|
+
const primary = comptime manifestUpdateString("public_key");
|
|
180
|
+
if (comptime primary.len == 0) return "";
|
|
181
|
+
if (comptime !@hasField(@TypeOf(app_manifest.updates), "public_keys")) return primary;
|
|
182
|
+
comptime var keys: []const u8 = primary;
|
|
183
|
+
inline for (app_manifest.updates.public_keys) |key| keys = keys ++ "," ++ key;
|
|
184
|
+
return keys;
|
|
185
|
+
}
|
|
186
|
+
|
|
175
187
|
fn manifestUpdateCheckOnStart() bool {
|
|
176
188
|
if (comptime !@hasField(@TypeOf(app_manifest), "updates")) return false;
|
|
177
189
|
if (comptime !@hasField(@TypeOf(app_manifest.updates), "check_on_start")) return false;
|
|
178
190
|
return app_manifest.updates.check_on_start;
|
|
179
191
|
}
|
|
180
192
|
|
|
193
|
+
fn manifestUpdateMode() native_sdk.platform.UpdateMode {
|
|
194
|
+
if (comptime !@hasField(@TypeOf(app_manifest), "updates")) return .host;
|
|
195
|
+
if (comptime !@hasField(@TypeOf(app_manifest.updates), "mode")) return .host;
|
|
196
|
+
return comptime std.meta.stringToEnum(native_sdk.platform.UpdateMode, app_manifest.updates.mode) orelse
|
|
197
|
+
@compileError("app manifest updates.mode must be \"host\" or \"app\"");
|
|
198
|
+
}
|
|
199
|
+
|
|
181
200
|
const CommandStorage = struct {
|
|
182
201
|
commands: [native_sdk.app_manifest.max_commands]native_sdk.Command = undefined,
|
|
183
202
|
|
|
@@ -651,6 +670,30 @@ fn manifestDeclaresCredentials() bool {
|
|
|
651
670
|
return false;
|
|
652
671
|
}
|
|
653
672
|
|
|
673
|
+
fn manifestSingleInstance() bool {
|
|
674
|
+
return if (comptime @hasField(@TypeOf(app_manifest), "single_instance")) app_manifest.single_instance else false;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/// Manifest `single_instance`: claims ownership before anything else
|
|
678
|
+
/// starts. A later launch hands its working directory and arguments to
|
|
679
|
+
/// the owner and exits here, its exit code naming the delivery result.
|
|
680
|
+
/// Replay never claims. Only the first call claims, so an entry point may
|
|
681
|
+
/// call it before its own startup work and the call in `runWithOptions`
|
|
682
|
+
/// is then a no-op.
|
|
683
|
+
pub fn enforceSingleInstance(init: std.process.Init) void {
|
|
684
|
+
if (comptime !manifestSingleInstance()) return;
|
|
685
|
+
if (init.environ_map.get("NATIVE_SDK_SESSION_REPLAY") != null) return;
|
|
686
|
+
const outcome = native_sdk.single_instance.claim(init.io, init.gpa, init.minimal.args, app_manifest.id) orelse return;
|
|
687
|
+
switch (outcome) {
|
|
688
|
+
.primary => {},
|
|
689
|
+
.standalone => |reason| std.log.warn("single_instance: {s}; running without instance ownership", .{reason}),
|
|
690
|
+
.secondary => |result| {
|
|
691
|
+
if (result != .delivered) std.debug.print("{s}: {s}\n", .{ app_manifest.name, result.describe() });
|
|
692
|
+
std.process.exit(result.exitCode());
|
|
693
|
+
},
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
654
697
|
fn menuItem(comptime item: anytype) native_sdk.MenuItem {
|
|
655
698
|
return .{
|
|
656
699
|
.label = if (@hasField(@TypeOf(item), "label")) item.label else "",
|
|
@@ -694,6 +737,7 @@ pub fn runWithOptions(app: native_sdk.App, options: RunOptions, init: std.proces
|
|
|
694
737
|
if (init.environ_map.get("NATIVE_SDK_SESSION_REPLAY")) |journal_path| {
|
|
695
738
|
return runSessionReplay(app, options, init, journal_path);
|
|
696
739
|
}
|
|
740
|
+
enforceSingleInstance(init);
|
|
697
741
|
const RecordStoreType = if (comptime manifestDeclaresStore()) native_sdk.RecordStore else void;
|
|
698
742
|
var record_store_value: RecordStoreType = undefined;
|
|
699
743
|
var record_store_open = false;
|
|
@@ -859,6 +903,7 @@ fn runMacos(app: native_sdk.App, options: RunOptions, init: std.process.Init) !v
|
|
|
859
903
|
native_sdk.runtime.launch_timing.lap("runner_main");
|
|
860
904
|
var buffers: StateBuffers = undefined;
|
|
861
905
|
var app_info = options.appInfo(&buffers);
|
|
906
|
+
configureUpdates(&app_info, init.environ_map);
|
|
862
907
|
const store = prepareStateStore(init.io, init.environ_map, &app_info, &buffers);
|
|
863
908
|
const session_recorder = setupSessionRecorder(init, app_info, app.recording_policy, app.blob_policy);
|
|
864
909
|
// Heap wrapper, latch-gated free: worker threads hold this address
|
|
@@ -931,6 +976,7 @@ fn runMacos(app: native_sdk.App, options: RunOptions, init: std.process.Init) !v
|
|
|
931
976
|
fn runLinux(app: native_sdk.App, options: RunOptions, init: std.process.Init) !void {
|
|
932
977
|
var buffers: StateBuffers = undefined;
|
|
933
978
|
var app_info = options.appInfo(&buffers);
|
|
979
|
+
configureUpdates(&app_info, init.environ_map);
|
|
934
980
|
const store = prepareStateStore(init.io, init.environ_map, &app_info, &buffers);
|
|
935
981
|
const session_recorder = setupSessionRecorder(init, app_info, app.recording_policy, app.blob_policy);
|
|
936
982
|
// Heap wrapper, latch-gated free: worker threads hold this address
|
|
@@ -998,9 +1044,22 @@ fn runLinux(app: native_sdk.App, options: RunOptions, init: std.process.Init) !v
|
|
|
998
1044
|
finishSessionRecorder(session_recorder);
|
|
999
1045
|
}
|
|
1000
1046
|
|
|
1047
|
+
/// Points the app-driven updater at this installation (see
|
|
1048
|
+
/// `updater.configureApp`).
|
|
1049
|
+
fn configureUpdates(app_info: *native_sdk.platform.AppInfo, environ_map: *std.process.Environ.Map) void {
|
|
1050
|
+
const host: native_sdk.updater.Host = comptime if (std.mem.eql(u8, build_options.platform, "windows"))
|
|
1051
|
+
.windows
|
|
1052
|
+
else if (std.mem.eql(u8, build_options.platform, "macos") and std.mem.eql(u8, build_options.web_engine, "system"))
|
|
1053
|
+
.macos_appkit
|
|
1054
|
+
else
|
|
1055
|
+
.other;
|
|
1056
|
+
native_sdk.updater.configureApp(host, app_info, environ_map);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1001
1059
|
fn runWindows(app: native_sdk.App, options: RunOptions, init: std.process.Init) !void {
|
|
1002
1060
|
var buffers: StateBuffers = undefined;
|
|
1003
1061
|
var app_info = options.appInfo(&buffers);
|
|
1062
|
+
configureUpdates(&app_info, init.environ_map);
|
|
1004
1063
|
const store = prepareStateStore(init.io, init.environ_map, &app_info, &buffers);
|
|
1005
1064
|
const session_recorder = setupSessionRecorder(init, app_info, app.recording_policy, app.blob_policy);
|
|
1006
1065
|
// Heap wrapper, latch-gated free: worker threads hold this address
|
|
@@ -1293,9 +1352,10 @@ test "RunOptions resolves manifest commands menus and shortcuts" {
|
|
|
1293
1352
|
};
|
|
1294
1353
|
var info_buffers: StateBuffers = .{};
|
|
1295
1354
|
const app_info = options.appInfo(&info_buffers);
|
|
1296
|
-
try std.testing.expectEqualStrings("https://example.com/native-update.json", app_info.update_feed_url);
|
|
1297
|
-
try std.testing.expectEqualStrings("11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo=", app_info.update_public_key);
|
|
1355
|
+
try std.testing.expectEqualStrings("https://example.com/releases/{target}/native-update.json", app_info.update_feed_url);
|
|
1356
|
+
try std.testing.expectEqualStrings("11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo=,wFDFY3pE+oYp//PMzOIwDLNipj2Z2V/FQUUmb0MyRFo=", app_info.update_public_key);
|
|
1298
1357
|
try std.testing.expect(app_info.update_check_on_start);
|
|
1358
|
+
try std.testing.expectEqual(native_sdk.platform.UpdateMode.app, app_info.update_mode);
|
|
1299
1359
|
|
|
1300
1360
|
var command_storage: CommandStorage = .{};
|
|
1301
1361
|
const commands = options.resolvedCommands(&command_storage);
|
|
@@ -54,6 +54,11 @@
|
|
|
54
54
|
//! when a core requests it, so packaged apps never
|
|
55
55
|
//! depend on their process working directory for
|
|
56
56
|
//! durable files.
|
|
57
|
+
//! single instance app.zon's `.single_instance` claims ownership first
|
|
58
|
+
//! thing in `main`; a later launch exits after handing
|
|
59
|
+
//! its cwd and arguments to the owner, whose core
|
|
60
|
+
//! receives them on the `native-sdk.app.launches`
|
|
61
|
+
//! channel source.
|
|
57
62
|
//!
|
|
58
63
|
//! Editing this file is never core-level work: it carries no app logic and
|
|
59
64
|
//! regenerates from the SDK on every build.
|
|
@@ -169,6 +174,9 @@ pub fn testMain(init: std.process.Init) !void {
|
|
|
169
174
|
}
|
|
170
175
|
|
|
171
176
|
pub fn main(init: std.process.Init) !void {
|
|
177
|
+
// Before persistence, services, or any window: a later launch of a
|
|
178
|
+
// `single_instance` app exits here once the owner has its arguments.
|
|
179
|
+
runner.enforceSingleInstance(init);
|
|
172
180
|
var options = appOptions(init.io);
|
|
173
181
|
// The platform caches directory for this app: when the core's
|
|
174
182
|
// `Cmd.audioPlay` names a URL with no cachePath, the bridge derives
|
|
@@ -419,6 +419,15 @@ void native_sdk_appkit_set_dock_icon_rgba(native_sdk_appkit_host_t *host, const
|
|
|
419
419
|
void native_sdk_appkit_set_dock_icon_file(native_sdk_appkit_host_t *host, const char *path, size_t path_len);
|
|
420
420
|
void native_sdk_appkit_configure_updates(native_sdk_appkit_host_t *host, const char *feed_url, size_t feed_url_len, const char *public_key, size_t public_key_len, int check_on_start, const char *target, size_t target_len);
|
|
421
421
|
int native_sdk_appkit_check_for_updates(native_sdk_appkit_host_t *host, int user_initiated);
|
|
422
|
+
// App-driven updates (src/updater/macos.zig); none of these shows UI.
|
|
423
|
+
int native_sdk_macos_updates_eligibility(void);
|
|
424
|
+
size_t native_sdk_macos_updates_staged_version(const char *bundle_id, size_t bundle_id_len, char *output, size_t capacity);
|
|
425
|
+
size_t native_sdk_macos_updates_take_last(const char *bundle_id, size_t bundle_id_len, char *output, size_t capacity);
|
|
426
|
+
int native_sdk_macos_updates_fetch(const char *url, size_t url_len, int allow_loopback_http, uint8_t *output, size_t capacity, size_t *output_len);
|
|
427
|
+
int native_sdk_macos_updates_download(const char *url, size_t url_len, int allow_loopback_http, uint64_t limit, char *path_output, size_t path_capacity, size_t *path_len, void *context, void (*progress)(void *context, uint64_t received), int (*cancelled)(void *context));
|
|
428
|
+
int native_sdk_macos_updates_stage(const char *archive_path, size_t archive_path_len, const char *bundle_id, size_t bundle_id_len, const char *version, size_t version_len);
|
|
429
|
+
void native_sdk_macos_updates_discard(void);
|
|
430
|
+
int native_sdk_macos_updates_install(const char *bundle_id, size_t bundle_id_len, const char *from_version, size_t from_version_len, int relaunch);
|
|
422
431
|
void native_sdk_appkit_run(native_sdk_appkit_host_t *host, native_sdk_appkit_event_callback_t callback, void *context);
|
|
423
432
|
// The host-side shutdown request: a failed event emit asks the host to
|
|
424
433
|
// deliver SHUTDOWN and stop. While the run loop is live the delivery is
|