nanoshell 1.7.3 → 1.7.5

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/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
- }
package/cli/main.zig DELETED
@@ -1,264 +0,0 @@
1
- const std = @import("std");
2
- const installer = @import("installer.zig");
3
-
4
- extern "kernel32" fn GetCommandLineW() callconv(.c) [*:0]const u16;
5
-
6
- pub fn main() !void {
7
- const allocator = std.heap.page_allocator;
8
-
9
- const cmd_line_ptr = GetCommandLineW();
10
- const len = std.mem.indexOfSentinel(u16, 0, cmd_line_ptr);
11
-
12
- var args_iter = try std.process.Args.Iterator.Windows.init(allocator, cmd_line_ptr[0..len]);
13
- defer args_iter.deinit();
14
-
15
- _ = args_iter.skip(); // skip executable name
16
-
17
- const subcmd = args_iter.next() orelse "";
18
-
19
- if (std.mem.eql(u8, subcmd, "build")) {
20
- std.log.info("NanoShell Build", .{});
21
- std.log.info("Building production release binary...", .{});
22
-
23
- const io = std.Io.Threaded.global_single_threaded.io();
24
- _ = std.Io.Dir.cwd().createDir(io, "dist", .default_dir) catch {};
25
-
26
- var config = installer.loadConfig(allocator) catch installer.AppConfig{};
27
- defer config.deinit(allocator);
28
-
29
- var bundler = @import("bundler.zig").SingleBinaryBundler.init(allocator, .{
30
- .app_name = config.name,
31
- .entry_point = "app/index.html",
32
- .output_dir = "dist",
33
- });
34
- try bundler.bundle();
35
-
36
- std.log.info("Build complete.", .{});
37
- return;
38
- }
39
-
40
- if (std.mem.eql(u8, subcmd, "package") or std.mem.eql(u8, subcmd, "installer")) {
41
- std.log.info("NanoShell Build & Package", .{});
42
- std.log.info("Building production release binary...", .{});
43
-
44
- const io = std.Io.Threaded.global_single_threaded.io();
45
- _ = std.Io.Dir.cwd().createDir(io, "dist", .default_dir) catch {};
46
-
47
- var config = installer.loadConfig(allocator) catch installer.AppConfig{};
48
- defer config.deinit(allocator);
49
-
50
- var bundler = @import("bundler.zig").SingleBinaryBundler.init(allocator, .{
51
- .app_name = config.name,
52
- .entry_point = "app/index.html",
53
- .output_dir = "dist",
54
- });
55
- try bundler.bundle();
56
-
57
- try installer.run(allocator);
58
- return;
59
- }
60
-
61
- const dev_server = @import("dev_server.zig");
62
-
63
- if (std.mem.eql(u8, subcmd, "start")) {
64
- var server = dev_server.DevServer.init(allocator);
65
- try server.runDevMode(".");
66
- return;
67
- }
68
-
69
- // Scaffolding a new NanoShell project if an app name is passed
70
- if (subcmd.len > 0 and !std.mem.startsWith(u8, subcmd, "-")) {
71
- const app_dir_name = subcmd;
72
- std.log.info("Scaffolding new NanoShell app in .\\{s}...", .{app_dir_name});
73
-
74
- const io = std.Io.Threaded.global_single_threaded.io();
75
- _ = std.Io.Dir.cwd().createDir(io, app_dir_name, .default_dir) catch |err| {
76
- if (err != error.PathAlreadyExists) {
77
- std.log.err("Failed to create directory '{s}': {s}", .{ app_dir_name, @errorName(err) });
78
- return err;
79
- }
80
- };
81
-
82
- var app_dir = std.Io.Dir.cwd().openDir(io, app_dir_name, .{}) catch |err| {
83
- std.log.err("Failed to open directory '{s}': {s}", .{ app_dir_name, @errorName(err) });
84
- return err;
85
- };
86
-
87
- _ = app_dir.createDir(io, "app", .default_dir) catch {};
88
- _ = app_dir.createDir(io, "assets", .default_dir) catch {};
89
- _ = app_dir.createDir(io, "resources", .default_dir) catch {};
90
- _ = app_dir.createDir(io, "bin", .default_dir) catch {};
91
- _ = app_dir.createDir(io, "bin/resources", .default_dir) catch {};
92
-
93
- // Write placeholder assets/icon.ico
94
- try app_dir.writeFile(io, .{ .sub_path = "assets/icon.ico", .data = "NANO_ICON" });
95
-
96
- // Write default nanoshell.json with full window management settings
97
- const json_content = try std.fmt.allocPrint(allocator,
98
- "{{\n" ++
99
- " \"name\": \"{s}\",\n" ++
100
- " \"version\": \"1.0.0\",\n" ++
101
- " \"author\": \"Your Name\",\n" ++
102
- " \"description\": \"Blazing fast desktop app created with NanoShell\",\n" ++
103
- " \"icon\": \"assets\\\\icon.ico\",\n" ++
104
- " \"exe_name\": \"{s}.exe\",\n" ++
105
- " \"window\": {{\n" ++
106
- " \"width\": 1280,\n" ++
107
- " \"height\": 720,\n" ++
108
- " \"resizable\": true,\n" ++
109
- " \"frameless\": false,\n" ++
110
- " \"transparent\": false,\n" ++
111
- " \"always_on_top\": false\n" ++
112
- " }}\n" ++
113
- "}}\n",
114
- .{ app_dir_name, app_dir_name }
115
- );
116
- defer allocator.free(json_content);
117
-
118
- try app_dir.writeFile(io, .{ .sub_path = "nanoshell.json", .data = json_content });
119
-
120
- // Write default HTML/CSS/JS inside app/
121
- const html_content =
122
- \\<!DOCTYPE html>
123
- \\<html lang="en">
124
- \\<head>
125
- \\ <meta charset="UTF-8">
126
- \\ <meta name="viewport" content="width=device-width, initial-scale=1.0">
127
- \\ <title>NanoShell Application</title>
128
- \\ <link rel="stylesheet" href="styles.css">
129
- \\</head>
130
- \\<body>
131
- \\ <div class="container">
132
- \\ <div class="fps-badge"><span id="fps-val">120</span> FPS Locked</div>
133
- \\ <h1>⚡ NanoShell Desktop App</h1>
134
- \\ <p>Ultra-lightweight WebKit desktop runtime</p>
135
- \\ <button id="btn">Click Me</button>
136
- \\ </div>
137
- \\ <!-- app.js is injected inline by the NanoShell engine at load time -->
138
- \\</body>
139
- \\</html>
140
- ;
141
- try app_dir.writeFile(io, .{ .sub_path = "app/index.html", .data = html_content });
142
-
143
- const css_content =
144
- \\body {
145
- \\ margin: 0;
146
- \\ font-family: system-ui, -apple-system, sans-serif;
147
- \\ background: #0f172a;
148
- \\ color: #f8fafc;
149
- \\ display: flex;
150
- \\ justify-content: center;
151
- \\ align-items: center;
152
- \\ height: 100vh;
153
- \\}
154
- \\.container {
155
- \\ position: relative;
156
- \\ text-align: center;
157
- \\ background: #1e293b;
158
- \\ padding: 2.5rem;
159
- \\ border-radius: 1rem;
160
- \\ box-shadow: 0 10px 25px rgba(0,0,0,0.5);
161
- \\}
162
- \\.fps-badge {
163
- \\ position: absolute;
164
- \\ top: 1rem;
165
- \\ right: 1rem;
166
- \\ font-size: 0.75rem;
167
- \\ font-weight: bold;
168
- \\ color: #38bdf8;
169
- \\ background: rgba(56, 189, 248, 0.1);
170
- \\ padding: 0.25rem 0.5rem;
171
- \\ border-radius: 0.25rem;
172
- \\}
173
- \\button {
174
- \\ background: #38bdf8;
175
- \\ color: #0f172a;
176
- \\ border: none;
177
- \\ padding: 0.75rem 1.5rem;
178
- \\ font-weight: bold;
179
- \\ border-radius: 0.5rem;
180
- \\ cursor: pointer;
181
- \\}
182
- \\button:hover { background: #7dd3fc; }
183
- ;
184
- try app_dir.writeFile(io, .{ .sub_path = "app/styles.css", .data = css_content });
185
-
186
- const js_content =
187
- \\// NanoShell Application JavaScript (0% Idle CPU Overhead)
188
- \\console.log('⚡ [NanoShell] App loaded successfully');
189
- \\
190
- \\const btn = document.getElementById('btn');
191
- \\if (btn) {
192
- \\ btn.addEventListener('click', () => {
193
- \\ alert('Hello from NanoShell!');
194
- \\ });
195
- \\}
196
- ;
197
- try app_dir.writeFile(io, .{ .sub_path = "app/app.js", .data = js_content });
198
-
199
- // Copy prebuilt runtime binaries and DLLs into scaffolded project
200
- const cwd = std.Io.Dir.cwd();
201
- const exe_dst_path = try std.fmt.allocPrint(allocator, "bin/{s}.exe", .{app_dir_name});
202
- defer allocator.free(exe_dst_path);
203
-
204
- _ = cwd.copyFile("zig-out/bin/example_app.exe", app_dir, exe_dst_path, io, .{}) catch
205
- cwd.copyFile("example_app.exe", app_dir, exe_dst_path, io, .{}) catch {};
206
-
207
- _ = cwd.copyFile("zig-out/bin/AppCore.dll", app_dir, "bin/AppCore.dll", io, .{}) catch
208
- cwd.copyFile("AppCore.dll", app_dir, "bin/AppCore.dll", io, .{}) catch {};
209
-
210
- _ = cwd.copyFile("zig-out/bin/Ultralight.dll", app_dir, "bin/Ultralight.dll", io, .{}) catch
211
- cwd.copyFile("Ultralight.dll", app_dir, "bin/Ultralight.dll", io, .{}) catch {};
212
-
213
- _ = cwd.copyFile("zig-out/bin/UltralightCore.dll", app_dir, "bin/UltralightCore.dll", io, .{}) catch
214
- cwd.copyFile("UltralightCore.dll", app_dir, "bin/UltralightCore.dll", io, .{}) catch {};
215
-
216
- _ = cwd.copyFile("zig-out/bin/WebCore.dll", app_dir, "bin/WebCore.dll", io, .{}) catch
217
- cwd.copyFile("WebCore.dll", app_dir, "bin/WebCore.dll", io, .{}) catch {};
218
-
219
- _ = cwd.copyFile("vendor/bin/vcruntime140.dll", app_dir, "bin/vcruntime140.dll", io, .{}) catch
220
- cwd.copyFile("zig-out/bin/vcruntime140.dll", app_dir, "bin/vcruntime140.dll", io, .{}) catch {};
221
-
222
- _ = cwd.copyFile("vendor/bin/msvcp140.dll", app_dir, "bin/msvcp140.dll", io, .{}) catch
223
- cwd.copyFile("zig-out/bin/msvcp140.dll", app_dir, "bin/msvcp140.dll", io, .{}) catch {};
224
-
225
- _ = cwd.copyFile("vendor/bin/vcruntime140_1.dll", app_dir, "bin/vcruntime140_1.dll", io, .{}) catch
226
- cwd.copyFile("zig-out/bin/vcruntime140_1.dll", app_dir, "bin/vcruntime140_1.dll", io, .{}) catch {};
227
-
228
- _ = cwd.copyFile("vendor/resources/icudt67l.dat", app_dir, "bin/icudt67l.dat", io, .{}) catch
229
- cwd.copyFile("bin/icudt67l.dat", app_dir, "bin/icudt67l.dat", io, .{}) catch {};
230
-
231
- _ = cwd.copyFile("vendor/resources/cacert.pem", app_dir, "bin/cacert.pem", io, .{}) catch
232
- cwd.copyFile("bin/cacert.pem", app_dir, "bin/cacert.pem", io, .{}) catch {};
233
-
234
- _ = cwd.copyFile("vendor/resources/icudt67l.dat", app_dir, "bin/resources/icudt67l.dat", io, .{}) catch
235
- cwd.copyFile("bin/resources/icudt67l.dat", app_dir, "bin/resources/icudt67l.dat", io, .{}) catch {};
236
-
237
- _ = cwd.copyFile("vendor/resources/cacert.pem", app_dir, "bin/resources/cacert.pem", io, .{}) catch
238
- cwd.copyFile("bin/resources/cacert.pem", app_dir, "bin/resources/cacert.pem", io, .{}) catch {};
239
-
240
- std.log.info("=================================================================", .{});
241
- std.log.info(" SUCCESS! Scaffolded new NanoShell app: .\\{s}", .{app_dir_name});
242
- std.log.info(" To get started:", .{});
243
- std.log.info(" cd {s}", .{app_dir_name});
244
- std.log.info(" npx nanoshell package", .{});
245
- std.log.info("=================================================================", .{});
246
- return;
247
- }
248
-
249
- // Help banner if no command or invalid command is provided
250
- std.log.info("+===================================================+", .{});
251
- std.log.info("| NanoShell CLI v1.0.4 . by Suman Biswas |", .{});
252
- std.log.info("| 17MB RAM . 120 FPS . WebKit-powered desktop apps |", .{});
253
- std.log.info("+===================================================+", .{});
254
- std.log.info("Usage:", .{});
255
- std.log.info(" npx nanoshell <my-app> Scaffold a new NanoShell app", .{});
256
- std.log.info(" npx nanoshell build Build production release binary only", .{});
257
- std.log.info(" npx nanoshell start Launch the dev engine", .{});
258
- std.log.info(" npx nanoshell package Build binary and generate Windows installer", .{});
259
- std.log.info("nanoshell package:", .{});
260
- std.log.info(" - Reads nanoshell.json for app name, version, author", .{});
261
- std.log.info(" - Generates setup.iss (Inno Setup script)", .{});
262
- std.log.info(" - Auto-compiles to dist\\AppName-Setup.exe if ISCC found", .{});
263
- std.log.info(" - Installer lets user choose: Admin OR per-user install", .{});
264
- }
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file