@zogien/native-sdk 0.11.0-fork.3 → 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.
Files changed (49) hide show
  1. package/README.md +1 -1
  2. package/build/app.zig +3 -0
  3. package/build/cli.zig +22 -0
  4. package/package.json +10 -10
  5. package/packages/core/package-lock.json +2 -2
  6. package/packages/core/package.json +1 -1
  7. package/schemas/app.schema.json +9 -1
  8. package/skill-data/ts-core/SKILL.md +2 -0
  9. package/src/app_runner/app_assets.zig +85 -1
  10. package/src/app_runner/root.zig +63 -3
  11. package/src/app_runner/ts_core_main.zig +8 -0
  12. package/src/platform/macos/appkit_host.h +9 -0
  13. package/src/platform/macos/appkit_host.m +366 -0
  14. package/src/platform/macos/root.zig +29 -6
  15. package/src/platform/root.zig +1 -0
  16. package/src/platform/types.zig +9 -0
  17. package/src/platform/windows/root.zig +37 -0
  18. package/src/platform/windows/webview2_host.cpp +371 -26
  19. package/src/primitives/app_manifest/root.zig +1 -0
  20. package/src/primitives/app_manifest/tests.zig +35 -0
  21. package/src/primitives/app_manifest/types.zig +12 -0
  22. package/src/primitives/app_manifest/validation.zig +44 -6
  23. package/src/root.zig +1 -0
  24. package/src/runtime/channel_sources.zig +49 -1
  25. package/src/runtime/effects.zig +12 -1
  26. package/src/runtime/flow.zig +6 -0
  27. package/src/runtime/flow_reference.zig +6 -0
  28. package/src/runtime/root.zig +4 -0
  29. package/src/runtime/single_instance.zig +893 -0
  30. package/src/tooling/manifest.zig +111 -3
  31. package/src/tooling/package.zig +98 -8
  32. package/src/tooling/raw_manifest.zig +7 -0
  33. package/src/tooling/root.zig +2 -0
  34. package/src/tooling/templates.zig +44 -2
  35. package/src/tooling/update.zig +228 -10
  36. package/src/tooling/windows_installer.zig +245 -0
  37. package/src/tooling/zip_writer.zig +211 -0
  38. package/src/updater/engine.zig +936 -0
  39. package/src/updater/feed.zig +261 -12
  40. package/src/updater/macos.zig +187 -0
  41. package/src/updater/root.zig +41 -0
  42. package/src/updater/windows.zig +470 -0
  43. package/src/windows_install/config.zig +247 -0
  44. package/src/windows_install/main.zig +690 -0
  45. package/src/windows_install/payload.zig +146 -0
  46. package/src/windows_install/switch.zig +184 -0
  47. package/src/windows_install/tests.zig +11 -0
  48. package/src/windows_install/win32.zig +248 -0
  49. package/tools/native-sdk/main.zig +10 -9
@@ -0,0 +1,211 @@
1
+ //! A small ZIP writer for install images and update archives: deflate
2
+ //! (or store, when deflate does not help), UTF-8 names with forward
3
+ //! slashes, and a fixed timestamp so the same tree always produces the
4
+ //! same bytes. No ZIP64: an archive stays below 4 GiB and 65535 entries.
5
+ //! `std.zip` reads what this writes.
6
+
7
+ const std = @import("std");
8
+ const flate = std.compress.flate;
9
+
10
+ pub const Entry = struct {
11
+ /// Forward-slash path inside the archive, without a leading slash.
12
+ name: []const u8,
13
+ data: []const u8,
14
+ };
15
+
16
+ pub const Error = error{ ZipTooLarge, InvalidZipEntryName } || std.mem.Allocator.Error || std.Io.Writer.Error;
17
+
18
+ const dos_time: u16 = 0;
19
+ /// 1980-01-01, the earliest date a ZIP timestamp can hold.
20
+ const dos_date: u16 = (0 << 9) | (1 << 5) | 1;
21
+ const version: u16 = 20;
22
+ /// General purpose flag bit 11: names are UTF-8.
23
+ const utf8_flag: u16 = 0x0800;
24
+
25
+ const Written = struct {
26
+ name: []const u8,
27
+ method: std.zip.CompressionMethod,
28
+ crc32: u32,
29
+ compressed_size: u32,
30
+ uncompressed_size: u32,
31
+ offset: u32,
32
+ };
33
+
34
+ pub fn write(allocator: std.mem.Allocator, out: *std.Io.Writer, entries: []const Entry) Error!void {
35
+ if (entries.len > std.math.maxInt(u16)) return error.ZipTooLarge;
36
+ const written = try allocator.alloc(Written, entries.len);
37
+ defer allocator.free(written);
38
+ const window = try allocator.alloc(u8, flate.max_window_len);
39
+ defer allocator.free(window);
40
+
41
+ var offset: u64 = 0;
42
+ for (entries, 0..) |entry, index| {
43
+ try validateName(entry.name);
44
+ if (entry.data.len > std.math.maxInt(u32)) return error.ZipTooLarge;
45
+ var compressed: std.Io.Writer.Allocating = try .initCapacity(allocator, @max(64, entry.data.len / 2));
46
+ defer compressed.deinit();
47
+ var compressor = try flate.Compress.init(&compressed.writer, window, .raw, .default);
48
+ try compressor.writer.writeAll(entry.data);
49
+ try compressor.finish();
50
+ const deflated = compressed.written().len < entry.data.len;
51
+ const body = if (deflated) compressed.written() else entry.data;
52
+ if (offset > std.math.maxInt(u32)) return error.ZipTooLarge;
53
+ written[index] = .{
54
+ .name = entry.name,
55
+ .method = if (deflated) .deflate else .store,
56
+ .crc32 = std.hash.Crc32.hash(entry.data),
57
+ .compressed_size = @intCast(body.len),
58
+ .uncompressed_size = @intCast(entry.data.len),
59
+ .offset = @intCast(offset),
60
+ };
61
+ const record = written[index];
62
+ try out.writeAll(&std.zip.local_file_header_sig);
63
+ try out.writeInt(u16, version, .little);
64
+ try out.writeInt(u16, utf8_flag, .little);
65
+ try out.writeInt(u16, @intFromEnum(record.method), .little);
66
+ try out.writeInt(u16, dos_time, .little);
67
+ try out.writeInt(u16, dos_date, .little);
68
+ try out.writeInt(u32, record.crc32, .little);
69
+ try out.writeInt(u32, record.compressed_size, .little);
70
+ try out.writeInt(u32, record.uncompressed_size, .little);
71
+ try out.writeInt(u16, @intCast(entry.name.len), .little);
72
+ try out.writeInt(u16, 0, .little);
73
+ try out.writeAll(entry.name);
74
+ try out.writeAll(body);
75
+ offset += 30 + entry.name.len + body.len;
76
+ }
77
+
78
+ if (offset > std.math.maxInt(u32)) return error.ZipTooLarge;
79
+ const central_offset: u32 = @intCast(offset);
80
+ var central_size: u64 = 0;
81
+ for (written) |record| {
82
+ try out.writeAll(&std.zip.central_file_header_sig);
83
+ try out.writeInt(u16, version, .little);
84
+ try out.writeInt(u16, version, .little);
85
+ try out.writeInt(u16, utf8_flag, .little);
86
+ try out.writeInt(u16, @intFromEnum(record.method), .little);
87
+ try out.writeInt(u16, dos_time, .little);
88
+ try out.writeInt(u16, dos_date, .little);
89
+ try out.writeInt(u32, record.crc32, .little);
90
+ try out.writeInt(u32, record.compressed_size, .little);
91
+ try out.writeInt(u32, record.uncompressed_size, .little);
92
+ try out.writeInt(u16, @intCast(record.name.len), .little);
93
+ try out.writeInt(u16, 0, .little);
94
+ try out.writeInt(u16, 0, .little);
95
+ try out.writeInt(u16, 0, .little);
96
+ try out.writeInt(u16, 0, .little);
97
+ try out.writeInt(u32, 0, .little);
98
+ try out.writeInt(u32, record.offset, .little);
99
+ try out.writeAll(record.name);
100
+ central_size += 46 + record.name.len;
101
+ }
102
+ if (central_size > std.math.maxInt(u32)) return error.ZipTooLarge;
103
+ try out.writeAll(&std.zip.end_record_sig);
104
+ try out.writeInt(u16, 0, .little);
105
+ try out.writeInt(u16, 0, .little);
106
+ try out.writeInt(u16, @intCast(entries.len), .little);
107
+ try out.writeInt(u16, @intCast(entries.len), .little);
108
+ try out.writeInt(u32, @intCast(central_size), .little);
109
+ try out.writeInt(u32, central_offset, .little);
110
+ try out.writeInt(u16, 0, .little);
111
+ }
112
+
113
+ /// Collects every file under `root` (a directory path relative to the
114
+ /// working directory) as archive entries named by their forward-slash
115
+ /// path below it, sorted so the archive is reproducible. The caller frees
116
+ /// the names and data with `freeEntries`.
117
+ pub fn collectDirectory(allocator: std.mem.Allocator, io: std.Io, root: []const u8) ![]Entry {
118
+ var dir = try std.Io.Dir.cwd().openDir(io, root, .{ .iterate = true });
119
+ defer dir.close(io);
120
+ var walker = try dir.walk(allocator);
121
+ defer walker.deinit();
122
+ var entries: std.ArrayList(Entry) = .empty;
123
+ errdefer freeEntries(allocator, entries.items);
124
+ errdefer entries.deinit(allocator);
125
+ while (try walker.next(io)) |item| {
126
+ if (item.kind != .file) continue;
127
+ const name = try allocator.dupe(u8, item.path);
128
+ errdefer allocator.free(name);
129
+ std.mem.replaceScalar(u8, name, '\\', '/');
130
+ const data = try dir.readFileAlloc(io, item.path, allocator, .unlimited);
131
+ errdefer allocator.free(data);
132
+ try entries.append(allocator, .{ .name = name, .data = data });
133
+ }
134
+ std.mem.sort(Entry, entries.items, {}, lessThan);
135
+ return entries.toOwnedSlice(allocator);
136
+ }
137
+
138
+ pub fn freeEntries(allocator: std.mem.Allocator, entries: []const Entry) void {
139
+ for (entries) |entry| {
140
+ allocator.free(entry.name);
141
+ allocator.free(entry.data);
142
+ }
143
+ }
144
+
145
+ fn lessThan(_: void, a: Entry, b: Entry) bool {
146
+ return std.mem.lessThan(u8, a.name, b.name);
147
+ }
148
+
149
+ fn validateName(name: []const u8) error{InvalidZipEntryName}!void {
150
+ if (name.len == 0 or name.len > std.math.maxInt(u16)) return error.InvalidZipEntryName;
151
+ if (name[0] == '/' or std.mem.indexOfScalar(u8, name, '\\') != null) return error.InvalidZipEntryName;
152
+ var parts = std.mem.splitScalar(u8, name, '/');
153
+ while (parts.next()) |part| {
154
+ if (part.len == 0 or std.mem.eql(u8, part, ".") or std.mem.eql(u8, part, "..")) return error.InvalidZipEntryName;
155
+ }
156
+ }
157
+
158
+ test "an archive std.zip extracts back to the same tree" {
159
+ const allocator = std.testing.allocator;
160
+ const io = std.testing.io;
161
+ var tmp = std.testing.tmpDir(.{ .iterate = true });
162
+ defer tmp.cleanup();
163
+
164
+ const repetitive = "native " ** 4096;
165
+ const entries = [_]Entry{
166
+ .{ .name = "bin/demo.exe", .data = "MZ-not-really" },
167
+ .{ .name = "resources/assets/text.txt", .data = repetitive },
168
+ .{ .name = "native-install.json", .data = "{}" },
169
+ };
170
+ var archive: std.Io.Writer.Allocating = .init(allocator);
171
+ defer archive.deinit();
172
+ try write(allocator, &archive.writer, &entries);
173
+ try tmp.dir.writeFile(io, .{ .sub_path = "image.zip", .data = archive.written() });
174
+ try std.testing.expect(archive.written().len < repetitive.len);
175
+
176
+ try tmp.dir.createDirPath(io, "out");
177
+ var out = try tmp.dir.openDir(io, "out", .{});
178
+ defer out.close(io);
179
+ var file = try tmp.dir.openFile(io, "image.zip", .{});
180
+ defer file.close(io);
181
+ var buffer: [4096]u8 = undefined;
182
+ var reader = file.reader(io, &buffer);
183
+ try std.zip.extract(out, &reader, .{});
184
+
185
+ for (entries) |entry| {
186
+ const bytes = try out.readFileAlloc(io, entry.name, allocator, .unlimited);
187
+ defer allocator.free(bytes);
188
+ try std.testing.expectEqualStrings(entry.data, bytes);
189
+ }
190
+ }
191
+
192
+ test "the same entries always produce the same bytes" {
193
+ const allocator = std.testing.allocator;
194
+ const entries = [_]Entry{.{ .name = "a.txt", .data = "abc" }};
195
+ var first: std.Io.Writer.Allocating = .init(allocator);
196
+ defer first.deinit();
197
+ var second: std.Io.Writer.Allocating = .init(allocator);
198
+ defer second.deinit();
199
+ try write(allocator, &first.writer, &entries);
200
+ try write(allocator, &second.writer, &entries);
201
+ try std.testing.expectEqualSlices(u8, first.written(), second.written());
202
+ }
203
+
204
+ test "entry names stay inside the archive" {
205
+ const allocator = std.testing.allocator;
206
+ var archive: std.Io.Writer.Allocating = .init(allocator);
207
+ defer archive.deinit();
208
+ for ([_][]const u8{ "", "/abs.txt", "../up.txt", "a/../b.txt", "a\\b.txt", "a//b.txt" }) |name| {
209
+ try std.testing.expectError(error.InvalidZipEntryName, write(allocator, &archive.writer, &.{.{ .name = name, .data = "x" }}));
210
+ }
211
+ }