craft-native 0.0.185 → 0.0.187

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/dist/index.cjs CHANGED
@@ -269,9 +269,8 @@ __export(exports_src, {
269
269
  });
270
270
  module.exports = __toCommonJS(exports_src);
271
271
  var import_node_child_process = require("node:child_process");
272
- var import_node_fs2 = require("node:fs");
273
- var import_node_os = require("node:os");
274
- var import_node_path = require("node:path");
272
+ var import_node_fs3 = require("node:fs");
273
+ var import_node_path2 = require("node:path");
275
274
  var import_node_util = require("node:util");
276
275
 
277
276
  // src/binary-resolver.ts
@@ -310,11 +309,125 @@ function craftBinaryNotFoundMessage(triedPath) {
310
309
  `);
311
310
  }
312
311
  // package.json
313
- var version = "0.0.185";
312
+ var version = "0.0.187";
314
313
 
315
314
  // src/sdk-version.ts
316
315
  var SDK_VERSION = version;
317
316
 
317
+ // src/temp-dir.ts
318
+ var import_node_fs2 = require("node:fs");
319
+ var import_node_os = require("node:os");
320
+ var import_node_path = require("node:path");
321
+ var OWNED_NAME = /^craft-(?:.+-)?p(\d+)-[A-Za-z0-9]{6}$/;
322
+ var KEEP_MARKER = ".craft-keep";
323
+ var SWEEP_MIN_AGE_MS = 60000;
324
+ var SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"];
325
+ var live = new Set;
326
+ var hooked = false;
327
+ var swept = false;
328
+ function removeQuietly(dir) {
329
+ try {
330
+ import_node_fs2.rmSync(dir, { recursive: true, force: true });
331
+ } catch {}
332
+ }
333
+ function removeAllTempDirs() {
334
+ for (const dir of live)
335
+ removeQuietly(dir);
336
+ live.clear();
337
+ unhook();
338
+ }
339
+ function onExit() {
340
+ for (const dir of live)
341
+ removeQuietly(dir);
342
+ live.clear();
343
+ }
344
+ function onSignal(signal) {
345
+ removeAllTempDirs();
346
+ if (process.listenerCount(signal) === 0)
347
+ process.exit(128 + (import_node_os.constants.signals[signal] ?? 1));
348
+ }
349
+ var signalHandlers = new Map(SIGNALS.map((signal) => [signal, () => onSignal(signal)]));
350
+ function hook() {
351
+ if (hooked)
352
+ return;
353
+ hooked = true;
354
+ process.on("exit", onExit);
355
+ for (const [signal, handler] of signalHandlers) {
356
+ try {
357
+ process.on(signal, handler);
358
+ } catch {}
359
+ }
360
+ }
361
+ function unhook() {
362
+ if (!hooked)
363
+ return;
364
+ hooked = false;
365
+ process.off("exit", onExit);
366
+ for (const [signal, handler] of signalHandlers)
367
+ process.off(signal, handler);
368
+ }
369
+ function pidIsAlive(pid) {
370
+ try {
371
+ process.kill(pid, 0);
372
+ return true;
373
+ } catch (error) {
374
+ return error.code === "EPERM";
375
+ }
376
+ }
377
+ function sweepOrphanedTempDirs(options = {}) {
378
+ const root = options.root ?? import_node_os.tmpdir();
379
+ const isAlive = options.isAlive ?? pidIsAlive;
380
+ const minAge = options.minAgeMs ?? SWEEP_MIN_AGE_MS;
381
+ const now = options.now ?? Date.now();
382
+ const removed = [];
383
+ let names;
384
+ try {
385
+ names = import_node_fs2.readdirSync(root);
386
+ } catch {
387
+ return removed;
388
+ }
389
+ for (const name of names) {
390
+ const match = OWNED_NAME.exec(name);
391
+ if (!match)
392
+ continue;
393
+ const pid = Number(match[1]);
394
+ if (pid === process.pid || isAlive(pid))
395
+ continue;
396
+ const path = import_node_path.join(root, name);
397
+ try {
398
+ if (now - import_node_fs2.statSync(path).mtimeMs < minAge)
399
+ continue;
400
+ if (import_node_fs2.existsSync(import_node_path.join(path, KEEP_MARKER)))
401
+ continue;
402
+ } catch {
403
+ continue;
404
+ }
405
+ removeQuietly(path);
406
+ removed.push(path);
407
+ }
408
+ return removed;
409
+ }
410
+ function tempDirPrefix(label) {
411
+ const bare = label.replace(/^craft-/, "").replace(/-+$/, "");
412
+ return bare ? `craft-${bare}-p${process.pid}-` : `craft-p${process.pid}-`;
413
+ }
414
+ function createTempDir(label, root = import_node_os.tmpdir()) {
415
+ if (!swept) {
416
+ swept = true;
417
+ sweepOrphanedTempDirs({ root });
418
+ }
419
+ hook();
420
+ const dir = import_node_fs2.mkdtempSync(import_node_path.join(root, tempDirPrefix(label)));
421
+ live.add(dir);
422
+ return dir;
423
+ }
424
+ function removeTempDir(dir) {
425
+ removeQuietly(dir);
426
+ live.delete(dir);
427
+ if (live.size === 0)
428
+ unhook();
429
+ }
430
+
318
431
  // src/package.ts
319
432
  var import_child_process = require("child_process");
320
433
  var import_crypto = require("crypto");
@@ -935,7 +1048,7 @@ async function createPKG(opts) {
935
1048
  }));
936
1049
  return built.success ? { success: true, outputPath: opts.outputPath } : { success: false, error: built.error };
937
1050
  }
938
- const tempDir = import_fs.mkdtempSync(import_path.join(import_os.tmpdir(), "craft-pkg-"));
1051
+ const tempDir = createTempDir("craft-pkg-");
939
1052
  try {
940
1053
  const root = import_path.join(tempDir, "root");
941
1054
  const appsDir = import_path.join(root, "Applications");
@@ -955,7 +1068,7 @@ async function createPKG(opts) {
955
1068
  }));
956
1069
  return built.success ? { success: true, outputPath: opts.outputPath } : { success: false, error: built.error };
957
1070
  } finally {
958
- import_fs.rmSync(tempDir, { recursive: true, force: true });
1071
+ removeTempDir(tempDir);
959
1072
  }
960
1073
  }
961
1074
  function xml(value) {
@@ -1075,7 +1188,7 @@ function runCommand(command, args, cwd) {
1075
1188
  });
1076
1189
  }
1077
1190
  async function createMSI(opts) {
1078
- const tempDir = import_fs.mkdtempSync(import_path.join(import_os.tmpdir(), "craft-msi-"));
1191
+ const tempDir = createTempDir("craft-msi-");
1079
1192
  try {
1080
1193
  const binaryName = windowsExecutableName(opts.name);
1081
1194
  const additionalFiles = windowsCompanionFiles(opts.additionalFiles, binaryName);
@@ -1103,7 +1216,7 @@ async function createMSI(opts) {
1103
1216
  const missingTool = /ENOENT|not found/i.test(message);
1104
1217
  return { success: false, error: missingTool ? `WiX Toolset not found: ${message}` : message };
1105
1218
  } finally {
1106
- import_fs.rmSync(tempDir, { recursive: true, force: true });
1219
+ removeTempDir(tempDir);
1107
1220
  }
1108
1221
  }
1109
1222
  async function createZIP(opts) {
@@ -1123,6 +1236,7 @@ async function createZIP(opts) {
1123
1236
  }
1124
1237
  async function createDEB(opts) {
1125
1238
  return new Promise((resolve) => {
1239
+ let staging;
1126
1240
  try {
1127
1241
  const sanitizedName = opts.name.toLowerCase();
1128
1242
  const safeDebName = /^[a-z0-9._+-]+$/;
@@ -1145,7 +1259,8 @@ async function createDEB(opts) {
1145
1259
  }
1146
1260
  }
1147
1261
  const desktopContent = linuxDesktopEntry(opts.name, sanitizedName, opts.urlSchemes, opts.launchUrl);
1148
- const tempDir = import_fs.mkdtempSync(import_path.join(import_os.tmpdir(), "craft-deb-"));
1262
+ const tempDir = createTempDir("craft-deb-");
1263
+ staging = tempDir;
1149
1264
  const debianDir = import_path.join(tempDir, "DEBIAN");
1150
1265
  const binDir = import_path.join(tempDir, "usr", "bin");
1151
1266
  const applicationsDir = import_path.join(tempDir, "usr", "share", "applications");
@@ -1168,7 +1283,7 @@ Description: ${description || opts.name}
1168
1283
  import_fs.writeFileSync(import_path.join(applicationsDir, `${binaryName}.desktop`), desktopContent);
1169
1284
  const proc = import_child_process.spawn("dpkg-deb", ["--build", tempDir, opts.outputPath]);
1170
1285
  proc.on("close", (code) => {
1171
- import_fs.rmSync(tempDir, { recursive: true, force: true });
1286
+ removeTempDir(tempDir);
1172
1287
  if (code === 0) {
1173
1288
  resolve({ success: true, outputPath: opts.outputPath });
1174
1289
  } else {
@@ -1176,10 +1291,12 @@ Description: ${description || opts.name}
1176
1291
  }
1177
1292
  });
1178
1293
  proc.on("error", (err) => {
1179
- import_fs.rmSync(tempDir, { recursive: true, force: true });
1294
+ removeTempDir(tempDir);
1180
1295
  resolve({ success: false, error: err.message });
1181
1296
  });
1182
1297
  } catch (error) {
1298
+ if (staging)
1299
+ removeTempDir(staging);
1183
1300
  resolve({ success: false, error: error.message });
1184
1301
  }
1185
1302
  });
@@ -1260,8 +1377,10 @@ install -m 755 %{SOURCE0} %{buildroot}/usr/bin/${sanitizedName}
1260
1377
  }
1261
1378
  async function createAppImage(opts) {
1262
1379
  return new Promise((resolve) => {
1380
+ let staging;
1263
1381
  try {
1264
- const appDir = import_fs.mkdtempSync(import_path.join(import_os.tmpdir(), "craft-appimage-"));
1382
+ const appDir = createTempDir("craft-appimage-");
1383
+ staging = appDir;
1265
1384
  const appDirPath = import_path.join(appDir, `${opts.name}.AppDir`);
1266
1385
  const binDir = import_path.join(appDirPath, "usr", "bin");
1267
1386
  const shareDir = import_path.join(appDirPath, "usr", "share");
@@ -1367,7 +1486,7 @@ Icon=${binaryName}
1367
1486
  env: { ...process.env, ARCH: "x86_64" }
1368
1487
  });
1369
1488
  proc.on("close", (code) => {
1370
- import_fs.rmSync(appDir, { recursive: true, force: true });
1489
+ removeTempDir(appDir);
1371
1490
  if (code === 0) {
1372
1491
  import_fs.chmodSync(opts.outputPath, 493);
1373
1492
  resolve({ success: true, outputPath: opts.outputPath });
@@ -1376,10 +1495,12 @@ Icon=${binaryName}
1376
1495
  }
1377
1496
  });
1378
1497
  proc.on("error", (err) => {
1379
- import_fs.rmSync(appDir, { recursive: true, force: true });
1498
+ removeTempDir(appDir);
1380
1499
  resolve({ success: false, error: `appimagetool not found: ${err.message}` });
1381
1500
  });
1382
1501
  } catch (error) {
1502
+ if (staging)
1503
+ removeTempDir(staging);
1383
1504
  resolve({ success: false, error: error.message });
1384
1505
  }
1385
1506
  });
@@ -10238,7 +10359,7 @@ __export(exports_updater, {
10238
10359
  });
10239
10360
  var import_fs4 = require("fs");
10240
10361
  var import_path3 = require("path");
10241
- var import_os3 = require("os");
10362
+ var import_os2 = require("os");
10242
10363
  var import_child_process3 = require("child_process");
10243
10364
  var import_crypto3 = require("crypto");
10244
10365
  var import_events3 = require("events");
@@ -10246,7 +10367,6 @@ var import_events3 = require("events");
10246
10367
  // src/updater/macos-bundle.ts
10247
10368
  var import_child_process2 = require("child_process");
10248
10369
  var import_fs3 = require("fs");
10249
- var import_os2 = require("os");
10250
10370
  var import_path2 = require("path");
10251
10371
  var import_util = require("util");
10252
10372
  var execFileAsync = import_util.promisify(import_child_process2.execFile);
@@ -10344,9 +10464,10 @@ function findTopLevelApp(root) {
10344
10464
  return parts[0] ?? null;
10345
10465
  }
10346
10466
  async function extractBundleFromDmg(dmgPath, stagingDir) {
10347
- const staging = stagingDir ?? import_fs3.mkdtempSync(import_path2.join(import_os2.tmpdir(), "craft-update-"));
10348
- const mountPoint = import_fs3.mkdtempSync(import_path2.join(import_os2.tmpdir(), "craft-mount-"));
10467
+ const staging = stagingDir ?? createTempDir("craft-update-");
10468
+ const mountPoint = createTempDir("craft-mount-");
10349
10469
  let mounted = false;
10470
+ let staged = false;
10350
10471
  try {
10351
10472
  await execFileAsync("hdiutil", ["attach", dmgPath, "-nobrowse", "-readonly", "-mountpoint", mountPoint]);
10352
10473
  mounted = true;
@@ -10355,6 +10476,7 @@ async function extractBundleFromDmg(dmgPath, stagingDir) {
10355
10476
  throw new Error(`No .app found in disk image: ${dmgPath}`);
10356
10477
  const appPath = import_path2.join(staging, import_path2.basename(source));
10357
10478
  await dittoBundle(source, appPath);
10479
+ staged = true;
10358
10480
  return { appPath, stagingDir: staging };
10359
10481
  } finally {
10360
10482
  if (mounted) {
@@ -10362,19 +10484,25 @@ async function extractBundleFromDmg(dmgPath, stagingDir) {
10362
10484
  await execFileAsync("hdiutil", ["detach", mountPoint, "-force"]);
10363
10485
  } catch {}
10364
10486
  }
10365
- try {
10366
- import_fs3.rmSync(mountPoint, { recursive: true, force: true });
10367
- } catch {}
10487
+ removeTempDir(mountPoint);
10488
+ if (!staged && !stagingDir)
10489
+ removeTempDir(staging);
10368
10490
  }
10369
10491
  }
10370
10492
  async function extractBundleFromZip(zipPath, stagingDir) {
10371
- const staging = stagingDir ?? import_fs3.mkdtempSync(import_path2.join(import_os2.tmpdir(), "craft-update-"));
10493
+ const staging = stagingDir ?? createTempDir("craft-update-");
10372
10494
  const unpacked = import_path2.join(staging, "payload");
10373
- await execFileAsync("ditto", ["-x", "-k", zipPath, unpacked]);
10374
- const source = findTopLevelApp(unpacked);
10375
- if (!source)
10376
- throw new Error(`No .app found in archive: ${zipPath}`);
10377
- return { appPath: source, stagingDir: staging };
10495
+ try {
10496
+ await execFileAsync("ditto", ["-x", "-k", zipPath, unpacked]);
10497
+ const source = findTopLevelApp(unpacked);
10498
+ if (!source)
10499
+ throw new Error(`No .app found in archive: ${zipPath}`);
10500
+ return { appPath: source, stagingDir: staging };
10501
+ } catch (error) {
10502
+ if (!stagingDir)
10503
+ removeTempDir(staging);
10504
+ throw error;
10505
+ }
10378
10506
  }
10379
10507
  async function extractBundle(archivePath, stagingDir) {
10380
10508
  if (archivePath.endsWith(".dmg"))
@@ -10469,7 +10597,7 @@ class AutoUpdater extends import_events3.EventEmitter {
10469
10597
  downloadDir() {
10470
10598
  if (this.config.downloadDir)
10471
10599
  return this.config.downloadDir;
10472
- return import_path3.join(import_os3.tmpdir(), "craft-updates", import_path3.basename(this.config.appPath) || "app");
10600
+ return import_path3.join(import_os2.tmpdir(), "craft-updates", import_path3.basename(this.config.appPath) || "app");
10473
10601
  }
10474
10602
  getLastError() {
10475
10603
  return this.lastError;
@@ -10759,9 +10887,7 @@ class AutoUpdater extends import_events3.EventEmitter {
10759
10887
  leftoverPath: previousPath
10760
10888
  });
10761
10889
  } finally {
10762
- try {
10763
- import_fs4.rmSync(staged.stagingDir, { recursive: true, force: true });
10764
- } catch {}
10890
+ removeTempDir(staged.stagingDir);
10765
10891
  }
10766
10892
  }
10767
10893
  async assertBundleTrusted(bundlePath) {
@@ -11132,6 +11258,7 @@ function detectDevMode() {
11132
11258
  class CraftApp {
11133
11259
  process;
11134
11260
  config;
11261
+ sidebarConfigDir;
11135
11262
  constructor(config = {}) {
11136
11263
  if (config.window?.width !== undefined && (config.window.width < 100 || config.window.width > 1e4)) {
11137
11264
  throw new Error(`Invalid window width: ${config.window.width}. Must be between 100 and 10000 pixels.`);
@@ -11168,8 +11295,15 @@ class CraftApp {
11168
11295
  throw new Error(`No content to display. Provide either html or url:
11169
11296
  ` + " show(html, options) or loadURL(url, options)");
11170
11297
  }
11298
+ this.removeSidebarConfig();
11171
11299
  const args = this.buildArgs();
11172
- const craftPath = await this.findCraftBinary();
11300
+ let craftPath;
11301
+ try {
11302
+ craftPath = await this.findCraftBinary();
11303
+ } catch (error) {
11304
+ this.removeSidebarConfig();
11305
+ throw error;
11306
+ }
11173
11307
  probeBinaryVersion(craftPath);
11174
11308
  const quiet = this.config.quiet === true;
11175
11309
  this.process = import_node_child_process.spawn(craftPath, args, {
@@ -11182,6 +11316,7 @@ class CraftApp {
11182
11316
  });
11183
11317
  return new Promise((resolve, reject) => {
11184
11318
  this.process?.on("error", (error) => {
11319
+ this.removeSidebarConfig();
11185
11320
  if (error.code === "ENOENT") {
11186
11321
  reject(new Error(craftBinaryNotFoundMessage(craftPath)));
11187
11322
  return;
@@ -11199,6 +11334,7 @@ class CraftApp {
11199
11334
  ` + " https://github.com/home-lang/craft/issues"));
11200
11335
  });
11201
11336
  this.process?.on("close", (code) => {
11337
+ this.removeSidebarConfig();
11202
11338
  if (code === 0 || code === null) {
11203
11339
  resolve();
11204
11340
  } else {
@@ -11216,6 +11352,12 @@ class CraftApp {
11216
11352
  this.process.kill();
11217
11353
  }
11218
11354
  }
11355
+ removeSidebarConfig() {
11356
+ if (!this.sidebarConfigDir)
11357
+ return;
11358
+ removeTempDir(this.sidebarConfigDir);
11359
+ this.sidebarConfigDir = undefined;
11360
+ }
11219
11361
  buildArgs() {
11220
11362
  const args = [];
11221
11363
  const { window: window3, html, url, appName } = this.config;
@@ -11314,9 +11456,10 @@ class CraftApp {
11314
11456
  if (safeForArgv) {
11315
11457
  args.push("--sidebar-config", json);
11316
11458
  } else {
11317
- const dir = import_node_fs2.mkdtempSync(import_node_path.join(import_node_os.tmpdir(), "craft-sidebar-"));
11318
- const path = import_node_path.join(dir, "sidebar-config.json");
11319
- import_node_fs2.writeFileSync(path, json, "utf-8");
11459
+ const dir = createTempDir("craft-sidebar-");
11460
+ this.sidebarConfigDir = dir;
11461
+ const path = import_node_path2.join(dir, "sidebar-config.json");
11462
+ import_node_fs3.writeFileSync(path, json, "utf-8");
11320
11463
  args.push("--sidebar-config-file", path);
11321
11464
  }
11322
11465
  }
package/dist/index.d.cts CHANGED
@@ -42,6 +42,8 @@ export type { BundleIdentity, BundleTrustFailure, BundleTrustPolicy, BundleTrust
42
42
  export declare class CraftApp {
43
43
  private process?;
44
44
  private config;
45
+ /** The spilled sidebar config's directory, removed when the window process ends. */
46
+ private sidebarConfigDir?;
45
47
  constructor(config?: AppConfig);
46
48
  /**
47
49
  * Create and show a window with HTML content
@@ -55,6 +57,8 @@ export declare class CraftApp {
55
57
  * Close the window
56
58
  */
57
59
  close(): void;
60
+ /** The window process has read its sidebar config, or never will. */
61
+ private removeSidebarConfig;
58
62
  private buildArgs;
59
63
  /**
60
64
  * Resolve the `craft` binary to spawn.
package/dist/index.d.ts CHANGED
@@ -42,6 +42,8 @@ export type { BundleIdentity, BundleTrustFailure, BundleTrustPolicy, BundleTrust
42
42
  export declare class CraftApp {
43
43
  private process?;
44
44
  private config;
45
+ /** The spilled sidebar config's directory, removed when the window process ends. */
46
+ private sidebarConfigDir?;
45
47
  constructor(config?: AppConfig);
46
48
  /**
47
49
  * Create and show a window with HTML content
@@ -55,6 +57,8 @@ export declare class CraftApp {
55
57
  * Close the window
56
58
  */
57
59
  close(): void;
60
+ /** The window process has read its sidebar config, or never will. */
61
+ private removeSidebarConfig;
58
62
  private buildArgs;
59
63
  /**
60
64
  * Resolve the `craft` binary to spawn.