sparkbun 0.2.3 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sparkbun",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Build fast, lightweight, cross-platform desktop apps with TypeScript and Bun.",
5
5
  "license": "MIT",
6
6
  "author": "SparkBun Contributors",
@@ -4,6 +4,7 @@ import {
4
4
  type SparkBunRPCSchema,
5
5
  type SparkBunRPCConfig,
6
6
  type RPCWithTransport,
7
+ type AnyRPC,
7
8
  createRPC,
8
9
  defineSparkBunRPC,
9
10
  } from "../shared/rpc.js";
@@ -18,11 +19,11 @@ const WEBVIEW_ID = window.__sparkbunWebviewId;
18
19
  const HOST_SOCKET_PORT =
19
20
  window.__sparkbunHostSocketPort ?? window.__sparkbunRpcSocketPort;
20
21
 
21
- class Electroview<T extends RPCWithTransport> {
22
+ class Electroview<T extends RPCWithTransport = AnyRPC> {
22
23
  hostSocket?: WebSocket;
23
24
  hostSocketCanSend = false;
24
- // user's custom rpc browser <-> bun
25
- rpc?: T;
25
+ // user's custom rpc browser <-> bun — always provided via the constructor
26
+ rpc: T;
26
27
  rpcHandler?: (msg: unknown) => void;
27
28
  carrots = {
28
29
  invoke: <R = unknown>(
@@ -4,6 +4,7 @@ import {
4
4
  type SparkBunRPCSchema,
5
5
  type SparkBunRPCConfig,
6
6
  type RPCWithTransport,
7
+ type AnyRPC,
7
8
  defineSparkBunRPC,
8
9
  } from "../../shared/rpc.js";
9
10
  import { BuildConfig } from "./BuildConfig";
@@ -62,7 +63,7 @@ const defaultOptions: Partial<BrowserViewOptions> = {
62
63
  height: 600,
63
64
  },
64
65
  };
65
- export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
66
+ export class BrowserView<T extends RPCWithTransport = AnyRPC> {
66
67
  id = 0;
67
68
  hostWebviewId?: number;
68
69
  windowId!: number;
@@ -351,7 +352,7 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
351
352
 
352
353
  // Core can create webviews before Bun has constructed a JS wrapper for them.
353
354
  // Use this in native/runtime paths that need to ensure a wrapper exists.
354
- static ensureWrapped<T extends RPCWithTransport = RPCWithTransport>(
355
+ static ensureWrapped<T extends RPCWithTransport = AnyRPC>(
355
356
  id: number,
356
357
  options: Partial<BrowserViewOptions<T>> = {},
357
358
  ) {
@@ -361,7 +362,7 @@ export class BrowserView<T extends RPCWithTransport = RPCWithTransport> {
361
362
  );
362
363
  }
363
364
 
364
- static adoptExisting<T extends RPCWithTransport = RPCWithTransport>(
365
+ static adoptExisting<T extends RPCWithTransport = AnyRPC>(
365
366
  id: number,
366
367
  options: Partial<BrowserViewOptions<T>> = {},
367
368
  ) {
@@ -3,7 +3,7 @@ import sparkBunEventEmitter from "../events/eventEmitter";
3
3
  import { BrowserView } from "./BrowserView";
4
4
  import { type Pointer } from "bun:ffi";
5
5
  import { BuildConfig } from "./BuildConfig";
6
- import { type RPCWithTransport } from "../../shared/rpc.js";
6
+ import { type RPCWithTransport, type AnyRPC } from "../../shared/rpc.js";
7
7
  import { WGPUView } from "./WGPUView";
8
8
 
9
9
  const buildConfig = BuildConfig.getSync();
@@ -97,7 +97,7 @@ sparkBunEventEmitter.on("close", (event: { data: { id: number } }) => {
97
97
 
98
98
  });
99
99
 
100
- export class BrowserWindow<T extends RPCWithTransport = RPCWithTransport> {
100
+ export class BrowserWindow<T extends RPCWithTransport = AnyRPC> {
101
101
  id = 0;
102
102
  title: string = "SparkBun";
103
103
  state: "creating" | "created" = "creating";
@@ -632,7 +632,9 @@ const Updater = {
632
632
  emitStatus("decompressing", "Decompressing update bundle...");
633
633
  try {
634
634
  const compressed = readFileSync(prevVersionCompressedTarballPath);
635
- const decompressed = Bun.gunzipSync(compressed);
635
+ const decompressed = Bun.gunzipSync(
636
+ compressed as unknown as Uint8Array<ArrayBuffer>,
637
+ );
636
638
  writeFileSync(latestTarPath, decompressed);
637
639
  emitStatus("decompressing", "Decompression complete");
638
640
  } catch (err) {
@@ -0,0 +1,5 @@
1
+ // Type declarations for the build-generated ./compiled.ts (produced by build.ts;
2
+ // gitignored). This stub lets the runtime typecheck from a clean tree before the
3
+ // file is generated; the generated .ts supersedes it at build time.
4
+ export const preloadScript: string;
5
+ export const preloadScriptSandboxed: string;
@@ -2384,7 +2384,7 @@ export const Screen = {
2384
2384
  * @returns Display object for the primary monitor
2385
2385
  */
2386
2386
  getPrimaryDisplay: (): Display => {
2387
- const jsonStr = hasFFI ? core_.symbols.getPrimaryDisplay() : null;
2387
+ const jsonStr = core_.symbols.getPrimaryDisplay();
2388
2388
  if (!jsonStr) {
2389
2389
  return {
2390
2390
  id: 0,
@@ -2412,7 +2412,7 @@ export const Screen = {
2412
2412
  * @returns Array of Display objects
2413
2413
  */
2414
2414
  getAllDisplays: (): Display[] => {
2415
- const jsonStr = hasFFI ? core_.symbols.getAllDisplays() : null;
2415
+ const jsonStr = core_.symbols.getAllDisplays();
2416
2416
  if (!jsonStr) {
2417
2417
  return [];
2418
2418
  }
@@ -2428,7 +2428,7 @@ export const Screen = {
2428
2428
  * @returns Point with x and y coordinates
2429
2429
  */
2430
2430
  getCursorScreenPoint: (): Point => {
2431
- const jsonStr = hasFFI ? core_.symbols.getCursorScreenPoint() : null;
2431
+ const jsonStr = core_.symbols.getCursorScreenPoint();
2432
2432
  if (!jsonStr) {
2433
2433
  return { x: 0, y: 0 };
2434
2434
  }
@@ -2444,7 +2444,7 @@ export const Screen = {
2444
2444
  */
2445
2445
  getMouseButtons: (): bigint => {
2446
2446
  try {
2447
- return hasFFI ? core_.symbols.getMouseButtons() : BigInt(0);
2447
+ return core_.symbols.getMouseButtons();
2448
2448
  } catch {
2449
2449
  return 0n;
2450
2450
  }
package/src/cli/index.ts CHANGED
@@ -39,6 +39,14 @@ const _MAX_CHUNK_SIZE = 1024 * 2;
39
39
 
40
40
  // const binExt = OS === 'win' ? '.exe' : '';
41
41
 
42
+ // @types/node's Buffer is backed by ArrayBufferLike, which doesn't structurally
43
+ // match the Uint8Array<ArrayBuffer> that Bun APIs, fs writes, and
44
+ // Buffer.prototype.copy expect under this lib config (ArrayBufferLike admits
45
+ // SharedArrayBuffer). These buffers are always ArrayBuffer-backed at runtime;
46
+ // asU8 narrows the type without touching the value.
47
+ const asU8 = (b: Buffer | Uint8Array): Uint8Array<ArrayBuffer> =>
48
+ b as unknown as Uint8Array<ArrayBuffer>;
49
+
42
50
  // Create a tar file using system tar command (preserves file permissions unlike Bun.Archive)
43
51
  function createTar(tarPath: string, cwd: string, entries: string[]) {
44
52
  // Use a relative path for the tar output on Windows to avoid bsdtar
@@ -331,8 +339,6 @@ async function ensureCoreDependencies(
331
339
  // Verify extraction completed successfully - check platform-specific binaries only
332
340
  const requiredBinaries = [
333
341
  platformPaths.BUN_BINARY,
334
- platformPaths.BSDIFF,
335
- platformPaths.BSPATCH,
336
342
  ];
337
343
  if (platformOS === "macos") {
338
344
  requiredBinaries.push(
@@ -426,6 +432,7 @@ function getEffectiveWGPUDir(
426
432
  * Trims an ICU .dat file to only include the specified locales.
427
433
  * Uses icupkg (from ICU tools) to list and remove unwanted locale data.
428
434
  */
435
+ // @ts-expect-error - reserved for future use (wiring for the build.locales option)
429
436
  async function trimICUData(
430
437
  source: string,
431
438
  dest: string,
@@ -1194,6 +1201,8 @@ const defaultConfig = {
1194
1201
  identifier: "com.example.myapp",
1195
1202
  version: "0.1.0",
1196
1203
  description: "" as string | undefined,
1204
+ publisher: undefined as string | undefined,
1205
+ copyright: undefined as string | undefined,
1197
1206
  urlSchemes: undefined as string[] | undefined,
1198
1207
  fileAssociations: undefined as FileAssociation[] | undefined,
1199
1208
  },
@@ -1221,6 +1230,7 @@ const defaultConfig = {
1221
1230
  icons: "icon.iconset",
1222
1231
  defaultRenderer: undefined as "native" | "cef" | undefined,
1223
1232
  chromiumFlags: undefined as Record<string, string | boolean> | undefined,
1233
+ requireAdmin: undefined as boolean | undefined,
1224
1234
  },
1225
1235
  win: {
1226
1236
  bundleCEF: false,
@@ -1228,6 +1238,7 @@ const defaultConfig = {
1228
1238
  icon: undefined as string | undefined,
1229
1239
  defaultRenderer: undefined as "native" | "cef" | undefined,
1230
1240
  chromiumFlags: undefined as Record<string, string | boolean> | undefined,
1241
+ requireAdmin: undefined as boolean | undefined,
1231
1242
  },
1232
1243
  linux: {
1233
1244
  bundleCEF: false,
@@ -1235,6 +1246,8 @@ const defaultConfig = {
1235
1246
  icon: undefined as string | undefined,
1236
1247
  defaultRenderer: undefined as "native" | "cef" | undefined,
1237
1248
  chromiumFlags: undefined as Record<string, string | boolean> | undefined,
1249
+ requireAdmin: undefined as boolean | undefined,
1250
+ createDeb: undefined as boolean | undefined,
1238
1251
  },
1239
1252
  bun: {
1240
1253
  entrypoint: "src/bun/index.ts",
@@ -1301,13 +1314,13 @@ function escapeXml(str: string): string {
1301
1314
  }
1302
1315
 
1303
1316
  function patchPeSubsystem(exePath: string): void {
1304
- const buf = Buffer.from(readFileSync(exePath));
1317
+ const buf = readFileSync(exePath);
1305
1318
  const peOffset = buf.readUInt32LE(0x3c);
1306
1319
  const subsystemOffset = peOffset + 0x5c;
1307
1320
  const current = buf.readUInt16LE(subsystemOffset);
1308
1321
  if (current !== 2) {
1309
1322
  buf.writeUInt16LE(2, subsystemOffset);
1310
- writeFileSync(exePath, buf);
1323
+ writeFileSync(exePath, asU8(buf));
1311
1324
  console.log(`Patched PE subsystem: ${current} -> 2 (WINDOWS)`);
1312
1325
  }
1313
1326
  }
@@ -2961,7 +2974,7 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
2961
2974
 
2962
2975
  try {
2963
2976
  const compressed = readFileSync(prevVersionCompressedTarballPath);
2964
- const decompressed = Bun.gunzipSync(compressed);
2977
+ const decompressed = Bun.gunzipSync(asU8(compressed));
2965
2978
  writeFileSync(prevTarballPath, decompressed);
2966
2979
  } catch (err) {
2967
2980
  console.log(
@@ -3018,7 +3031,7 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
3018
3031
  {
3019
3032
  console.log("compressing tarball with gzip...");
3020
3033
  const tarBytes = readFileSync(tarPath);
3021
- const gzipped = Bun.gzipSync(tarBytes);
3034
+ const gzipped = Bun.gzipSync(asU8(tarBytes));
3022
3035
  writeFileSync(compressedTarPath, gzipped);
3023
3036
  artifactsToUpload.push(compressedTarPath);
3024
3037
  }
@@ -3333,7 +3346,12 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
3333
3346
  }
3334
3347
 
3335
3348
  const outPath = getFlag("--out=");
3336
- const iconPath = getFlag("--icon=");
3349
+ // Fall back to the platform icon from sparkbun.config.ts (build.win.icon)
3350
+ // so the installer is branded without needing an explicit --icon flag.
3351
+ const iconPath =
3352
+ getFlag("--icon=") ||
3353
+ (config.build as any)?.win?.icon ||
3354
+ undefined;
3337
3355
  const installerName = getFlag("--name=") || config.app.name;
3338
3356
  const installerVersion = getFlag("--version=") || config.app.version;
3339
3357
  const installerPublisher = getFlag("--publisher=") || (config.app as any).publisher || "";
@@ -3380,8 +3398,6 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
3380
3398
  mkdirSync(buildFolder, { recursive: true });
3381
3399
 
3382
3400
  const cliDir = dirname(decodeURIComponent(new URL(import.meta.url).pathname).replace(/^\/([A-Za-z]:)/, "$1"));
3383
- const sparkbunRoot = join(cliDir, "..", "..");
3384
- const bunTarget = `bun-${targetOS === "macos" ? "darwin" : targetOS === "win" ? "windows" : "linux"}-${targetARCH}` as const;
3385
3401
  const targetOSName = targetOS === "macos" ? "darwin" : targetOS === "win" ? "windows" : "linux";
3386
3402
  const isWindows = targetOS === "win";
3387
3403
 
@@ -3412,15 +3428,25 @@ usageDescriptions : ""}${urlTypes ? "\n" + urlTypes : ""}${documentTypes ?
3412
3428
 
3413
3429
  console.log(` Archiving ${spec.name}: ${spec.path}`);
3414
3430
 
3415
- // Build an uncompressed tar that preserves the source folder name as
3416
- // the archive root (e.g. "Dune 2000/..."), so the installer extracts
3417
- // into the correct subdirectory. tar -C <parent> <basename>.
3431
+ // Build the uncompressed tar with the system `tar`. It streams to disk
3432
+ // (bounded build memory) and preserves empty directories as real
3433
+ // entries. The source folder name is kept as the archive root (e.g.
3434
+ // "Dune 2000/...") via `tar -C <parent> <basename>`, so it extracts
3435
+ // into the correct subdirectory.
3436
+ //
3437
+ // The on-disk format may be GNU (Windows/Linux — long paths via
3438
+ // "././@LongLink") or pax (macOS bsdtar — long paths via extended
3439
+ // headers). The installer's streaming extractor understands both, plus
3440
+ // plain ustar, so the archive is portable regardless of build host.
3441
+ //
3442
+ // (Bun.Archive is intentionally not used here: its lazy Bun.file()
3443
+ // inputs write empty file bodies, and eager byte inputs would require
3444
+ // loading the entire payload into memory.)
3418
3445
  const parent = dirname(spec.path);
3419
3446
  const baseName = basename(spec.path);
3420
3447
  createTar(tarTmpPath, parent, [baseName]);
3421
3448
 
3422
- // Measure uncompressed bytes + file count so the installer can show
3423
- // real per-file / byte progress without unpacking first.
3449
+ // Measure uncompressed bytes + file count for progress denominators.
3424
3450
  const { bytes: uncompressedBytes, fileCount } = measureDir(spec.path);
3425
3451
 
3426
3452
  // Stream-compress the tar at the requested level (bounded build memory).
@@ -4240,10 +4266,6 @@ ${archiveExports.join("\n")}
4240
4266
  ...defaultConfig.build.bun,
4241
4267
  ...(loadedConfig?.build?.bun || {}),
4242
4268
  },
4243
- zig: {
4244
- ...defaultConfig.build.zig,
4245
- ...(loadedConfig?.build?.zig || {}),
4246
- },
4247
4269
  },
4248
4270
  runtime: {
4249
4271
  ...defaultConfig.runtime,
@@ -4394,12 +4416,12 @@ ${archiveExports.join("\n")}
4394
4416
  function createArHeader(name: string, size: number, mode: number = 0o100644): Buffer {
4395
4417
  const header = Buffer.alloc(60, 0x20); // fill with spaces
4396
4418
  const now = Math.floor(Date.now() / 1000).toString();
4397
- Buffer.from(name.padEnd(16, " ")).copy(header, 0); // filename
4398
- Buffer.from(now.padEnd(12, " ")).copy(header, 16); // timestamp
4399
- Buffer.from("0".padEnd(6, " ")).copy(header, 28); // owner
4400
- Buffer.from("0".padEnd(6, " ")).copy(header, 34); // group
4401
- Buffer.from(mode.toString(8).padEnd(8, " ")).copy(header, 40); // mode
4402
- Buffer.from(size.toString().padEnd(10, " ")).copy(header, 48); // size
4419
+ Buffer.from(name.padEnd(16, " ")).copy(asU8(header),0); // filename
4420
+ Buffer.from(now.padEnd(12, " ")).copy(asU8(header),16); // timestamp
4421
+ Buffer.from("0".padEnd(6, " ")).copy(asU8(header),28); // owner
4422
+ Buffer.from("0".padEnd(6, " ")).copy(asU8(header),34); // group
4423
+ Buffer.from(mode.toString(8).padEnd(8, " ")).copy(asU8(header),40); // mode
4424
+ Buffer.from(size.toString().padEnd(10, " ")).copy(asU8(header),48); // size
4403
4425
  header[58] = 0x60; // magic
4404
4426
  header[59] = 0x0A;
4405
4427
  return header;
@@ -4415,7 +4437,7 @@ ${archiveExports.join("\n")}
4415
4437
  parts.push(Buffer.from("\n"));
4416
4438
  }
4417
4439
  }
4418
- return Buffer.concat(parts);
4440
+ return Buffer.concat(parts.map(asU8));
4419
4441
  }
4420
4442
 
4421
4443
  function collectFiles(dir: string, prefix: string = ""): { path: string; fullPath: string; isDir: boolean }[] {
@@ -4436,38 +4458,38 @@ ${archiveExports.join("\n")}
4436
4458
 
4437
4459
  function writeTarHeader(header: Buffer, file: { path: string; fullPath: string; isDir: boolean }, content?: Buffer): void {
4438
4460
  const tarPath = file.path;
4439
- Buffer.from(tarPath).copy(header, 0, 0, Math.min(tarPath.length, 100));
4461
+ Buffer.from(tarPath).copy(asU8(header),0, 0, Math.min(tarPath.length, 100));
4440
4462
 
4441
4463
  // mtime from actual file
4442
4464
  const mtime = Math.floor(statSync(file.fullPath).mtimeMs / 1000);
4443
- Buffer.from(mtime.toString(8).padStart(11, "0") + "\0").copy(header, 136);
4465
+ Buffer.from(mtime.toString(8).padStart(11, "0") + "\0").copy(asU8(header),136);
4444
4466
 
4445
4467
  // uid/gid = 0 (root)
4446
- Buffer.from("0000000\0").copy(header, 108);
4447
- Buffer.from("0000000\0").copy(header, 116);
4468
+ Buffer.from("0000000\0").copy(asU8(header),108);
4469
+ Buffer.from("0000000\0").copy(asU8(header),116);
4448
4470
 
4449
4471
  // ustar magic
4450
- Buffer.from("ustar\0").copy(header, 257);
4451
- Buffer.from("00").copy(header, 263);
4452
- Buffer.from("root\0").copy(header, 265);
4453
- Buffer.from("root\0").copy(header, 297);
4472
+ Buffer.from("ustar\0").copy(asU8(header),257);
4473
+ Buffer.from("00").copy(asU8(header),263);
4474
+ Buffer.from("root\0").copy(asU8(header),265);
4475
+ Buffer.from("root\0").copy(asU8(header),297);
4454
4476
 
4455
4477
  if (file.isDir) {
4456
- Buffer.from("0040755\0").copy(header, 100);
4457
- Buffer.from("00000000000\0").copy(header, 124);
4478
+ Buffer.from("0040755\0").copy(asU8(header),100);
4479
+ Buffer.from("00000000000\0").copy(asU8(header),124);
4458
4480
  header[156] = 0x35; // '5'
4459
4481
  } else {
4460
4482
  const isExecutable = file.path.match(/\/bin\/|\/postinst$|\/preinst$|\/postrm$|\/prerm$/);
4461
- Buffer.from(isExecutable ? "0100755\0" : "0100644\0").copy(header, 100);
4462
- Buffer.from((content!.length).toString(8).padStart(11, "0") + "\0").copy(header, 124);
4483
+ Buffer.from(isExecutable ? "0100755\0" : "0100644\0").copy(asU8(header),100);
4484
+ Buffer.from((content!.length).toString(8).padStart(11, "0") + "\0").copy(asU8(header),124);
4463
4485
  header[156] = 0x30; // '0'
4464
4486
  }
4465
4487
 
4466
4488
  // checksum (must be computed last)
4467
- Buffer.from(" ").copy(header, 148);
4489
+ Buffer.from(" ").copy(asU8(header),148);
4468
4490
  let checksum = 0;
4469
- for (let i = 0; i < 512; i++) checksum += header[i];
4470
- Buffer.from(checksum.toString(8).padStart(6, "0") + "\0 ").copy(header, 148);
4491
+ for (let i = 0; i < 512; i++) checksum += header[i]!;
4492
+ Buffer.from(checksum.toString(8).padStart(6, "0") + "\0 ").copy(asU8(header),148);
4471
4493
  }
4472
4494
 
4473
4495
  function buildTarGz(baseDir: string, prefix: string = "./"): Buffer {
@@ -4492,8 +4514,8 @@ ${archiveExports.join("\n")}
4492
4514
  }
4493
4515
 
4494
4516
  blocks.push(Buffer.alloc(1024, 0));
4495
- const tarData = Buffer.concat(blocks);
4496
- return Buffer.from(Bun.gzipSync(tarData));
4517
+ const tarData = Buffer.concat(blocks.map(asU8));
4518
+ return Buffer.from(Bun.gzipSync(asU8(tarData)));
4497
4519
  }
4498
4520
 
4499
4521
  async function createDebPackage(
@@ -4501,7 +4523,7 @@ ${archiveExports.join("\n")}
4501
4523
  appBundleFolderPath: string,
4502
4524
  config: any,
4503
4525
  targetArch: string,
4504
- projectRoot: string,
4526
+ _projectRoot: string,
4505
4527
  ): Promise<string> {
4506
4528
  const debArch = targetArch === "arm64" ? "arm64" : "amd64";
4507
4529
  const appNameNoSpaces = config.app.name.replace(/ /g, "");
@@ -4552,19 +4574,19 @@ ${archiveExports.join("\n")}
4552
4574
  const wmClass = config.app.name.slice(0, oldWmName.length);
4553
4575
  if (existsSync(nativeLib)) {
4554
4576
  const newName = Buffer.alloc(oldWmName.length, 0);
4555
- Buffer.from(wmClass).copy(newName);
4577
+ Buffer.from(wmClass).copy(asU8(newName));
4556
4578
  const data = readFileSync(nativeLib);
4557
4579
  let patched = 0;
4558
4580
  let offset = 0;
4559
4581
  while (true) {
4560
- const idx = data.indexOf(oldWmName, offset);
4582
+ const idx = data.indexOf(asU8(oldWmName), offset);
4561
4583
  if (idx === -1) break;
4562
- newName.copy(data, idx);
4584
+ newName.copy(asU8(data), idx);
4563
4585
  offset = idx + oldWmName.length;
4564
4586
  patched++;
4565
4587
  }
4566
4588
  if (patched > 0) {
4567
- writeFileSync(nativeLib, data);
4589
+ writeFileSync(nativeLib, asU8(data));
4568
4590
  console.log(` Patched WM class in libNativeWrapper.so (${patched} occurrences)`);
4569
4591
  }
4570
4592
  }
@@ -4575,7 +4597,7 @@ ${archiveExports.join("\n")}
4575
4597
  if (existsSync(resourcesDir)) {
4576
4598
  const pngs = readdirSync(resourcesDir).filter((f: string) => f.endsWith(".png"));
4577
4599
  if (pngs.length > 0) {
4578
- const iconSrc = join(resourcesDir, pngs[0]);
4600
+ const iconSrc = join(resourcesDir, pngs[0]!);
4579
4601
  for (const size of ["256x256", "128x128"]) {
4580
4602
  const iconDir = join(dataDir, "usr", "share", "icons", "hicolor", size, "apps");
4581
4603
  mkdirSync(iconDir, { recursive: true });
@@ -4665,7 +4687,7 @@ update-desktop-database /usr/share/applications 2>/dev/null || true
4665
4687
 
4666
4688
  const debFileName = `${pkgName}_${version}_${debArch}.deb`;
4667
4689
  const debOutputPath = join(buildFolder, debFileName);
4668
- writeFileSync(debOutputPath, debData);
4690
+ writeFileSync(debOutputPath, asU8(debData));
4669
4691
 
4670
4692
  // Clean up
4671
4693
  rmSync(stagingDir, { recursive: true, force: true });
@@ -4760,7 +4782,6 @@ update-desktop-database /usr/share/applications 2>/dev/null || true
4760
4782
  }
4761
4783
 
4762
4784
  // Sign CEF helper apps (they're in the main Frameworks directory, not inside CEF framework)
4763
- const mainProcess = config.build.mainProcess ?? "bun";
4764
4785
  const cefHelperApps = getCEFHelperNames().map(
4765
4786
  (helperName) => `${helperName}.app`,
4766
4787
  );
@@ -0,0 +1,12 @@
1
+ // Type declarations for the build-generated ./embedded.ts (produced by build.ts
2
+ // from the templates/ directory; gitignored). This stub lets the CLI typecheck
3
+ // from a clean tree before the file is generated; the generated .ts supersedes
4
+ // it at build time.
5
+ export interface Template {
6
+ name: string;
7
+ files: Record<string, string>;
8
+ }
9
+
10
+ export const templates: Record<string, Template>;
11
+ export function getTemplateNames(): string[];
12
+ export function getTemplate(name: string): Template | undefined;
@@ -36,7 +36,7 @@ function elevateAndExit(): void {
36
36
  returns: "ptr",
37
37
  },
38
38
  });
39
- const encode = (s: string) => ptr(new Uint8Array(Buffer.from(s + "\0", "utf-16le")));
39
+ const encode = (s: string) => ptr(new Uint8Array(Buffer.from(s + "\0", "utf16le")));
40
40
  shell32.symbols.ShellExecuteW(
41
41
  null,
42
42
  encode("runas"),
@@ -4,18 +4,6 @@
4
4
 
5
5
  const MAGIC = "SBDIFF10";
6
6
 
7
- function offtin(buf: Uint8Array, offset: number): bigint {
8
- let y = 0n;
9
- for (let i = 0; i < 7; i++) {
10
- y |= BigInt(buf[offset + i]) << BigInt(i * 8);
11
- }
12
- y |= BigInt(buf[offset + 7] & 0x7f) << 56n;
13
- if (buf[offset + 7] & 0x80) {
14
- y = -y;
15
- }
16
- return y;
17
- }
18
-
19
7
  function offtout(x: bigint, buf: Uint8Array, offset: number): void {
20
8
  let y: bigint;
21
9
  if (x < 0n) {
@@ -47,24 +35,24 @@ function search(
47
35
  bestPos: { value: number },
48
36
  ): number {
49
37
  if (hi - lo < 2) {
50
- const loMatch = matchlen(oldData, suffixArray[lo], newData, newStart);
51
- const hiMatch = matchlen(oldData, suffixArray[hi], newData, newStart);
38
+ const loMatch = matchlen(oldData, suffixArray[lo]!, newData, newStart);
39
+ const hiMatch = matchlen(oldData, suffixArray[hi]!, newData, newStart);
52
40
  if (loMatch > hiMatch) {
53
- bestPos.value = suffixArray[lo];
41
+ bestPos.value = suffixArray[lo]!;
54
42
  return loMatch;
55
43
  }
56
- bestPos.value = suffixArray[hi];
44
+ bestPos.value = suffixArray[hi]!;
57
45
  return hiMatch;
58
46
  }
59
47
 
60
48
  const mid = lo + ((hi - lo) >>> 1);
61
- const midPos = suffixArray[mid];
49
+ const midPos = suffixArray[mid]!;
62
50
  const compareLen = Math.min(oldData.length - midPos, newData.length - newStart);
63
51
 
64
52
  let cmp = 0;
65
53
  for (let i = 0; i < compareLen; i++) {
66
- if (oldData[midPos + i] < newData[newStart + i]) { cmp = -1; break; }
67
- if (oldData[midPos + i] > newData[newStart + i]) { cmp = 1; break; }
54
+ if (oldData[midPos + i]! < newData[newStart + i]!) { cmp = -1; break; }
55
+ if (oldData[midPos + i]! > newData[newStart + i]!) { cmp = 1; break; }
68
56
  }
69
57
 
70
58
  if (cmp < 0) {
@@ -82,7 +70,7 @@ function qsufsort(data: Uint8Array): Int32Array {
82
70
  const lb = n - b;
83
71
  const len = la < lb ? la : lb;
84
72
  for (let i = 0; i < len; i++) {
85
- if (data[a + i] !== data[b + i]) return data[a + i] - data[b + i];
73
+ if (data[a + i] !== data[b + i]) return data[a + i]! - data[b + i]!;
86
74
  }
87
75
  return la - lb;
88
76
  });
@@ -175,12 +163,12 @@ export function createPatch(oldData: Uint8Array, newData: Uint8Array): Uint8Arra
175
163
  }
176
164
 
177
165
  for (let i = 0; i < lenf; i++) {
178
- diffBytes.push((newData[lastScan + i] - oldData[lastPos + i] + 256) & 0xff);
166
+ diffBytes.push((newData[lastScan + i]! - oldData[lastPos + i]! + 256) & 0xff);
179
167
  }
180
168
 
181
169
  const extraLen = (scan - lenb) - (lastScan + lenf);
182
170
  for (let i = 0; i < extraLen; i++) {
183
- extraBytes.push(newData[lastScan + lenf + i]);
171
+ extraBytes.push(newData[lastScan + lenf + i]!);
184
172
  }
185
173
 
186
174
  controlEntries.push(BigInt(lenf), BigInt(extraLen), BigInt((pos - lenb) - (lastPos + lenf)));
@@ -193,7 +181,7 @@ export function createPatch(oldData: Uint8Array, newData: Uint8Array): Uint8Arra
193
181
 
194
182
  const controlBuf = new Uint8Array(controlEntries.length * 8);
195
183
  for (let i = 0; i < controlEntries.length; i++) {
196
- offtout(controlEntries[i], controlBuf, i * 8);
184
+ offtout(controlEntries[i]!, controlBuf, i * 8);
197
185
  }
198
186
 
199
187
  const controlCompressed = Bun.gzipSync(controlBuf);
@@ -225,7 +213,7 @@ if (import.meta.main) {
225
213
  console.error("Usage: bsdiff <oldfile> <newfile> <patchfile>");
226
214
  process.exit(1);
227
215
  }
228
- const [oldPath, newPath, patchPath] = args;
216
+ const [oldPath, newPath, patchPath] = args as [string, string, string];
229
217
  const oldData = new Uint8Array(await Bun.file(oldPath).arrayBuffer());
230
218
  const newData = new Uint8Array(await Bun.file(newPath).arrayBuffer());
231
219
  const patch = createPatch(oldData, newData);
@@ -7,10 +7,10 @@ const MAGIC = "SBDIFF10";
7
7
  function offtin(buf: Uint8Array, offset: number): bigint {
8
8
  let y = 0n;
9
9
  for (let i = 0; i < 7; i++) {
10
- y |= BigInt(buf[offset + i]) << BigInt(i * 8);
10
+ y |= BigInt(buf[offset + i]!) << BigInt(i * 8);
11
11
  }
12
- y |= BigInt(buf[offset + 7] & 0x7f) << 56n;
13
- if (buf[offset + 7] & 0x80) {
12
+ y |= BigInt(buf[offset + 7]! & 0x7f) << 56n;
13
+ if (buf[offset + 7]! & 0x80) {
14
14
  y = -y;
15
15
  }
16
16
  return y;
@@ -64,7 +64,7 @@ export function applyPatch(oldData: Uint8Array, patchData: Uint8Array): Uint8Arr
64
64
  }
65
65
 
66
66
  for (let i = 0; i < readDiffBy; i++) {
67
- newData[newPos + i] = (oldData[oldPos + i] + diffBlock[diffPos + i]) & 0xff;
67
+ newData[newPos + i] = (oldData[oldPos + i]! + diffBlock[diffPos + i]!) & 0xff;
68
68
  }
69
69
  diffPos += readDiffBy;
70
70
  newPos += readDiffBy;
@@ -88,7 +88,7 @@ if (import.meta.main) {
88
88
  console.error("Usage: bspatch <oldfile> <newfile> <patchfile>");
89
89
  process.exit(1);
90
90
  }
91
- const [oldPath, newPath, patchPath] = args;
91
+ const [oldPath, newPath, patchPath] = args as [string, string, string];
92
92
  const oldData = new Uint8Array(await Bun.file(oldPath).arrayBuffer());
93
93
  const patchData = new Uint8Array(await Bun.file(patchPath).arrayBuffer());
94
94
  const newData = applyPatch(oldData, patchData);
package/src/shared/rpc.ts CHANGED
@@ -461,6 +461,32 @@ export interface RPCWithTransport {
461
461
  setTransport: (transport: RPCTransport) => void;
462
462
  }
463
463
 
464
+ /**
465
+ * Permissive RPC surface used as the *default* type argument for BrowserView,
466
+ * BrowserWindow and Electroview when the caller does not parameterize them with
467
+ * a concrete schema (e.g. `let win: BrowserWindow`). It exposes the send/request
468
+ * proxies with loose typing so member access type-checks without casts, while a
469
+ * concrete `BrowserWindow<typeof myRpc>` still gets fully-typed methods. It
470
+ * extends RPCWithTransport, so real RPC objects (the return of defineRPC) remain
471
+ * assignable to the `T extends RPCWithTransport` constraint.
472
+ */
473
+ export interface AnyRPC extends RPCWithTransport {
474
+ // Members are intentionally `any`: this is the permissive *default* type
475
+ // argument, so member access (rpc.send.foo(), rpc.request.bar()) type-checks
476
+ // without casts. Crucially, the concrete RPC object returned by defineRPC
477
+ // (with fully-typed send/request) stays assignable to AnyRPC — so a typed
478
+ // `new BrowserWindow({ rpc })` is assignable to a bare `BrowserWindow`
479
+ // annotation. Parameterize explicitly (BrowserWindow<typeof rpc>) for full
480
+ // per-method typing.
481
+ send: any;
482
+ request: any;
483
+ sendProxy: any;
484
+ requestProxy: any;
485
+ addMessageListener: any;
486
+ removeMessageListener: any;
487
+ proxy: any;
488
+ }
489
+
464
490
  export type SparkBunRPCConfig<
465
491
  Schema extends SparkBunRPCSchema,
466
492
  Side extends "bun" | "webview",
package/tsconfig.json CHANGED
@@ -27,5 +27,8 @@
27
27
  "noUncheckedIndexedAccess": true
28
28
  },
29
29
  "include": ["src/**/*"],
30
- "exclude": ["node_modules", "dist", "vendors", "build", "src/tests"]
30
+ // src/installer/* are standalone templates compiled by the CLI in a staging
31
+ // dir alongside generated siblings (app-archive.tar.gz, embedded-archives,
32
+ // metadata.json); they cannot resolve in the library's own compilation.
33
+ "exclude": ["node_modules", "dist", "vendors", "build", "src/tests", "src/installer"]
31
34
  }