nanoshell 1.7.4 → 1.7.6

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/cli/bundler.zig DELETED
@@ -1,128 +0,0 @@
1
- const std = @import("std");
2
-
3
- pub const BundlerConfig = struct {
4
- app_name: []const u8 = "ZeroApp",
5
- entry_point: []const u8 = "assets/index.html",
6
- output_dir: []const u8 = "dist",
7
- };
8
-
9
- pub const SingleBinaryBundler = struct {
10
- allocator: std.mem.Allocator,
11
- config: BundlerConfig,
12
-
13
- pub fn init(allocator: std.mem.Allocator, config: BundlerConfig) SingleBinaryBundler {
14
- return .{
15
- .allocator = allocator,
16
- .config = config,
17
- };
18
- }
19
-
20
- pub fn bundle(self: *SingleBinaryBundler) !void {
21
- std.log.info("Starting ZeroUI Native App Bundler for '{s}'...", .{self.config.app_name});
22
- std.log.info("Packaging Assets -> {s}", .{self.config.entry_point});
23
-
24
- const io = std.Io.Threaded.global_single_threaded.io();
25
- _ = std.Io.Dir.cwd().createDir(io, self.config.output_dir, .default_dir) catch {};
26
-
27
- var dist_dir = std.Io.Dir.cwd().openDir(io, self.config.output_dir, .{}) catch |err| {
28
- std.log.err("Failed to open output dir: {s}", .{@errorName(err)});
29
- return err;
30
- };
31
-
32
- _ = dist_dir.createDir(io, "app", .default_dir) catch {};
33
- _ = dist_dir.createDir(io, "resources", .default_dir) catch {};
34
-
35
- const cwd = std.Io.Dir.cwd();
36
-
37
- // Copy Main Executable (search zig-out/bin, bin, root, example_app.exe)
38
- const possible_srcs = [_][]const u8{
39
- "zig-out/bin/example_app.exe",
40
- "example_app.exe",
41
- "bin/example_app.exe",
42
- try std.fmt.allocPrint(self.allocator, "bin/{s}.exe", .{self.config.app_name}),
43
- try std.fmt.allocPrint(self.allocator, "{s}.exe", .{self.config.app_name}),
44
- };
45
- const dst_exe_name = try std.fmt.allocPrint(self.allocator, "{s}.exe", .{self.config.app_name});
46
- defer self.allocator.free(dst_exe_name);
47
- const dst_exe_path = try std.fmt.allocPrint(self.allocator, "{s}/{s}.exe", .{ self.config.output_dir, self.config.app_name });
48
- defer self.allocator.free(dst_exe_path);
49
-
50
- var copied = false;
51
- for (possible_srcs) |src| {
52
- if (cwd.copyFile(src, dist_dir, dst_exe_name, io, .{})) |_| {
53
- copied = true;
54
- break;
55
- } else |_| {}
56
- }
57
- if (!copied) {
58
- std.log.warn("Executable copy warning: Could not locate baseline executable binary", .{});
59
- }
60
-
61
- // Copy DLLs to dist/ (search vendor/lib, zig-out/bin, and root)
62
- const dlls = [_][]const u8{ "AppCore.dll", "Ultralight.dll", "UltralightCore.dll", "WebCore.dll", "vcruntime140.dll", "msvcp140.dll", "vcruntime140_1.dll" };
63
- for (dlls) |dll| {
64
- const possible_dll_srcs = [_][]const u8{
65
- try std.fmt.allocPrint(self.allocator, "vendor/lib/{s}", .{dll}),
66
- try std.fmt.allocPrint(self.allocator, "zig-out/bin/{s}", .{dll}),
67
- try std.fmt.allocPrint(self.allocator, "bin/{s}", .{dll}),
68
- dll,
69
- };
70
- defer for (possible_dll_srcs) |p| self.allocator.free(p);
71
-
72
- const dst_dll = try std.fmt.allocPrint(self.allocator, "{s}/{s}", .{ self.config.output_dir, dll });
73
- defer self.allocator.free(dst_dll);
74
-
75
- for (possible_dll_srcs) |src_dll| {
76
- if (copyFileSimple(src_dll, dst_dll)) {
77
- break;
78
- }
79
- }
80
- }
81
-
82
- // Copy Resource Files to dist/ and dist/resources/
83
- const res_files = [_][]const u8{ "icudt67l.dat", "cacert.pem" };
84
- for (res_files) |file| {
85
- const possible_res_srcs = [_][]const u8{
86
- try std.fmt.allocPrint(self.allocator, "vendor/resources/{s}", .{file}),
87
- try std.fmt.allocPrint(self.allocator, "resources/{s}", .{file}),
88
- try std.fmt.allocPrint(self.allocator, "zig-out/bin/resources/{s}", .{file}),
89
- try std.fmt.allocPrint(self.allocator, "bin/{s}", .{file}),
90
- };
91
- defer for (possible_res_srcs) |p| self.allocator.free(p);
92
-
93
- const dst_res1 = try std.fmt.allocPrint(self.allocator, "{s}/{s}", .{ self.config.output_dir, file });
94
- defer self.allocator.free(dst_res1);
95
- const dst_res2 = try std.fmt.allocPrint(self.allocator, "{s}/resources/{s}", .{ self.config.output_dir, file });
96
- defer self.allocator.free(dst_res2);
97
-
98
- for (possible_res_srcs) |src_res| {
99
- if (copyFileSimple(src_res, dst_res1)) {
100
- _ = copyFileSimple(src_res, dst_res2);
101
- break;
102
- }
103
- }
104
- }
105
-
106
- // Copy app/* files to dist/app/*
107
- var app_dir_opt = cwd.openDir(io, "app", .{ .iterate = true }) catch null;
108
- if (app_dir_opt) |*app_dir| {
109
- var dist_app_dir = dist_dir.openDir(io, "app", .{}) catch null;
110
- if (dist_app_dir) |*dst_app| {
111
- var iter = app_dir.iterate();
112
- while (iter.next(io) catch null) |entry| {
113
- if (entry.kind == .file) {
114
- _ = app_dir.copyFile(entry.name, dst_app.*, entry.name, io, .{}) catch {};
115
- }
116
- }
117
- }
118
- }
119
-
120
- std.log.info("Built Custom Named Executable -> '{s}' (Self-contained release package)", .{dst_exe_path});
121
- }
122
- };
123
-
124
- fn copyFileSimple(src: []const u8, dst: []const u8) bool {
125
- const io = std.Io.Threaded.global_single_threaded.io();
126
- std.Io.Dir.cwd().copyFile(src, std.Io.Dir.cwd(), dst, io, .{}) catch return false;
127
- return true;
128
- }
@@ -1,192 +0,0 @@
1
- const std = @import("std");
2
- const installer = @import("installer.zig");
3
-
4
- pub const DevServer = struct {
5
- allocator: std.mem.Allocator,
6
-
7
- pub fn init(allocator: std.mem.Allocator) DevServer {
8
- return .{ .allocator = allocator };
9
- }
10
-
11
- pub fn runDevMode(self: *DevServer, app_dir_name: []const u8) !void {
12
- _ = app_dir_name;
13
- std.log.info("=================================================================", .{});
14
- std.log.info(" ⚡ NanoShell Dev Server Started", .{});
15
- std.log.info(" Watching app/ directory & nanoshell.json for live changes...", .{});
16
- std.log.info("=================================================================", .{});
17
-
18
- var config = installer.loadConfig(self.allocator) catch installer.AppConfig{};
19
- defer config.deinit(self.allocator);
20
-
21
- // Find executable inside bin/ or zig-out/bin/
22
- const io = std.Io.Threaded.global_single_threaded.io();
23
- const cwd = std.Io.Dir.cwd();
24
-
25
- var found_exe: ?[]const u8 = null;
26
-
27
- const candidate_paths = [_][]const u8{
28
- "zig-out/bin/example_app.exe",
29
- "bin/example_app.exe",
30
- "zig-out/bin/nanoshell.exe",
31
- };
32
-
33
- for (candidate_paths) |p| {
34
- if (cwd.openFile(io, p, .{})) |f| {
35
- f.close(io);
36
- found_exe = p;
37
- break;
38
- } else |_| {}
39
- }
40
-
41
- if (found_exe == null) {
42
- // Search bin/ for any .exe
43
- var bin_dir = cwd.openDir(io, "bin", .{ .iterate = true }) catch null;
44
- if (bin_dir) |*d| {
45
- defer d.close(io);
46
- var iter = d.iterate();
47
- while (iter.next(io) catch null) |entry| {
48
- if (entry.kind == .file and std.mem.endsWith(u8, entry.name, ".exe")) {
49
- found_exe = try std.fmt.allocPrint(self.allocator, "bin/{s}", .{entry.name});
50
- break;
51
- }
52
- }
53
- }
54
- }
55
-
56
- var win_exe_buf: [256]u8 = undefined;
57
- const exe_to_run = found_exe orelse "bin\\app.exe";
58
- var pos: usize = 0;
59
- for (exe_to_run) |ch| {
60
- if (ch == '/') {
61
- win_exe_buf[pos] = '\\';
62
- } else {
63
- win_exe_buf[pos] = ch;
64
- }
65
- pos += 1;
66
- }
67
- win_exe_buf[pos] = 0;
68
- const win_path = win_exe_buf[0..pos];
69
-
70
- var dev_cmd_buf: [300]u8 = undefined;
71
- const dev_cmd = std.fmt.bufPrintZ(&dev_cmd_buf, "{s} --dev", .{win_path}) catch win_path;
72
-
73
- std.log.info("Launching NanoShell runtime in DEV mode: {s}", .{dev_cmd});
74
-
75
- // Launch app process once - native engine handles smooth in-window hot reload in dev mode
76
- var child = try self.spawnChildProcess(dev_cmd);
77
- defer _ = child.kill();
78
-
79
- std.log.info("App running! In-window hot reload active for app/index.html, styles.css, app.js.", .{});
80
-
81
- const WinApi = struct {
82
- pub extern "kernel32" fn Sleep(dwMilliseconds: u32) callconv(.winapi) void;
83
- pub extern "kernel32" fn GetExitCodeProcess(hProcess: std.os.windows.HANDLE, lpExitCode: *u32) callconv(.winapi) i32;
84
- };
85
-
86
- var exit_code: u32 = 259; // STILL_ACTIVE
87
- while (exit_code == 259) {
88
- WinApi.Sleep(500);
89
- _ = WinApi.GetExitCodeProcess(child.hProcess, &exit_code);
90
- }
91
- }
92
-
93
- fn spawnChildProcess(self: *DevServer, exe_path: []const u8) !ChildProcess {
94
- _ = self;
95
- var cmd_utf16: [1024]u16 = undefined;
96
- const len = try std.unicode.utf8ToUtf16Le(&cmd_utf16, exe_path);
97
- cmd_utf16[len] = 0;
98
-
99
- const STARTUPINFOW = extern struct {
100
- cb: u32 = 104,
101
- lpReserved: ?[*:0]u16 = null,
102
- lpDesktop: ?[*:0]u16 = null,
103
- lpTitle: ?[*:0]u16 = null,
104
- dwX: u32 = 0,
105
- dwY: u32 = 0,
106
- dwXSize: u32 = 0,
107
- dwYSize: u32 = 0,
108
- dwXCountChars: u32 = 0,
109
- dwYCountChars: u32 = 0,
110
- dwFillAttribute: u32 = 0,
111
- dwFlags: u32 = 0,
112
- wShowWindow: u16 = 0,
113
- cbReserved2: u16 = 0,
114
- lpReserved2: ?*u8 = null,
115
- hStdInput: ?std.os.windows.HANDLE = null,
116
- hStdOutput: ?std.os.windows.HANDLE = null,
117
- hStdError: ?std.os.windows.HANDLE = null,
118
- };
119
-
120
- const PROCESS_INFORMATION = extern struct {
121
- hProcess: std.os.windows.HANDLE,
122
- hThread: std.os.windows.HANDLE,
123
- dwProcessId: u32,
124
- dwThreadId: u32,
125
- };
126
-
127
- const WinApi = struct {
128
- pub extern "kernel32" fn CreateProcessW(
129
- lpApplicationName: ?[*:0]const u16,
130
- lpCommandLine: ?[*:0]u16,
131
- lpProcessAttributes: ?*anyopaque,
132
- lpThreadAttributes: ?*anyopaque,
133
- bInheritHandles: i32,
134
- dwCreationFlags: u32,
135
- lpEnvironment: ?*anyopaque,
136
- lpCurrentDirectory: ?[*:0]const u16,
137
- lpStartupInfo: *const STARTUPINFOW,
138
- lpProcessInformation: *PROCESS_INFORMATION,
139
- ) callconv(.winapi) i32;
140
-
141
- pub extern "kernel32" fn TerminateProcess(hProcess: std.os.windows.HANDLE, uExitCode: u32) callconv(.winapi) i32;
142
- pub extern "kernel32" fn CloseHandle(hObject: std.os.windows.HANDLE) callconv(.winapi) i32;
143
- };
144
-
145
- var si = STARTUPINFOW{};
146
- var pi: PROCESS_INFORMATION = undefined;
147
-
148
- const res = WinApi.CreateProcessW(null, @ptrCast(&cmd_utf16), null, null, 0, 0, null, null, &si, &pi);
149
- if (res == 0) return error.ProcessCreationFailed;
150
-
151
- return ChildProcess{
152
- .hProcess = pi.hProcess,
153
- .hThread = pi.hThread,
154
- };
155
- }
156
-
157
- fn getAppModTime(self: *DevServer) i128 {
158
- _ = self;
159
- const io = std.Io.Threaded.global_single_threaded.io();
160
- const cwd = std.Io.Dir.cwd();
161
- var max_mtime: i128 = 0;
162
-
163
- var app_dir = cwd.openDir(io, "app", .{ .iterate = true }) catch return 0;
164
- defer app_dir.close(io);
165
-
166
- var iter = app_dir.iterate();
167
- while (iter.next(io) catch null) |entry| {
168
- if (entry.kind == .file) {
169
- if (app_dir.statFile(io, entry.name, .{})) |st| {
170
- const ns = st.mtime.toNanoseconds();
171
- if (ns > max_mtime) max_mtime = ns;
172
- } else |_| {}
173
- }
174
- }
175
- return max_mtime;
176
- }
177
- };
178
-
179
- pub const ChildProcess = struct {
180
- hProcess: std.os.windows.HANDLE,
181
- hThread: std.os.windows.HANDLE,
182
-
183
- pub fn kill(self: *ChildProcess) void {
184
- const WinApi = struct {
185
- pub extern "kernel32" fn TerminateProcess(hProcess: std.os.windows.HANDLE, uExitCode: u32) callconv(.winapi) i32;
186
- pub extern "kernel32" fn CloseHandle(hObject: std.os.windows.HANDLE) callconv(.winapi) i32;
187
- };
188
- _ = WinApi.TerminateProcess(self.hProcess, 1);
189
- _ = WinApi.CloseHandle(self.hProcess);
190
- _ = WinApi.CloseHandle(self.hThread);
191
- }
192
- };
package/cli/installer.zig DELETED
@@ -1,362 +0,0 @@
1
- const std = @import("std");
2
-
3
- // ─── NanoShell Installer Generator ───────────────────────────────────────────
4
- // Reads nanoshell.json, generates a professional Inno Setup .iss script,
5
- // then auto-compiles with ISCC.exe if found.
6
- //
7
- // The generated installer shows a DIALOG letting the user choose:
8
- // [●] Install for all users → Program Files (requires UAC / Admin)
9
- // [○] Install just for me → AppData\Local (no admin needed)
10
- // ─────────────────────────────────────────────────────────────────────────────
11
-
12
- pub const AppConfig = struct {
13
- name: []const u8 = "MyApp",
14
- version: []const u8 = "1.0.0",
15
- author: []const u8 = "Unknown",
16
- description: []const u8 = "A NanoShell desktop application",
17
- icon: []const u8 = "assets\\icon.ico",
18
- url: []const u8 = "https://example.com",
19
- exe_name: []const u8 = "app.exe",
20
- allocated: bool = false,
21
-
22
- pub fn deinit(self: *AppConfig, allocator: std.mem.Allocator) void {
23
- const def = AppConfig{};
24
- if (self.name.ptr != def.name.ptr) allocator.free(self.name);
25
- if (self.version.ptr != def.version.ptr) allocator.free(self.version);
26
- if (self.author.ptr != def.author.ptr) allocator.free(self.author);
27
- if (self.description.ptr != def.description.ptr) allocator.free(self.description);
28
- if (self.icon.ptr != def.icon.ptr) allocator.free(self.icon);
29
- if (self.url.ptr != def.url.ptr) allocator.free(self.url);
30
- if (self.exe_name.ptr != def.exe_name.ptr) allocator.free(self.exe_name);
31
- }
32
- };
33
-
34
- /// Load config from nanoshell.json. Falls back to defaults if missing.
35
- pub fn loadConfig(allocator: std.mem.Allocator) !AppConfig {
36
- const io = std.Io.Threaded.global_single_threaded.io();
37
- const content = std.Io.Dir.cwd().readFileAlloc(io, "nanoshell.json", allocator, .unlimited) catch |err| {
38
- if (err == error.FileNotFound) {
39
- std.log.warn("nanoshell.json not found - using defaults.", .{});
40
- return AppConfig{};
41
- }
42
- return err;
43
- };
44
- defer allocator.free(content);
45
-
46
- var config = AppConfig{};
47
- if (jsonField(allocator, content, "name")) |v| { config.name = v; config.allocated = true; }
48
- if (jsonField(allocator, content, "version")) |v| { config.version = v; config.allocated = true; }
49
- if (jsonField(allocator, content, "author")) |v| { config.author = v; config.allocated = true; }
50
- if (jsonField(allocator, content, "description")) |v| { config.description = v; config.allocated = true; }
51
- if (jsonField(allocator, content, "icon")) |v| { config.icon = v; config.allocated = true; }
52
- if (jsonField(allocator, content, "url")) |v| { config.url = v; config.allocated = true; }
53
- if (jsonField(allocator, content, "exe_name")) |v| { config.exe_name = v; config.allocated = true; }
54
- return config;
55
- }
56
-
57
- fn jsonField(allocator: std.mem.Allocator, json: []const u8, key: []const u8) ?[]const u8 {
58
- const needle = std.fmt.allocPrint(allocator, "\"{s}\"", .{key}) catch return null;
59
- defer allocator.free(needle);
60
- const pos = std.mem.indexOf(u8, json, needle) orelse return null;
61
- const rest = json[pos + needle.len ..];
62
- var i: usize = 0;
63
- while (i < rest.len and (rest[i] == ' ' or rest[i] == ':' or rest[i] == '\t')) : (i += 1) {}
64
- if (i >= rest.len or rest[i] != '"') return null;
65
- i += 1;
66
- const start = i;
67
- while (i < rest.len and rest[i] != '"') : (i += 1) {}
68
- if (i >= rest.len) return null;
69
- return allocator.dupe(u8, rest[start..i]) catch null;
70
- }
71
-
72
- extern "kernel32" fn GetEnvironmentVariableW(lpName: [*:0]const u16, lpBuffer: [*]u16, nSize: u32) callconv(.c) u32;
73
-
74
- fn getEnvVarDynamic(allocator: std.mem.Allocator, key: []const u8) ?[]const u8 {
75
- var key_utf16_buf: [256]u16 = undefined;
76
- const key_len = std.unicode.utf8ToUtf16Le(&key_utf16_buf, key) catch return null;
77
- key_utf16_buf[key_len] = 0;
78
-
79
- var val_utf16_buf: [1024]u16 = undefined;
80
- const len = GetEnvironmentVariableW(@ptrCast(&key_utf16_buf), &val_utf16_buf, 1024);
81
- if (len == 0 or len >= 1024) return null;
82
-
83
- var val_utf8_buf: [1024]u8 = undefined;
84
- const utf8_len = std.unicode.utf16LeToUtf8(&val_utf8_buf, val_utf16_buf[0..len]) catch return null;
85
- return allocator.dupe(u8, val_utf8_buf[0..utf8_len]) catch null;
86
- }
87
-
88
- /// Search for ISCC.exe dynamically across any user's system (%LocalAppData%, %ProgramFiles%, %ProgramFiles(x86)%)
89
- pub fn findIscc(allocator: std.mem.Allocator) ?[]const u8 {
90
- const io = std.Io.Threaded.global_single_threaded.io();
91
-
92
- const env_keys = [_][]const u8{ "LOCALAPPDATA", "PROGRAMFILES", "PROGRAMFILES(X86)" };
93
- const sub_paths = [_][]const u8{
94
- "\\Programs\\Inno Setup 6\\ISCC.exe",
95
- "\\Programs\\Inno Setup 5\\ISCC.exe",
96
- "\\Inno Setup 6\\ISCC.exe",
97
- "\\Inno Setup 5\\ISCC.exe",
98
- };
99
-
100
- for (env_keys) |key| {
101
- if (getEnvVarDynamic(allocator, key)) |base_dir| {
102
- defer allocator.free(base_dir);
103
- for (sub_paths) |sub| {
104
- if (std.fmt.allocPrint(allocator, "{s}{s}", .{ base_dir, sub })) |p| {
105
- if (std.Io.Dir.accessAbsolute(io, p, .{})) |_| {
106
- return p;
107
- } else |_| {
108
- allocator.free(p);
109
- }
110
- } else |_| {}
111
- }
112
- }
113
- }
114
-
115
- const fallback_paths = [_][]const u8{
116
- "C:\\Program Files (x86)\\Inno Setup 6\\ISCC.exe",
117
- "C:\\Program Files\\Inno Setup 6\\ISCC.exe",
118
- "C:\\Program Files (x86)\\Inno Setup 5\\ISCC.exe",
119
- "C:\\Program Files\\Inno Setup 5\\ISCC.exe",
120
- };
121
- for (fallback_paths) |p| {
122
- std.Io.Dir.accessAbsolute(io, p, .{}) catch continue;
123
- return allocator.dupe(u8, p) catch null;
124
- }
125
-
126
- return null;
127
- }
128
-
129
- /// Generate the Inno Setup script.
130
- pub fn generateIss(allocator: std.mem.Allocator, config: AppConfig) ![]const u8 {
131
- const app_id = try allocator.dupe(u8, config.name);
132
- defer allocator.free(app_id);
133
- for (app_id) |*c| if (c.* == ' ') { c.* = '_'; };
134
-
135
- return std.fmt.allocPrint(allocator,
136
- \\; NanoShell Installer Script - generated by: npx nanoshell package
137
- \\; App: {s} v{s} | Author: {s}
138
- \\
139
- \\#define AppName "{s}"
140
- \\#define AppVersion "{s}"
141
- \\#define AppPublisher "{s}"
142
- \\#define AppURL "{s}"
143
- \\#define AppExeName "{s}"
144
- \\
145
- \\[Setup]
146
- \\AppId={{{{NS-{s}}}}}
147
- \\AppName={{#AppName}}
148
- \\AppVersion={{#AppVersion}}
149
- \\AppVerName={{#AppName}} {{#AppVersion}}
150
- \\AppPublisher={{#AppPublisher}}
151
- \\AppPublisherURL={{#AppURL}}
152
- \\AppSupportURL={{#AppURL}}
153
- \\AppUpdatesURL={{#AppURL}}
154
- \\
155
- \\; --- Install Scope ------------------------------------------------
156
- \\; PrivilegesRequired=lowest means non-admin install by default.
157
- \\; PrivilegesRequiredOverridesAllowed=dialog adds a dialog so the
158
- \\; user can CHOOSE between per-user or system-wide (admin) install.
159
- \\PrivilegesRequired=lowest
160
- \\PrivilegesRequiredOverridesAllowed=dialog
161
- \\
162
- \\DefaultDirName={{autopf}}\\{{#AppName}}
163
- \\DefaultGroupName={{#AppName}}
164
- \\AllowNoIcons=yes
165
- \\
166
- \\; --- Output -------------------------------------------------------
167
- \\OutputDir=dist
168
- \\OutputBaseFilename={{#AppName}}-Setup
169
- \\
170
- \\; --- Compression (lzma2/ultra64 = smallest .exe size) ---------------
171
- \\Compression=lzma2/ultra64
172
- \\SolidCompression=yes
173
- \\LZMAUseSeparateProcess=yes
174
- \\
175
- \\; --- Architecture --------------------------------------------------
176
- \\ArchitecturesInstallIn64BitMode=x64compatible
177
- \\
178
- \\; --- Appearance ----------------------------------------------------
179
- \\WizardStyle=modern
180
- \\
181
- \\[Languages]
182
- \\Name: "english"; MessagesFile: "compiler:Default.isl"
183
- \\
184
- \\[Tasks]
185
- \\Name: "desktopicon"; Description: "{{cm:CreateDesktopIcon}}"; GroupDescription: "{{cm:AdditionalIcons}}"; Flags: unchecked
186
- \\
187
- \\[Files]
188
- \\; Main executable (searches dist, root, zig-out\bin, and bin)
189
- \\Source: "dist\{{#AppName}}.exe"; DestDir: "{{app}}"; DestName: "{{#AppExeName}}"; Flags: ignoreversion skipifsourcedoesntexist
190
- \\Source: "example_app.exe"; DestDir: "{{app}}"; DestName: "{{#AppExeName}}"; Flags: ignoreversion skipifsourcedoesntexist
191
- \\Source: "zig-out\bin\example_app.exe"; DestDir: "{{app}}"; DestName: "{{#AppExeName}}"; Flags: ignoreversion skipifsourcedoesntexist
192
- \\Source: "bin\{{#AppExeName}}"; DestDir: "{{app}}"; Flags: ignoreversion skipifsourcedoesntexist
193
- \\
194
- \\; NanoShell Runtime DLLs & Portable C++ Runtimes (vendor\lib and bin)
195
- \\Source: "vendor\lib\AppCore.dll"; DestDir: "{{app}}"; Flags: ignoreversion skipifsourcedoesntexist
196
- \\Source: "vendor\lib\Ultralight.dll"; DestDir: "{{app}}"; Flags: ignoreversion skipifsourcedoesntexist
197
- \\Source: "vendor\lib\UltralightCore.dll"; DestDir: "{{app}}"; Flags: ignoreversion skipifsourcedoesntexist
198
- \\Source: "vendor\lib\WebCore.dll"; DestDir: "{{app}}"; Flags: ignoreversion skipifsourcedoesntexist
199
- \\Source: "vendor\lib\icudt67l.dat"; DestDir: "{{app}}"; Flags: ignoreversion skipifsourcedoesntexist
200
- \\Source: "vendor\lib\cacert.pem"; DestDir: "{{app}}"; Flags: ignoreversion skipifsourcedoesntexist
201
- \\Source: "vendor\lib\resources\*"; DestDir: "{{app}}\resources"; Flags: ignoreversion recursesubdirs createallsubdirs skipifsourcedoesntexist
202
- \\
203
- \\Source: "bin\AppCore.dll"; DestDir: "{{app}}"; Flags: ignoreversion skipifsourcedoesntexist
204
- \\Source: "bin\Ultralight.dll"; DestDir: "{{app}}"; Flags: ignoreversion skipifsourcedoesntexist
205
- \\Source: "bin\UltralightCore.dll"; DestDir: "{{app}}"; Flags: ignoreversion skipifsourcedoesntexist
206
- \\Source: "bin\WebCore.dll"; DestDir: "{{app}}"; Flags: ignoreversion skipifsourcedoesntexist
207
- \\Source: "bin\vcruntime140.dll"; DestDir: "{{app}}"; Flags: ignoreversion skipifsourcedoesntexist
208
- \\Source: "bin\msvcp140.dll"; DestDir: "{{app}}"; Flags: ignoreversion skipifsourcedoesntexist
209
- \\Source: "bin\vcruntime140_1.dll"; DestDir: "{{app}}"; Flags: ignoreversion skipifsourcedoesntexist
210
- \\Source: "bin\icudt67l.dat"; DestDir: "{{app}}"; Flags: ignoreversion skipifsourcedoesntexist
211
- \\Source: "bin\cacert.pem"; DestDir: "{{app}}"; Flags: ignoreversion skipifsourcedoesntexist
212
- \\Source: "bin\resources\*"; DestDir: "{{app}}\resources"; Flags: ignoreversion recursesubdirs createallsubdirs skipifsourcedoesntexist
213
- \\
214
- \\; App HTML / CSS / JS assets
215
- \\Source: "example_app\*"; DestDir: "{{app}}\app"; Flags: ignoreversion recursesubdirs createallsubdirs skipifsourcedoesntexist
216
- \\Source: "app\*"; DestDir: "{{app}}\app"; Flags: ignoreversion recursesubdirs createallsubdirs skipifsourcedoesntexist
217
- \\
218
- \\[Icons]
219
- \\Name: "{{group}}\\{{#AppName}}"; Filename: "{{app}}\\{{#AppExeName}}"; WorkingDir: "{{app}}"
220
- \\Name: "{{group}}\\{{cm:UninstallProgram,{{#AppName}}}}"; Filename: "{{uninstallexe}}"
221
- \\Name: "{{userdesktop}}\\{{#AppName}}"; Filename: "{{app}}\\{{#AppExeName}}"; WorkingDir: "{{app}}"; Tasks: desktopicon
222
- \\
223
- \\[Run]
224
- \\Filename: "{{app}}\\{{#AppExeName}}"; WorkingDir: "{{app}}"; Description: "{{cm:LaunchProgram,{{#StringChange(AppName, '&', '&&')}}}}"; Flags: nowait postinstall skipifsilent
225
- \\
226
- \\[UninstallRun]
227
- \\Filename: "{{app}}\\{{#AppExeName}}"; Parameters: "--uninstall"; Flags: skipifdoesntexist runhidden; RunOnceId: "CleanupApp"
228
- \\
229
- , .{
230
- config.name,
231
- config.version,
232
- config.author,
233
- config.name,
234
- config.version,
235
- config.author,
236
- config.url,
237
- config.exe_name,
238
- app_id,
239
- });
240
- }
241
-
242
- /// Main: load config, write .iss, find ISCC, auto-compile.
243
- pub fn run(allocator: std.mem.Allocator) !void {
244
- const io = std.Io.Threaded.global_single_threaded.io();
245
- std.log.info("NanoShell Installer Generator v1.0.2", .{});
246
- std.log.info("Powered by Inno Setup . by Suman Biswas", .{});
247
-
248
- std.log.info("[1/4] Reading nanoshell.json...", .{});
249
- var config = try loadConfig(allocator);
250
- defer config.deinit(allocator);
251
- std.log.info("App: {s} v{s} by {s} ({s})", .{ config.name, config.version, config.author, config.exe_name });
252
-
253
- std.log.info("[2/4] Generating setup.iss...", .{});
254
- const iss = try generateIss(allocator, config);
255
- defer allocator.free(iss);
256
- try std.Io.Dir.cwd().writeFile(io, .{ .sub_path = "setup.iss", .data = iss });
257
- std.log.info("Written: setup.iss", .{});
258
-
259
- _ = std.Io.Dir.cwd().createDir(io, "dist", .default_dir) catch {};
260
-
261
- std.log.info("[3/4] Searching for Inno Setup (ISCC.exe)...", .{});
262
- if (findIscc(allocator)) |iscc_path| {
263
- defer allocator.free(iscc_path);
264
- std.log.info("Found Inno Setup at: {s}", .{iscc_path});
265
- std.log.info("[4/4] Compiling setup.iss into dist\\{s}-Setup.exe...", .{config.name});
266
-
267
- // Auto-compile using Windows CreateProcessW API
268
- const cmd_str = try std.fmt.allocPrint(allocator, "\"{s}\" setup.iss", .{iscc_path});
269
- defer allocator.free(cmd_str);
270
-
271
- var cmd_utf16: [1024]u16 = undefined;
272
- const cmd_len = try std.unicode.utf8ToUtf16Le(&cmd_utf16, cmd_str);
273
- cmd_utf16[cmd_len] = 0;
274
-
275
- const STARTUPINFOW = extern struct {
276
- cb: u32 = 104,
277
- lpReserved: ?[*:0]u16 = null,
278
- lpDesktop: ?[*:0]u16 = null,
279
- lpTitle: ?[*:0]u16 = null,
280
- dwX: u32 = 0,
281
- dwY: u32 = 0,
282
- dwXSize: u32 = 0,
283
- dwYSize: u32 = 0,
284
- dwXCountChars: u32 = 0,
285
- dwYCountChars: u32 = 0,
286
- dwFillAttribute: u32 = 0,
287
- dwFlags: u32 = 0,
288
- wShowWindow: u16 = 0,
289
- cbReserved2: u16 = 0,
290
- lpReserved2: ?*u8 = null,
291
- hStdInput: ?std.os.windows.HANDLE = null,
292
- hStdOutput: ?std.os.windows.HANDLE = null,
293
- hStdError: ?std.os.windows.HANDLE = null,
294
- };
295
-
296
- const PROCESS_INFORMATION = extern struct {
297
- hProcess: std.os.windows.HANDLE,
298
- hThread: std.os.windows.HANDLE,
299
- dwProcessId: u32,
300
- dwThreadId: u32,
301
- };
302
-
303
- const WinApi = struct {
304
- pub extern "kernel32" fn CreateProcessW(
305
- lpApplicationName: ?[*:0]const u16,
306
- lpCommandLine: ?[*:0]u16,
307
- lpProcessAttributes: ?*anyopaque,
308
- lpThreadAttributes: ?*anyopaque,
309
- bInheritHandles: i32,
310
- dwCreationFlags: u32,
311
- lpEnvironment: ?*anyopaque,
312
- lpCurrentDirectory: ?[*:0]const u16,
313
- lpStartupInfo: *const STARTUPINFOW,
314
- lpProcessInformation: *PROCESS_INFORMATION,
315
- ) callconv(.winapi) i32;
316
-
317
- pub extern "kernel32" fn WaitForSingleObject(hHandle: std.os.windows.HANDLE, dwMilliseconds: u32) callconv(.winapi) u32;
318
- pub extern "kernel32" fn CloseHandle(hObject: std.os.windows.HANDLE) callconv(.winapi) i32;
319
- pub extern "kernel32" fn GetExitCodeProcess(hProcess: std.os.windows.HANDLE, lpExitCode: *u32) callconv(.winapi) i32;
320
- };
321
-
322
- var si = STARTUPINFOW{};
323
- var pi: PROCESS_INFORMATION = undefined;
324
-
325
- const res = WinApi.CreateProcessW(
326
- null,
327
- @ptrCast(&cmd_utf16),
328
- null,
329
- null,
330
- 0,
331
- 0,
332
- null,
333
- null,
334
- &si,
335
- &pi,
336
- );
337
-
338
- if (res != 0) {
339
- defer _ = WinApi.CloseHandle(pi.hProcess);
340
- defer _ = WinApi.CloseHandle(pi.hThread);
341
- _ = WinApi.WaitForSingleObject(pi.hProcess, 0xFFFFFFFF);
342
-
343
- var exit_code: u32 = 0;
344
- _ = WinApi.GetExitCodeProcess(pi.hProcess, &exit_code);
345
-
346
- if (exit_code == 0) {
347
- std.log.info("=================================================================", .{});
348
- std.log.info(" SUCCESS! Windows Installer Built -> dist\\{s}-Setup.exe", .{config.name});
349
- std.log.info("=================================================================", .{});
350
- } else {
351
- std.log.err("ISCC compilation exited with code {d}.", .{exit_code});
352
- }
353
- } else {
354
- std.log.warn("Could not auto-launch ISCC.exe. Open setup.iss in Inno Setup.", .{});
355
- }
356
- } else {
357
- std.log.info("[4/4] setup.iss generated successfully!", .{});
358
- std.log.info(" 1. Install Inno Setup: https://jrsoftware.org/isdl.php", .{});
359
- std.log.info(" 2. Open setup.iss or run: ISCC setup.iss", .{});
360
- std.log.info(" 3. Output binary will be saved in: dist\\{s}-Setup.exe", .{config.name});
361
- }
362
- }