@zappdev/cli 0.1.0

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 (209) hide show
  1. package/README.md +55 -0
  2. package/dist/zapp-cli.js +9471 -0
  3. package/native/src/app/app.zc +490 -0
  4. package/native/src/event/event.zc +24 -0
  5. package/native/src/event/events.zc +70 -0
  6. package/native/src/platform/darwin/backend.zc +923 -0
  7. package/native/src/platform/darwin/backend_bootstrap.zc +9 -0
  8. package/native/src/platform/darwin/bootstrap.zc +9 -0
  9. package/native/src/platform/darwin/engine_jsc.zc +86 -0
  10. package/native/src/platform/darwin/engine_qjs.zc +92 -0
  11. package/native/src/platform/darwin/platform.zc +156 -0
  12. package/native/src/platform/darwin/webview.zc +550 -0
  13. package/native/src/platform/darwin/webview_bootstrap.zc +9 -0
  14. package/native/src/platform/darwin/window.zc +1223 -0
  15. package/native/src/platform/darwin/worker/common.zc +223 -0
  16. package/native/src/platform/darwin/worker/core/base64_core.zc +29 -0
  17. package/native/src/platform/darwin/worker/core/crypto_core.zc +19 -0
  18. package/native/src/platform/darwin/worker/core/encoding_core.zc +32 -0
  19. package/native/src/platform/darwin/worker/core/fetch_core.zc +145 -0
  20. package/native/src/platform/darwin/worker/core/url_core.zc +69 -0
  21. package/native/src/platform/darwin/worker/core/websocket_core.zc +179 -0
  22. package/native/src/platform/darwin/worker/dispatch.zc +55 -0
  23. package/native/src/platform/darwin/worker/jsc/base64_jsc.zc +39 -0
  24. package/native/src/platform/darwin/worker/jsc/crypto_jsc.zc +49 -0
  25. package/native/src/platform/darwin/worker/jsc/encoding_jsc.zc +86 -0
  26. package/native/src/platform/darwin/worker/jsc/fetch_jsc.zc +149 -0
  27. package/native/src/platform/darwin/worker/jsc/url_jsc.zc +54 -0
  28. package/native/src/platform/darwin/worker/jsc/websocket_jsc.zc +127 -0
  29. package/native/src/platform/darwin/worker/jsc.zc +670 -0
  30. package/native/src/platform/darwin/worker/mod.zc +30 -0
  31. package/native/src/platform/darwin/worker/qjs/fetch_qjs.zc +233 -0
  32. package/native/src/platform/darwin/worker/qjs/qjs_macros.zc +23 -0
  33. package/native/src/platform/darwin/worker/qjs/websocket_qjs.zc +223 -0
  34. package/native/src/platform/darwin/worker/qjs.zc +1053 -0
  35. package/native/src/platform/darwin/worker/timers.zc +149 -0
  36. package/native/src/platform/darwin/worker/timers_qjs.zc +209 -0
  37. package/native/src/platform/platform.zc +64 -0
  38. package/native/src/platform/shared/log.zc +156 -0
  39. package/native/src/platform/shared/worker/qjs/base64_qjs.zc +38 -0
  40. package/native/src/platform/shared/worker/qjs/crypto_qjs.zc +44 -0
  41. package/native/src/platform/shared/worker/qjs/encoding_qjs.zc +95 -0
  42. package/native/src/platform/shared/worker/qjs/url_qjs.zc +65 -0
  43. package/native/src/platform/shared/worker_registry.zc +206 -0
  44. package/native/src/platform/window.zc +446 -0
  45. package/native/src/platform/windows/backend.zc +452 -0
  46. package/native/src/platform/windows/backend_bootstrap.zc +9 -0
  47. package/native/src/platform/windows/bootstrap.zc +9 -0
  48. package/native/src/platform/windows/engine_qjs.zc +60 -0
  49. package/native/src/platform/windows/platform.zc +387 -0
  50. package/native/src/platform/windows/webview.zc +1175 -0
  51. package/native/src/platform/windows/webview_bootstrap.zc +9 -0
  52. package/native/src/platform/windows/window.zc +1271 -0
  53. package/native/src/platform/windows/worker/common.zc +409 -0
  54. package/native/src/platform/windows/worker/core/base64_core.zc +52 -0
  55. package/native/src/platform/windows/worker/core/crypto_core.zc +34 -0
  56. package/native/src/platform/windows/worker/core/encoding_core.zc +60 -0
  57. package/native/src/platform/windows/worker/core/fetch_core.zc +274 -0
  58. package/native/src/platform/windows/worker/core/url_core.zc +216 -0
  59. package/native/src/platform/windows/worker/core/websocket_core.zc +343 -0
  60. package/native/src/platform/windows/worker/dispatch.zc +34 -0
  61. package/native/src/platform/windows/worker/mod.zc +46 -0
  62. package/native/src/platform/windows/worker/qjs/fetch_qjs.zc +255 -0
  63. package/native/src/platform/windows/worker/qjs/websocket_qjs.zc +263 -0
  64. package/native/src/platform/windows/worker/qjs.zc +1049 -0
  65. package/native/src/platform/windows/worker/timers_qjs.zc +288 -0
  66. package/native/src/platform/worker.zc +8 -0
  67. package/native/src/service/service.zc +228 -0
  68. package/native/vendor/quickjs-ng/.gitattributes +4 -0
  69. package/native/vendor/quickjs-ng/.github/dependabot.yml +7 -0
  70. package/native/vendor/quickjs-ng/.github/workflows/ci.yml +812 -0
  71. package/native/vendor/quickjs-ng/.github/workflows/docs.yml +49 -0
  72. package/native/vendor/quickjs-ng/.github/workflows/release.yml +162 -0
  73. package/native/vendor/quickjs-ng/.github/workflows/test-docs.yml +23 -0
  74. package/native/vendor/quickjs-ng/.github/workflows/tsan.yml +32 -0
  75. package/native/vendor/quickjs-ng/.github/workflows/valgrind.yml +33 -0
  76. package/native/vendor/quickjs-ng/.gitmodules +5 -0
  77. package/native/vendor/quickjs-ng/CMakeLists.txt +553 -0
  78. package/native/vendor/quickjs-ng/LICENSE +24 -0
  79. package/native/vendor/quickjs-ng/Makefile +149 -0
  80. package/native/vendor/quickjs-ng/amalgam.js +53 -0
  81. package/native/vendor/quickjs-ng/api-test.c +927 -0
  82. package/native/vendor/quickjs-ng/builtin-array-fromasync.h +119 -0
  83. package/native/vendor/quickjs-ng/builtin-array-fromasync.js +36 -0
  84. package/native/vendor/quickjs-ng/builtin-iterator-zip-keyed.h +332 -0
  85. package/native/vendor/quickjs-ng/builtin-iterator-zip-keyed.js +194 -0
  86. package/native/vendor/quickjs-ng/builtin-iterator-zip.h +337 -0
  87. package/native/vendor/quickjs-ng/builtin-iterator-zip.js +210 -0
  88. package/native/vendor/quickjs-ng/ctest.c +17 -0
  89. package/native/vendor/quickjs-ng/cutils.h +2013 -0
  90. package/native/vendor/quickjs-ng/cxxtest.cc +2 -0
  91. package/native/vendor/quickjs-ng/dtoa.c +1619 -0
  92. package/native/vendor/quickjs-ng/dtoa.h +87 -0
  93. package/native/vendor/quickjs-ng/examples/fib.c +67 -0
  94. package/native/vendor/quickjs-ng/examples/fib_module.js +10 -0
  95. package/native/vendor/quickjs-ng/examples/hello.js +1 -0
  96. package/native/vendor/quickjs-ng/examples/hello_module.js +6 -0
  97. package/native/vendor/quickjs-ng/examples/meson.build +17 -0
  98. package/native/vendor/quickjs-ng/examples/pi_bigint.js +118 -0
  99. package/native/vendor/quickjs-ng/examples/point.c +154 -0
  100. package/native/vendor/quickjs-ng/examples/test_fib.js +8 -0
  101. package/native/vendor/quickjs-ng/examples/test_point.js +43 -0
  102. package/native/vendor/quickjs-ng/fuzz.c +51 -0
  103. package/native/vendor/quickjs-ng/gen/function_source.c +81 -0
  104. package/native/vendor/quickjs-ng/gen/hello.c +53 -0
  105. package/native/vendor/quickjs-ng/gen/hello_module.c +106 -0
  106. package/native/vendor/quickjs-ng/gen/repl.c +3053 -0
  107. package/native/vendor/quickjs-ng/gen/standalone.c +324 -0
  108. package/native/vendor/quickjs-ng/gen/test_fib.c +81 -0
  109. package/native/vendor/quickjs-ng/libregexp-opcode.h +58 -0
  110. package/native/vendor/quickjs-ng/libregexp.c +2687 -0
  111. package/native/vendor/quickjs-ng/libregexp.h +98 -0
  112. package/native/vendor/quickjs-ng/libunicode-table.h +4707 -0
  113. package/native/vendor/quickjs-ng/libunicode.c +1746 -0
  114. package/native/vendor/quickjs-ng/libunicode.h +126 -0
  115. package/native/vendor/quickjs-ng/list.h +107 -0
  116. package/native/vendor/quickjs-ng/lre-test.c +73 -0
  117. package/native/vendor/quickjs-ng/meson.build +684 -0
  118. package/native/vendor/quickjs-ng/meson_options.txt +6 -0
  119. package/native/vendor/quickjs-ng/qjs-wasi-reactor.c +208 -0
  120. package/native/vendor/quickjs-ng/qjs.c +748 -0
  121. package/native/vendor/quickjs-ng/qjsc.c +673 -0
  122. package/native/vendor/quickjs-ng/quickjs-atom.h +267 -0
  123. package/native/vendor/quickjs-ng/quickjs-c-atomics.h +54 -0
  124. package/native/vendor/quickjs-ng/quickjs-libc.c +4986 -0
  125. package/native/vendor/quickjs-ng/quickjs-libc.h +79 -0
  126. package/native/vendor/quickjs-ng/quickjs-opcode.h +369 -0
  127. package/native/vendor/quickjs-ng/quickjs.c +60259 -0
  128. package/native/vendor/quickjs-ng/quickjs.h +1419 -0
  129. package/native/vendor/quickjs-ng/repl.js +1927 -0
  130. package/native/vendor/quickjs-ng/run-test262.c +2417 -0
  131. package/native/vendor/quickjs-ng/standalone.js +129 -0
  132. package/native/vendor/quickjs-ng/tests/assert.js +49 -0
  133. package/native/vendor/quickjs-ng/tests/bug1221.js +16 -0
  134. package/native/vendor/quickjs-ng/tests/bug1296.js +12 -0
  135. package/native/vendor/quickjs-ng/tests/bug1297.js +22 -0
  136. package/native/vendor/quickjs-ng/tests/bug1301.js +21 -0
  137. package/native/vendor/quickjs-ng/tests/bug1302.js +24 -0
  138. package/native/vendor/quickjs-ng/tests/bug1305.js +26 -0
  139. package/native/vendor/quickjs-ng/tests/bug1318.js +54 -0
  140. package/native/vendor/quickjs-ng/tests/bug1352.js +8 -0
  141. package/native/vendor/quickjs-ng/tests/bug1354.js +6 -0
  142. package/native/vendor/quickjs-ng/tests/bug1355.js +58 -0
  143. package/native/vendor/quickjs-ng/tests/bug1368.js +9 -0
  144. package/native/vendor/quickjs-ng/tests/bug39/1.js +6 -0
  145. package/native/vendor/quickjs-ng/tests/bug39/2.js +6 -0
  146. package/native/vendor/quickjs-ng/tests/bug39/3.js +7 -0
  147. package/native/vendor/quickjs-ng/tests/bug488-upstream.js +7 -0
  148. package/native/vendor/quickjs-ng/tests/bug633/0.js +7 -0
  149. package/native/vendor/quickjs-ng/tests/bug633/1.js +4 -0
  150. package/native/vendor/quickjs-ng/tests/bug633/2.js +4 -0
  151. package/native/vendor/quickjs-ng/tests/bug633/3.js +4 -0
  152. package/native/vendor/quickjs-ng/tests/bug645/0.js +4 -0
  153. package/native/vendor/quickjs-ng/tests/bug645/1.js +9 -0
  154. package/native/vendor/quickjs-ng/tests/bug645/2.js +7 -0
  155. package/native/vendor/quickjs-ng/tests/bug648.js +13 -0
  156. package/native/vendor/quickjs-ng/tests/bug652.js +4 -0
  157. package/native/vendor/quickjs-ng/tests/bug741.js +19 -0
  158. package/native/vendor/quickjs-ng/tests/bug775.js +7 -0
  159. package/native/vendor/quickjs-ng/tests/bug776.js +7 -0
  160. package/native/vendor/quickjs-ng/tests/bug832.js +2 -0
  161. package/native/vendor/quickjs-ng/tests/bug858.js +26 -0
  162. package/native/vendor/quickjs-ng/tests/bug904.js +6 -0
  163. package/native/vendor/quickjs-ng/tests/bug988.js +7 -0
  164. package/native/vendor/quickjs-ng/tests/bug999.js +3 -0
  165. package/native/vendor/quickjs-ng/tests/destructured-export.js +8 -0
  166. package/native/vendor/quickjs-ng/tests/detect_module/0.js +1 -0
  167. package/native/vendor/quickjs-ng/tests/detect_module/1.js +2 -0
  168. package/native/vendor/quickjs-ng/tests/detect_module/2.js +1 -0
  169. package/native/vendor/quickjs-ng/tests/detect_module/3.js +8 -0
  170. package/native/vendor/quickjs-ng/tests/detect_module/4.js +3 -0
  171. package/native/vendor/quickjs-ng/tests/empty.js +0 -0
  172. package/native/vendor/quickjs-ng/tests/fixture_cyclic_import.js +2 -0
  173. package/native/vendor/quickjs-ng/tests/fixture_string_exports.js +12 -0
  174. package/native/vendor/quickjs-ng/tests/function_source.js +14 -0
  175. package/native/vendor/quickjs-ng/tests/microbench.js +1267 -0
  176. package/native/vendor/quickjs-ng/tests/null_or_undefined.js +38 -0
  177. package/native/vendor/quickjs-ng/tests/str-pad-leak.js +5 -0
  178. package/native/vendor/quickjs-ng/tests/test_bigint.js +107 -0
  179. package/native/vendor/quickjs-ng/tests/test_bjson.js +366 -0
  180. package/native/vendor/quickjs-ng/tests/test_builtin.js +1314 -0
  181. package/native/vendor/quickjs-ng/tests/test_closure.js +220 -0
  182. package/native/vendor/quickjs-ng/tests/test_cyclic_import.js +12 -0
  183. package/native/vendor/quickjs-ng/tests/test_domexception.js +35 -0
  184. package/native/vendor/quickjs-ng/tests/test_language.js +755 -0
  185. package/native/vendor/quickjs-ng/tests/test_loop.js +367 -0
  186. package/native/vendor/quickjs-ng/tests/test_queue_microtask.js +39 -0
  187. package/native/vendor/quickjs-ng/tests/test_std.js +340 -0
  188. package/native/vendor/quickjs-ng/tests/test_string_exports.js +25 -0
  189. package/native/vendor/quickjs-ng/tests/test_worker.js +43 -0
  190. package/native/vendor/quickjs-ng/tests/test_worker_module.js +30 -0
  191. package/native/vendor/quickjs-ng/tests.conf +14 -0
  192. package/native/vendor/quickjs-ng/unicode_download.sh +19 -0
  193. package/native/vendor/quickjs-ng/unicode_gen.c +3108 -0
  194. package/native/vendor/quickjs-ng/unicode_gen_def.h +310 -0
  195. package/native/vendor/quickjs-ng/update-version.sh +32 -0
  196. package/native/vendor/webview2/include/WebView2.h +60636 -0
  197. package/native/vendor/webview2/include/WebView2EnvironmentOptions.h +406 -0
  198. package/package.json +33 -0
  199. package/src/backend.ts +139 -0
  200. package/src/build-config.ts +87 -0
  201. package/src/build.ts +276 -0
  202. package/src/common.ts +195 -0
  203. package/src/config.ts +89 -0
  204. package/src/dev.ts +164 -0
  205. package/src/generate.ts +200 -0
  206. package/src/icons.ts +116 -0
  207. package/src/init.ts +190 -0
  208. package/src/package.ts +150 -0
  209. package/src/zapp-cli.ts +263 -0
package/src/build.ts ADDED
@@ -0,0 +1,276 @@
1
+ import path from "node:path";
2
+ import process from "node:process";
3
+ import { existsSync } from "node:fs";
4
+ import { mkdir } from "node:fs/promises";
5
+ import { brotliCompressSync, constants as zlibConstants } from "node:zlib";
6
+ import { generateBuildConfigZc } from "./build-config";
7
+ import { buildFileNeedsQjs, ensureQjsLib, nativeIncludeArgs, preferredJsTool, resolveNativeDir, runCmd, runPackageScript } from "./common";
8
+ import { resolveAndBundleBackend } from "./backend";
9
+ import { runGenerate } from "./generate";
10
+ import type { ResolvedZappConfig } from "./config";
11
+
12
+ export const walkFiles = async (dir: string): Promise<string[]> => {
13
+ const glob = new Bun.Glob("**/*");
14
+ const files: string[] = [];
15
+ // Bun.Glob.scan() on Windows requires forward slashes in the cwd path
16
+ for await (const file of glob.scan({ cwd: dir.replace(/\\/g, "/"), absolute: true })) {
17
+ const stat = Bun.file(file);
18
+ if (stat.size > 0 || (await stat.exists())) {
19
+ files.push(file);
20
+ }
21
+ }
22
+ return files;
23
+ };
24
+
25
+ export const maybeBrotli = async (filePath: string): Promise<string> => {
26
+ const source = await Bun.file(filePath).arrayBuffer();
27
+ const compressed = brotliCompressSync(new Uint8Array(source), {
28
+ params: {
29
+ [zlibConstants.BROTLI_PARAM_QUALITY]: 11,
30
+ },
31
+ });
32
+ const outPath = `${filePath}.br`;
33
+ await Bun.write(outPath, compressed);
34
+ return outPath;
35
+ };
36
+
37
+ export const generateAssetsZc = async (root: string, manifest: any, assetDir: string) => {
38
+ let zcContent = `// AUTO-GENERATED FILE. DO NOT EDIT.
39
+
40
+ `;
41
+
42
+ // If no assets, generate empty placeholder
43
+ if (!manifest.assets || manifest.assets.length === 0) {
44
+ zcContent += `
45
+ raw {
46
+ struct ZappEmbeddedAsset zapp_embedded_assets[1];
47
+ int zapp_embedded_assets_count = 0;
48
+ }
49
+ `;
50
+ const buildDir = path.join(root, ".zapp");
51
+ await mkdir(buildDir, { recursive: true });
52
+ const outPath = path.join(buildDir, "zapp_assets.zc");
53
+ await Bun.write(outPath, zcContent);
54
+ return outPath;
55
+ }
56
+
57
+ const assetEntries = [];
58
+ const assetExterns = [];
59
+ for (let i = 0; i < manifest.assets.length; i++) {
60
+ const item = manifest.assets[i];
61
+ const isBrotli = item.brotli != null;
62
+ const filePath = isBrotli ? item.brotli.file : item.file;
63
+ const absPathToEmbed = path.join(assetDir, filePath).replace(/\\/g, "/");
64
+
65
+ zcContent += `let __zapp_asset_${i} = embed "${absPathToEmbed}" as u8[];\n`;
66
+
67
+ let logicalPath = "/" + item.file.replace(/\\/g, "/");
68
+ assetExterns.push(` extern Slice_uint8_t __zapp_asset_${i};`);
69
+ assetEntries.push(` zapp_embedded_assets[${i}].path = "${logicalPath}";`);
70
+ assetEntries.push(` zapp_embedded_assets[${i}].data = __zapp_asset_${i}.data;`);
71
+ assetEntries.push(` zapp_embedded_assets[${i}].len = __zapp_asset_${i}.len;`);
72
+ assetEntries.push(` zapp_embedded_assets[${i}].uncompressed_len = ${item.size};`);
73
+ assetEntries.push(` zapp_embedded_assets[${i}].is_brotli = ${isBrotli ? 'true' : 'false'};`);
74
+ }
75
+
76
+ zcContent += `
77
+ raw {
78
+ ${assetExterns.join("\n")}
79
+ struct ZappEmbeddedAsset zapp_embedded_assets[${manifest.assets.length || 1}];
80
+ int zapp_embedded_assets_count = ${manifest.assets.length};
81
+
82
+ __attribute__((constructor))
83
+ static void init_zapp_assets(void) {
84
+ ${assetEntries.join("\n")}
85
+ }
86
+ }
87
+ `;
88
+
89
+ const buildDir = path.join(root, ".zapp");
90
+ await mkdir(buildDir, { recursive: true });
91
+ const outPath = path.join(buildDir, "zapp_assets.zc");
92
+ await Bun.write(outPath, zcContent);
93
+ return outPath;
94
+ };
95
+
96
+ const collectWorkerStems = async (assetDir: string): Promise<Set<string>> => {
97
+ const stems = new Set<string>();
98
+ const manifestPath = path.join(assetDir, "zapp-workers", "manifest.json");
99
+ const manifestFile = Bun.file(manifestPath);
100
+ if (!(await manifestFile.exists())) return stems;
101
+ try {
102
+ const raw = await manifestFile.text();
103
+ const data = JSON.parse(raw);
104
+ const workers = data.workers ?? data;
105
+ for (const key of Object.keys(workers)) {
106
+ const stem = path.basename(key).replace(/\.[^.]+$/, "");
107
+ if (stem) stems.add(stem);
108
+ }
109
+ } catch { /* malformed manifest */ }
110
+ return stems;
111
+ };
112
+
113
+ export const buildAssetManifest = async ({
114
+ assetDir,
115
+ withBrotli,
116
+ }: {
117
+ assetDir: string;
118
+ withBrotli: boolean;
119
+ }) => {
120
+ const allFiles = await walkFiles(assetDir);
121
+ const workerStems = withBrotli ? await collectWorkerStems(assetDir) : new Set<string>();
122
+ const manifest = {
123
+ v: 1,
124
+ generatedAt: new Date().toISOString(),
125
+ assets: [] as any[],
126
+ embedded: true,
127
+ };
128
+
129
+ for (const file of allFiles) {
130
+ const rel = path.relative(assetDir, file).split(path.sep).join("/");
131
+ const stat = Bun.file(file);
132
+ const item: any = { file: rel, size: stat.size, brotli: null };
133
+
134
+ let skipBrotli = false;
135
+ if (rel.startsWith("zapp-workers/")) {
136
+ skipBrotli = true;
137
+ } else if (rel.startsWith("assets/") && workerStems.size > 0) {
138
+ const baseName = path.basename(rel).replace(/\.[^.]+$/, "");
139
+ for (const stem of workerStems) {
140
+ if (baseName === stem || baseName.startsWith(`${stem}-`)) {
141
+ skipBrotli = true;
142
+ break;
143
+ }
144
+ }
145
+ }
146
+
147
+ if (withBrotli && !skipBrotli) {
148
+ const brPath = await maybeBrotli(file);
149
+ const brStat = Bun.file(brPath);
150
+ item.brotli = { file: `${rel}.br`, size: brStat.size };
151
+ }
152
+ manifest.assets.push(item);
153
+ }
154
+
155
+ return manifest;
156
+ };
157
+
158
+ export const runBuild = async ({
159
+ root,
160
+ frontendDir,
161
+ buildFile,
162
+ nativeOut,
163
+ assetDir,
164
+ withBrotli,
165
+ embedAssets,
166
+ isDebug,
167
+ backendScript,
168
+ logLevel,
169
+ config,
170
+ }: {
171
+ root: string;
172
+ frontendDir: string;
173
+ buildFile: string;
174
+ nativeOut: string;
175
+ assetDir: string;
176
+ withBrotli: boolean;
177
+ embedAssets: boolean;
178
+ isDebug: boolean;
179
+ backendScript?: string;
180
+ logLevel?: "error" | "warn" | "info" | "debug" | "trace";
181
+ config: ResolvedZappConfig;
182
+ }) => {
183
+ if (withBrotli && !embedAssets) {
184
+ process.stdout.write("[zapp] note: --brotli has no effect without embedded assets\n");
185
+ }
186
+
187
+ await runGenerate({ root, frontendDir });
188
+
189
+ process.stdout.write(`[zapp] building frontend assets (${preferredJsTool()})\n`);
190
+ await runPackageScript("build", { cwd: frontendDir });
191
+
192
+ const manifest = embedAssets
193
+ ? await buildAssetManifest({ assetDir, withBrotli })
194
+ : { v: 1, generatedAt: new Date().toISOString(), assets: [] as any[], embedded: false };
195
+
196
+ if (embedAssets) {
197
+ const manifestPath = path.join(assetDir, "zapp-assets-manifest.json");
198
+ await Bun.write(manifestPath, JSON.stringify(manifest, null, 2));
199
+ }
200
+
201
+ const backendScriptPath = await resolveAndBundleBackend({ root, frontendDir, backendScript });
202
+ const buildMode = isDebug ? "prod" : (embedAssets ? "prod-embedded" : "prod");
203
+ const effectiveLogLevel = logLevel ?? (isDebug ? "debug" : "warn");
204
+ const buildConfigFile = await generateBuildConfigZc({
205
+ root,
206
+ mode: buildMode,
207
+ assetDir,
208
+ backendScriptPath,
209
+ logLevel: effectiveLogLevel,
210
+ csp: config.security?.csp,
211
+ enableDevTools: isDebug ? true : undefined, // --debug enables dev tools; otherwise inherit from mode
212
+ });
213
+
214
+ process.stdout.write("[zapp] building native binary\n");
215
+ await mkdir(path.dirname(nativeOut), { recursive: true });
216
+ const needsQjs = await buildFileNeedsQjs(buildFile);
217
+ const qjsLib = needsQjs ? await ensureQjsLib(root, isDebug ? "dev" : "release") : null;
218
+ const zcArgs = ["build", buildFile, buildConfigFile, ...nativeIncludeArgs()];
219
+
220
+ // Always generate assets file (provides empty placeholder when not embedding)
221
+ const assetsFile = await generateAssetsZc(root, manifest, assetDir);
222
+ if (await Bun.file(assetsFile).exists()) zcArgs.push(assetsFile);
223
+
224
+ // Add debug flags if isDebug, otherwise optimize for size
225
+ if (isDebug) {
226
+ zcArgs.push("--debug");
227
+ } else {
228
+ zcArgs.push("-Oz"); // Aggressive size optimization
229
+ zcArgs.push("-flto"); // Link-time optimization
230
+ zcArgs.push("--no-debug"); // No debug symbols / .dSYM
231
+ }
232
+
233
+ // On Windows, embed the application manifest for comctl32 v6 (TaskDialogIndirect, visual styles)
234
+ if (process.platform === "win32") {
235
+ const manifestSrc = path.join(root, "config", "windows", "app.manifest");
236
+ if (existsSync(manifestSrc)) {
237
+ const buildDir = path.join(root, ".zapp", "build");
238
+ await mkdir(buildDir, { recursive: true });
239
+ const rcPath = path.join(buildDir, "app.rc");
240
+ await Bun.write(rcPath, `1 24 "${manifestSrc.replace(/\\/g, "/")}"\n`);
241
+ const resPath = path.join(buildDir, "app_manifest.o");
242
+ await runCmd("windres", [rcPath, "-O", "coff", "-o", resPath]);
243
+ zcArgs.push(resPath);
244
+ }
245
+ }
246
+
247
+ zcArgs.push("-o", nativeOut);
248
+ if (qjsLib) {
249
+ zcArgs.push("-L", path.dirname(qjsLib), "-lqjs");
250
+ }
251
+ await runCmd("zc", zcArgs, { cwd: root, env: { ZAPP_NATIVE: resolveNativeDir() } });
252
+
253
+ // Strip symbols in release builds for smaller binary
254
+ if (!isDebug) {
255
+ try {
256
+ const stripPath = Bun.which("strip");
257
+ if (stripPath) {
258
+ await runCmd("strip", [nativeOut]);
259
+ }
260
+ } catch (e) {
261
+ process.stdout.write(`[zapp] warning: strip failed: ${e}\n`);
262
+ }
263
+ }
264
+
265
+ process.stdout.write(
266
+ [
267
+ "[zapp] build complete",
268
+ `native: ${nativeOut}`,
269
+ `mode: ${isDebug ? 'debug' : buildMode}`,
270
+ "",
271
+ "Run:",
272
+ `${nativeOut}`,
273
+ "",
274
+ ].join("\n"),
275
+ );
276
+ };
package/src/common.ts ADDED
@@ -0,0 +1,195 @@
1
+ import { spawn, spawnSync, execSync, type ChildProcess } from "node:child_process";
2
+ import path from "node:path";
3
+ import { existsSync, statSync } from "node:fs";
4
+ import { mkdir } from "node:fs/promises";
5
+ import process from "node:process";
6
+
7
+ export const sleep = (ms: number) => Bun.sleep(ms);
8
+
9
+ let _nativeDir: string | null = null;
10
+
11
+ export const resolveNativeDir = (): string => {
12
+ if (_nativeDir) return _nativeDir;
13
+
14
+ const cliSrcDir = import.meta.dir;
15
+ const cliRoot = path.dirname(cliSrcDir);
16
+
17
+ // Monorepo: packages/cli/../../ is the repo root with src/ and vendor/
18
+ const repoRoot = path.resolve(cliRoot, "../..");
19
+ if (existsSync(path.join(repoRoot, "src")) && existsSync(path.join(repoRoot, "vendor"))) {
20
+ _nativeDir = repoRoot;
21
+ return _nativeDir;
22
+ }
23
+
24
+ // Published/linked: packages/cli/native/ exists
25
+ const bundled = path.join(cliRoot, "native");
26
+ if (existsSync(path.join(bundled, "src")) && existsSync(path.join(bundled, "vendor"))) {
27
+ _nativeDir = bundled;
28
+ return _nativeDir;
29
+ }
30
+
31
+ throw new Error(
32
+ "[zapp] Cannot find native framework code. Expected either:\n" +
33
+ ` - ${repoRoot}/src (monorepo development)\n` +
34
+ ` - ${bundled}/src (bundled with CLI)\n`
35
+ );
36
+ };
37
+
38
+ export const nativeIncludeArgs = (): string[] => {
39
+ const nd = resolveNativeDir();
40
+ const args: string[] = [];
41
+
42
+ args.push("-I", path.join(nd, "src"));
43
+ args.push("-I", path.join(nd, "vendor", "quickjs-ng"));
44
+
45
+ if (process.platform === "win32") {
46
+ args.push("-I", path.join(nd, "vendor", "webview2", "include"));
47
+ }
48
+
49
+ return args;
50
+ };
51
+
52
+ /**
53
+ * Compile QuickJS source into a static library (.a) if needed.
54
+ * Returns the path to the library, or null if the build dir isn't ready.
55
+ * The library is cached in .zapp/qjs/ and only rebuilt when source changes.
56
+ */
57
+ export const ensureQjsLib = async (root: string, mode: "dev" | "release" = "release"): Promise<string> => {
58
+ const nd = resolveNativeDir();
59
+ const qjsVendor = path.join(nd, "vendor", "quickjs-ng");
60
+ const qjsBuildDir = path.join(root, ".zapp", "qjs", mode);
61
+ await mkdir(qjsBuildDir, { recursive: true });
62
+
63
+ const libPath = path.join(qjsBuildDir, "libqjs.a");
64
+ const sourceFiles = ["quickjs.c", "quickjs-libc.c", "dtoa.c", "libregexp.c", "libunicode.c"];
65
+
66
+ const libExists = existsSync(libPath);
67
+ let needsRebuild = !libExists;
68
+ if (libExists) {
69
+ const libMtime = statSync(libPath).mtimeMs;
70
+ for (const src of sourceFiles) {
71
+ const srcPath = path.join(qjsVendor, src);
72
+ if (existsSync(srcPath) && statSync(srcPath).mtimeMs > libMtime) {
73
+ needsRebuild = true;
74
+ break;
75
+ }
76
+ }
77
+ }
78
+
79
+ if (!needsRebuild) return libPath;
80
+
81
+ process.stdout.write(`[zapp] compiling QuickJS (${mode})...\n`);
82
+
83
+ const cc = process.platform === "win32" ? "gcc" : "cc";
84
+
85
+ const cflags: string[] = [];
86
+ if (mode === "release") {
87
+ cflags.push("-Oz", "-flto", "-c", `-I${qjsVendor}`);
88
+ } else {
89
+ cflags.push("-O2", "-c", `-I${qjsVendor}`);
90
+ }
91
+ if (process.platform === "win32") {
92
+ cflags.push("-DUNICODE", "-D_UNICODE");
93
+ }
94
+
95
+ const objectFiles: string[] = [];
96
+ for (const src of sourceFiles) {
97
+ const srcPath = path.join(qjsVendor, src);
98
+ const objPath = path.join(qjsBuildDir, src.replace(".c", ".o"));
99
+ objectFiles.push(objPath);
100
+ await runCmd(cc, [...cflags, srcPath, "-o", objPath]);
101
+ }
102
+
103
+ // Use gcc-ar on Windows to handle LTO objects (needs linker plugin)
104
+ const ar = process.platform === "win32" ? "gcc-ar" : "ar";
105
+ await runCmd(ar, ["rcs", libPath, ...objectFiles]);
106
+
107
+ return libPath;
108
+ };
109
+
110
+ /**
111
+ * Check if a build.zc file defines ZAPP_WORKER_ENGINE_QJS for the current platform.
112
+ */
113
+ export const buildFileNeedsQjs = async (buildFile: string): Promise<boolean> => {
114
+ const content = await Bun.file(buildFile).text();
115
+ const platformTag = process.platform === "darwin" ? "macos" : "windows";
116
+ // Match lines like: //> windows: define: ZAPP_WORKER_ENGINE_QJS
117
+ const pattern = new RegExp(`^\\s*//> ${platformTag}: define: ZAPP_WORKER_ENGINE_QJS`, "m");
118
+ return pattern.test(content);
119
+ };
120
+
121
+ let cachedExec: string | null = null;
122
+
123
+ export const preferredJsTool = (): string => {
124
+ if (cachedExec) return cachedExec;
125
+ if (Bun.which("bun")) {
126
+ cachedExec = "bun";
127
+ return cachedExec;
128
+ }
129
+ cachedExec = "npm";
130
+ return cachedExec;
131
+ };
132
+
133
+ export const runCmd = async (command: string, args: string[], options: any = {}) => {
134
+ const { $ } = require("bun");
135
+ const env = { ...process.env, ...(options.env ?? {}) };
136
+ const cwd = options.cwd ?? process.cwd();
137
+
138
+ if (command === "zc") {
139
+ console.error(`[debug] zc ${args.join(" ")}`);
140
+ await $`zc ${args}`.cwd(cwd).env(env);
141
+ return;
142
+ }
143
+
144
+ if (command === "bun") {
145
+ await $`bun ${args}`.cwd(cwd).env(env);
146
+ } else {
147
+ const cmdPath = Bun.which(command) || command;
148
+ const proc = Bun.spawn([cmdPath, ...args], {
149
+ cwd,
150
+ stdio: ["inherit", "inherit", "inherit"],
151
+ env,
152
+ });
153
+ const code = await proc.exited;
154
+ if (code !== 0) {
155
+ throw new Error(`${command} ${args.join(" ")} failed (${code})`);
156
+ }
157
+ }
158
+ };
159
+
160
+ export const spawnStreaming = (command: string, args: string[], options: any = {}) => {
161
+ const cmdPath = Bun.which(command) || command;
162
+ return Bun.spawn([cmdPath, ...args], {
163
+ cwd: options.cwd ?? process.cwd(),
164
+ stdio: ["inherit", "inherit", "inherit"],
165
+ env: { ...process.env, ...(options.env ?? {}) },
166
+ });
167
+ };
168
+
169
+ export const killChild = (child: import("bun").Subprocess | null | any) => {
170
+ if (!child || child.killed) return;
171
+ const pid = child.pid;
172
+ if (process.platform === "win32" && pid) {
173
+ try {
174
+ execSync(`taskkill /F /T /PID ${pid}`, { stdio: "ignore" });
175
+ return;
176
+ } catch { /* process may already be gone */ }
177
+ }
178
+ try {
179
+ child.kill("SIGTERM");
180
+ } catch {
181
+ try { child.kill(9); } catch { /* ignore */ }
182
+ }
183
+ };
184
+
185
+ export const runPackageScript = (script: string, options: any = {}) => {
186
+ const tool = preferredJsTool();
187
+ const args = tool === "bun" ? ["run", script] : ["run", script];
188
+ return runCmd(tool, args, options);
189
+ };
190
+
191
+ export const spawnPackageScript = (script: string, options: any = {}) => {
192
+ const tool = preferredJsTool();
193
+ const args = tool === "bun" ? ["run", script] : ["run", script];
194
+ return spawnStreaming(tool, args, options);
195
+ };
package/src/config.ts ADDED
@@ -0,0 +1,89 @@
1
+ import path from "node:path";
2
+
3
+ export interface MacOSConfig {
4
+ /** Minimum macOS version. Defaults to "13.0" */
5
+ minimumSystemVersion?: string;
6
+ /** App category for the Mac App Store (e.g. "public.app-category.developer-tools") */
7
+ category?: string;
8
+ /** Path to .icon folder for macOS Tahoe liquid glass icons (created with Icon Composer) */
9
+ iconLayers?: string;
10
+ }
11
+
12
+ export interface SecurityConfig {
13
+ /** Custom Content Security Policy. Default: strict self-only policy */
14
+ csp?: string;
15
+ /** Allowed navigation URLs (glob patterns). Default: only app scheme + dev server */
16
+ allowNavigation?: string[];
17
+ }
18
+
19
+ export interface ZappConfig {
20
+ /** App name -- used for binary output, bundle name, window title fallback */
21
+ name: string;
22
+ /** Reverse-domain identifier (e.g. "com.mycompany.myapp"). Defaults to "com.zapp.{name}" */
23
+ identifier?: string;
24
+ /** App version string. Defaults to "1.0.0" */
25
+ version?: string;
26
+ /** Path to app icon source (1024x1024 PNG). Used to generate .icns (macOS) and .ico (Windows). */
27
+ icon?: string;
28
+ /** App description */
29
+ description?: string;
30
+ /** Author name */
31
+ author?: string;
32
+ /** macOS-specific config */
33
+ macos?: MacOSConfig;
34
+ /** Security settings */
35
+ security?: SecurityConfig;
36
+ }
37
+
38
+ export interface ResolvedZappConfig {
39
+ name: string;
40
+ identifier: string;
41
+ version: string;
42
+ icon?: string;
43
+ description?: string;
44
+ author?: string;
45
+ macos?: MacOSConfig;
46
+ security?: SecurityConfig;
47
+ }
48
+
49
+ /** Passthrough helper that provides type inference and autocomplete for zapp.config.ts */
50
+ export function defineConfig(config: ZappConfig): ZappConfig {
51
+ return config;
52
+ }
53
+
54
+ function applyDefaults(config: ZappConfig): ResolvedZappConfig {
55
+ return {
56
+ name: config.name,
57
+ identifier: config.identifier ?? `com.zapp.${config.name}`,
58
+ version: config.version ?? "1.0.0",
59
+ icon: config.icon,
60
+ description: config.description,
61
+ author: config.author,
62
+ macos: config.macos,
63
+ security: config.security,
64
+ };
65
+ }
66
+
67
+ export async function loadConfig(root: string): Promise<ResolvedZappConfig> {
68
+ const configPath = path.join(root, "zapp", "zapp.config.ts");
69
+ const configFile = Bun.file(configPath);
70
+
71
+ if (await configFile.exists()) {
72
+ try {
73
+ const mod = await import(configPath);
74
+ const raw: ZappConfig = mod.default ?? mod;
75
+
76
+ if (!raw.name || typeof raw.name !== "string") {
77
+ throw new Error(`"name" is required in ${configPath} and must be a non-empty string`);
78
+ }
79
+
80
+ return applyDefaults(raw);
81
+ } catch (err: unknown) {
82
+ if (err instanceof Error && err.message.includes("name")) throw err;
83
+ throw new Error(`Failed to load ${configPath}: ${err instanceof Error ? err.message : String(err)}`);
84
+ }
85
+ }
86
+
87
+ const fallbackName = path.basename(root) || "zapp-app";
88
+ return applyDefaults({ name: fallbackName });
89
+ }