positron.js 1.0.3 → 1.0.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/index.js CHANGED
@@ -267,7 +267,8 @@ class Window extends Events.EventEmitter {
267
267
  * @param {string} command
268
268
  * @param {string[]} args
269
269
  */
270
- sendCommand(command, args = []) {
270
+ sendCommand(command, ...args) {
271
+ if(args[0] instanceof Array) args = args[0].concat(args.slice(1));
271
272
  const normalizedArgs = Array.isArray(args) ? args.map(String) : [String(args)];
272
273
 
273
274
  const payload = JSON.stringify({
@@ -316,7 +317,7 @@ class Window extends Events.EventEmitter {
316
317
  * @param {string} path The path to the file to load.
317
318
  */
318
319
  async loadFile(path) {
319
- const res = await this.request("loadFile", `loadFile-reply-${this.id}`, path);
320
+ const res = await this.request("loadFile", { replyChannel: `loadFile-reply-${this.id}` }, path);
320
321
  this.emit("file-loaded", path);
321
322
  this.emit("navigated", path);
322
323
  return res;
@@ -327,7 +328,7 @@ class Window extends Events.EventEmitter {
327
328
  * @param {string} url The URL to load.
328
329
  */
329
330
  async loadURL(url) {
330
- const res = await this.request("loadURL", `loadURL-reply-${this.id}`, url);
331
+ const res = await this.request("loadURL", { replyChannel: `loadURL-reply-${this.id}` }, url);
331
332
  this.emit("url-loaded", url);
332
333
  this.emit("navigated", url);
333
334
  return res;
@@ -338,7 +339,7 @@ class Window extends Events.EventEmitter {
338
339
  * @param {string} channel The IPC channel to send the message on.
339
340
  * @param {string[]} args The arguments to send with the message.
340
341
  */
341
- sendIpc(channel, args = []) {
342
+ emitToRenderer(channel, args = []) {
342
343
  if (activeSocket && activeSocket.readyState === WebSocket.OPEN) {
343
344
  const payload = JSON.stringify({
344
345
  windowId: this.id,
@@ -348,10 +349,19 @@ sendIpc(channel, args = []) {
348
349
  activeSocket.send(payload);
349
350
  this.emit("ipc-sent", { channel, args });
350
351
  } else {
351
- warn(`Cannot send IPC message, socket not ready. Channel: ${channel}`);
352
+ warn(`Cannot send IPC message on ${channel}, socket not ready.`);
352
353
  }
353
354
  }
354
355
 
356
+
357
+ /**
358
+ * @deprecated Use emitToRenderer instead.
359
+ */
360
+ sendIpc = (channel, args = []) => {
361
+ warn("sendIpc is deprecated. Use emitToRenderer instead.");
362
+ this.emitToRenderer(channel, args);
363
+ }
364
+
355
365
  #created = false;
356
366
 
357
367
  /**
@@ -405,6 +415,10 @@ create(width, height, darwinOptions = {
405
415
  this.sendCommand("forceCloseWindow");
406
416
  }
407
417
 
418
+ isClosed() {
419
+ return !activeWindows.has(this);
420
+ }
421
+
408
422
 
409
423
  /**
410
424
  * Sets the application menu for this window.
@@ -488,10 +502,6 @@ resize(width, height) {
488
502
  * Opens the developer tools for the window. Emits a "devtools-opened" event when done. Does not work on macOS. For macOS, right-click the window and select "Inspect Element" to open dev tools for that window.
489
503
  */
490
504
  openDevTools() {
491
- if(process.platform === "darwin") {
492
- warn("The openDevTools command is not supported on macOS due to OS limitations. Please right-click the window and select 'Inspect Element' to access developer tools.");
493
- return;
494
- }
495
505
  this.sendCommand("openDevTools");
496
506
  this.emit("devtools-opened");
497
507
  }
@@ -560,6 +570,26 @@ focus() {
560
570
  this.emit("focused");
561
571
  }
562
572
 
573
+ /**
574
+ * Checks if the window is currently visible. Returns a Promise that resolves to true if the window is visible, or false if it is hidden. Emits an "is-visible-checked" event with the result as data when done.
575
+ * @returns {Promise<boolean>} True if the window is visible, false otherwise.
576
+ */
577
+ async isVisible() {
578
+ const res = await this.request("isVisible");
579
+ return res?.isVisible === "true";
580
+ }
581
+
582
+ /**
583
+ * Checks if the window is currently in fullscreen mode.
584
+ * @returns {Promise<boolean>} True if the window is fullscreen, false otherwise.
585
+ */
586
+ async isFullscreen() {
587
+ const res = await this.request("isFullscreen");
588
+ return res?.isFullscreen === "true";
589
+ }
590
+
591
+
592
+
563
593
  /**
564
594
  * Reloads the window. Emits a "reloaded" event when done.
565
595
  */
@@ -573,7 +603,7 @@ reload() {
573
603
  * @returns {Promise<Buffer|null>} The captured screenshot as a Buffer, or null if the capture failed.
574
604
  */
575
605
  async capturePage() {
576
- const response = await this.request("capturePage", `capture-page-result-${this.id}`);
606
+ const response = await this.request("capturePage", { replyChannel: `capture-page-result-${this.id}` });
577
607
  return response.image ? Buffer.from(response.image, "base64") : null;
578
608
  }
579
609
 
@@ -582,52 +612,94 @@ async capturePage() {
582
612
  * @returns {Promise<boolean>} True if the window can navigate back, false otherwise.
583
613
  */
584
614
  async canGoBack() {
585
- const response = await this.request("canGoBack", `canGoBack-reply-${this.id}`);
615
+ const response = await this.request("canGoBack");
586
616
  return response === "true";
587
617
  }
588
618
 
619
+ // @ts-check
620
+
621
+ /**
622
+ * @typedef {Object} RequestOptions
623
+ * @property {number} [timeout]
624
+ * @property {boolean} [noTimeout]
625
+ * @property {string} [replyChannel]
626
+ */
627
+
589
628
  /**
590
- * Sends a request/response command to the native layer. The command will be sent, and the method will wait for a response on the specified reply channel. Once a response is received, the promise will resolve with the reply data.
591
- * @param {string} command The command to send.
592
- * @param {string} replyChannel The channel to listen for the reply on.
593
- * @returns {Promise<*>} A promise that resolves to the reply data.
629
+ * @overload
630
+ * @param {string} command
631
+ * @param {...any} args
632
+ * @returns {Promise<any>}
594
633
  */
595
- async request(command, replyChannel, ...args) {
634
+
635
+ /**
636
+ * @overload
637
+ * @param {string} command
638
+ * @param {RequestOptions} options
639
+ * @param {...any} args
640
+ * @returns {Promise<any>}
641
+ */
642
+
643
+ /**
644
+ * Send an IPC request.
645
+ * @param {string} command
646
+ * @param {...any} args
647
+ */
648
+ async request(command, ...args) {
596
649
  return new Promise((resolve, reject) => {
597
650
  let settled = false;
598
651
 
652
+ let options = {};
653
+
654
+ if(typeof args[0] === "object" && args[0].constructor === Object) {
655
+ options = args[0];
656
+ args = args.slice(1);
657
+ }
658
+
599
659
  if(!command) {
600
660
  reject(new Error("Command is required for request"));
601
661
  return;
602
662
  }
603
663
 
604
- if(!replyChannel) {
605
- replyChannel = `${command}-reply-${this.id}`;
606
- }
664
+ const reqId = crypto.randomUUID();
665
+
666
+ let replyChannel = `${command}-reply-${reqId}`;
667
+
668
+ if (options.replyChannel) {
669
+ replyChannel = options.replyChannel;
670
+ } else if(args[0] && (args[0].includes("-reply-") || args[0].includes("-result-"))) {
671
+ // TEMP TRANSITIONAL LOGIC TO SUPPORT LEGACY REQUESTS THAT PASS REPLY CHANNEL AS FIRST ARGUMENT. WILL BE REMOVED IN A FUTURE RELEASE.
672
+ replyChannel = args[0];
673
+ }
607
674
 
608
675
  const unsubscribe = ipc.handle(replyChannel, (data) => {
609
676
  if (!settled) {
610
677
  settled = true;
611
678
  clearTimeout(timeout);
612
679
  unsubscribe();
680
+
681
+ for (const key in data) {
682
+ if (data[key] === "true") {
683
+ data[key] = true;
684
+ } else if (data[key] === "false") {
685
+ data[key] = false;
686
+ } else if (!isNaN(data[key])) {
687
+ data[key] = Number(data[key]);
688
+ }
689
+ }
690
+
613
691
  resolve(data);
614
692
  }
615
693
  });
616
694
 
617
695
  let timeout;
618
696
 
619
- if(!args.includes("NO_TIMEOUT")) {
697
+ if(!options.noTimeout) {
620
698
  let timeoutDuration = 7000;
621
-
622
- const timeoutArg = args.find(
623
- arg => typeof arg === "string" && arg.startsWith("TIMEOUT=")
624
- );
625
-
626
- if (timeoutArg) {
627
-
628
- args = args.filter(arg => arg !== timeoutArg);
699
+
629
700
 
630
- timeoutDuration = parseInt(timeoutArg.split("=")[1]);
701
+ if (options.timeout) {
702
+ timeoutDuration = options.timeout;
631
703
  }
632
704
 
633
705
  timeout = setTimeout(() => {
@@ -650,7 +722,7 @@ if (timeoutArg) {
650
722
  * @returns {Promise<boolean>} True if the window can navigate forward, false otherwise.
651
723
  */
652
724
  async canGoForward() {
653
- const response = await this.request("canGoForward", `canGoForward-reply-${this.id}`);
725
+ const response = await this.request("canGoForward");
654
726
  return response === "true";
655
727
  }
656
728
 
@@ -715,7 +787,7 @@ setBounds(x, y, width, height) {
715
787
  * @returns {Promise<string|null>} The user's input as a string, or null if the user cancelled the prompt.
716
788
  */
717
789
  async prompt(message, defaultValue = "") {
718
- const res = await this.request("prompt", `prompt-reply-${this.id}`, message, defaultValue);
790
+ const res = await this.request("prompt", message, defaultValue);
719
791
  this.emit("prompt", { message, defaultValue });
720
792
  return res?.input;
721
793
  }
@@ -747,8 +819,12 @@ setContextMenu(menuTemplate) {
747
819
  this.emit("context-menu-updated", menuTemplate);
748
820
  }
749
821
 
822
+ /**
823
+ * Checks if the window is currently focused.
824
+ * @returns {Promise<boolean>} True if the window is focused, false otherwise.
825
+ */
750
826
  async isFocused() {
751
- const res = await this.request("isFocused", `isFocused-reply-${this.id}`);
827
+ const res = await this.request("isFocused");
752
828
  return res?.isFocused === "true";
753
829
  }
754
830
 
@@ -757,7 +833,7 @@ async isFocused() {
757
833
  * @returns {Promise<{x: number, y: number, width: number, height: number}>} An object containing the window's bounds.
758
834
  */
759
835
  async getBounds() {
760
- return await this.request("getBounds", `getBounds-reply-${this.id}`);
836
+ return await this.request("getBounds");
761
837
  }
762
838
 
763
839
  /**
@@ -765,7 +841,7 @@ async getBounds() {
765
841
  * @returns {Promise<string>} The current URL loaded in the window.
766
842
  */
767
843
  async getURL() {
768
- return (await this.request("getURL", `getURL-reply-${this.id}`))?.url || "";
844
+ return (await this.request("getURL"))?.url || "";
769
845
  }
770
846
 
771
847
  /**
@@ -773,7 +849,7 @@ async getURL() {
773
849
  * @returns {Promise<string>} The current title of the window.
774
850
  */
775
851
  async getTitle() {
776
- return (await this.request("getTitle", `getTitle-reply-${this.id}`)).title || "";
852
+ return (await this.request("getTitle"))?.title || "";
777
853
  }
778
854
 
779
855
  /**
@@ -800,7 +876,7 @@ setTitlebarTransparent(isTransparent) {
800
876
  * @returns {Promise<*>} A Promise that resolves to the result of the evaluation.
801
877
  */
802
878
  async evaluateJavaScript(script) {
803
- const res = await this.request("evaluateJS", `evaluateJS-reply-${this.id}`, script);
879
+ const res = await this.request("evaluateJS", script);
804
880
  return res.result;
805
881
  }
806
882
 
@@ -950,7 +1026,7 @@ async removeOnClick(selector) {
950
1026
  * @returns {Promise<boolean>} True if the user confirmed, false if the user cancelled.
951
1027
  */
952
1028
  async confirm(message) {
953
- const res = await this.request("confirm", `confirm-reply-${this.id}`, message);
1029
+ const res = await this.request("confirm", message);
954
1030
  this.emit("confirm", message);
955
1031
  return res?.confirmed === "true";
956
1032
  }
@@ -961,10 +1037,70 @@ async confirm(message) {
961
1037
  * @returns {Promise<void>} A Promise that resolves when the swipe navigation setting has been updated.
962
1038
  */
963
1039
  async setSwipeNavigation(enabled) {
964
- const res = await this.request("setSwipeNav", `setSwipeNav-reply-${this.id}`, String(enabled));
1040
+ const res = await this.request("setSwipeNav", String(enabled));
965
1041
  this.emit("swipe-navigation-updated", enabled);
966
1042
  }
967
1043
 
1044
+ /**
1045
+ * Checks if swipe navigation is enabled for the window. Returns a Promise that resolves to true if swipe navigation is enabled, or false if it is disabled. Emits an "is-swipe-navigation-enabled-checked" event with the result as data when done.
1046
+ * @returns {Promise<boolean>} True if swipe navigation is enabled, false otherwise.
1047
+ */
1048
+ async isSwipeNavigationEnabled() {
1049
+ const res = await this.request("isSwipeNavEnabled");
1050
+ return res?.enabled === "true";
1051
+ }
1052
+
1053
+ /**
1054
+ * Adds content blocker rules to the window. The rules can be provided as a JSON object, loaded from a URL that returns JSON, or loaded from a local file.
1055
+ * Once the rules are added, the window will block content according to the specified rules. Emits a "content-blocker-updated" event with the new rules as data when done.
1056
+ * @param {Object} config The configuration for adding content blocker rules.
1057
+ * @param {Object[]} [config.json] An array of content blocker rules as JSON objects. Each rule should follow the format specified by the native layer's content blocking implementation.
1058
+ * @param {string} [config.url] A URL that returns a JSON array of content blocker rules. If provided, the rules will be loaded from this URL instead of using the `json` property.
1059
+ * @param {string} [config.file] A local file path containing the content blocker rules in JSON format. If provided, the rules will be loaded from this file instead of using the `json` or `url` properties.
1060
+ * @param {boolean} [config.reload] Whether to reload the window after adding the content blocker rules. Reloading may be necessary for the new rules to take effect immediately, but it can also be set to false if you want to add rules without interrupting the user's current session.
1061
+ * @param {boolean} [config.clearExisting] Whether to clear existing content blocker rules before adding the new ones. If false, the new rules will be added alongside any existing rules. If true, all existing rules will be removed before adding the new ones.
1062
+ * @platform macOS only
1063
+ * @see https://webkit.org/blog/3476/content-blockers-first-look/
1064
+ */
1065
+ async addToContentBlocker(config={ json:[], url:"", file:"", reload:true, clearExisting: false }) {
1066
+
1067
+ if(process.platform !== "darwin") return;
1068
+
1069
+ let json = config.json || [];
1070
+
1071
+ if(config.file) {
1072
+ json = config.file
1073
+ } else {
1074
+ if(config.url) {
1075
+ let req = (await fetch(config.url));
1076
+ let _json = await req.json();
1077
+
1078
+ if(json.length) {
1079
+ json = json.concat(_json);
1080
+ } else {
1081
+ json = _json;
1082
+ }
1083
+ }
1084
+
1085
+ json = JSON.stringify(json);
1086
+ }
1087
+
1088
+
1089
+ const res = await this.request("addToContentBlocker", json, config.reload, config.clearExisting);
1090
+ this.emit("content-blocker-updated", json);
1091
+ }
1092
+
1093
+ /**
1094
+ * Displays a file open dialog and returns a Promise that resolves to an array of selected file paths, or null if the user cancelled the dialog.
1095
+ * @param {*} options The options for the file open dialog.
1096
+ * @returns
1097
+ */
1098
+ async showFileOpenDialog(options = {}) {
1099
+ this.emit("show-file-open-dialog", options);
1100
+ return this.request("showFileOpenDialog", options);
1101
+ }
1102
+
1103
+
968
1104
  }
969
1105
 
970
1106
  const app = {
@@ -1047,9 +1183,10 @@ const app = {
1047
1183
  userData: {
1048
1184
  /**
1049
1185
  * Gets the path to the user data directory for the application. The path is determined based on the operating system and the application name. If the directory does not exist, it will be created. Emits a "user-data-path-retrieved" event with the path as data when done.
1186
+ * @param {string} [append] An optional string to append to the user data path. This can be used to create subdirectories within the user data directory for organizing different types of data. If provided, the appended path will also be created if it does not exist.
1050
1187
  * @returns {string} The path to the user data directory.
1051
1188
  */
1052
- getPath() {
1189
+ getPath(append) {
1053
1190
  let userPath = null;
1054
1191
 
1055
1192
  if (process.platform === "win32") {
@@ -1074,7 +1211,7 @@ userData: {
1074
1211
  fs.mkdirSync(userPath, { recursive: true });
1075
1212
  }
1076
1213
 
1077
- return userPath;
1214
+ return append ? path.join(userPath, append) : userPath;
1078
1215
  },
1079
1216
 
1080
1217
  /**
@@ -1108,11 +1245,71 @@ userData: {
1108
1245
  /**
1109
1246
  * Full access to the underlying event emitter for application-level events, allowing for advanced event handling patterns if needed.
1110
1247
  */
1111
- events: appEvents
1248
+ events: appEvents,
1249
+
1250
+ /**
1251
+ * Sends a command to the native layer. This is a low-level method that can be used to send arbitrary commands, but for most use cases you will want to use the higher-level methods provided by the Window class instead. Emits an "ipc-sent" event with the command and arguments as data when done.
1252
+ * @param {string} command The command to send to the native layer.
1253
+ * @param {any[]} args The arguments to send with the command.
1254
+ */
1255
+ sendToNative(command, args) {
1256
+ const firstWin = activeWindows.values().next().value;
1257
+ if (firstWin) {
1258
+ firstWin.sendCommand(command, args);
1259
+ } else {
1260
+ error("No active windows to send command to native layer");
1261
+ }
1262
+ },
1263
+
1264
+ /**
1265
+ * Sends a request to the native layer and returns a Promise that resolves to the response. This is a low-level method that can be used to send arbitrary requests, but for most use cases you will want to use the higher-level methods provided by the Window class instead. Emits an "ipc-request-sent" event with the command and arguments as data when done.
1266
+ * @param {string} command The command to send to the native layer.
1267
+ * @param {any[]} args The arguments to send with the command.
1268
+ * @returns {Promise<any>} A Promise that resolves to the response from the native layer.
1269
+ */
1270
+ async requestFromNative(command, ...args) {
1271
+ const firstWin = activeWindows.values().next().value;
1272
+ if (firstWin) {
1273
+ return await firstWin.request(command, ...args);
1274
+ } else {
1275
+ error("No active windows to send request to native layer");
1276
+ return null;
1277
+ }
1278
+ },
1279
+
1280
+ async isDarkMode() {
1281
+ const res = await this.requestFromNative("isDarkMode");
1282
+ return res?.isDarkMode;
1283
+ }
1284
+
1285
+ }
1286
+
1287
+ const clipboard = {
1288
+
1289
+ async writeText(text) {
1290
+ app.sendToNative("writeToClipboard", [text]);
1291
+ },
1292
+
1293
+ async readText() {
1294
+ const res = await app.requestFromNative("readFromClipboard");
1295
+ return res.text || "";
1296
+ }
1297
+
1298
+ }
1299
+
1300
+ const blockPowerSave = {
1112
1301
 
1302
+ start() {
1303
+ app.sendToNative("blockPowerSave");
1304
+ },
1305
+
1306
+ stop() {
1307
+ app.sendToNative("unblockPowerSave");
1308
+ }
1309
+
1113
1310
  }
1114
1311
 
1115
- module.exports = { Window, ipc, isPackaged, app, PORT };
1312
+ module.exports = { Window, ipc, isPackaged, app, PORT, clipboard, blockPowerSave };
1116
1313
 
1117
1314
  const findNearestPackageJson = require("./findpackage");
1118
1315
 
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "url": "https://github.com/systemsoftware/positron.js"
6
6
  },
7
7
  "homepage": "https://positronjs.gitbook.io",
8
- "version": "1.0.3",
8
+ "version": "1.0.5",
9
9
  "main": "index.js",
10
10
  "scripts": {
11
11
  "test": "node --test"
@@ -13,7 +13,13 @@
13
13
  "keywords": [
14
14
  "app",
15
15
  "framework",
16
- "desktop"
16
+ "desktop",
17
+ "cross-platform",
18
+ "native",
19
+ "javascript",
20
+ "node",
21
+ "macos",
22
+ "windows"
17
23
  ],
18
24
  "author": "Bryce",
19
25
  "license": "MIT",
@@ -21,6 +27,7 @@
21
27
  "dependencies": {
22
28
  "@yao-pkg/pkg": "^6.20.0",
23
29
  "esbuild": "^0.28.0",
30
+ "semver": "^7.8.1",
24
31
  "ws": "^8.20.1"
25
32
  },
26
33
  "bin": {
package/packager.js CHANGED
@@ -1,6 +1,6 @@
1
1
  const fs = require("fs");
2
2
  const path = require("path");
3
- const { execSync } = require("child_process");
3
+ const { execSync, execFileSync } = require("child_process");
4
4
  const { info, error, success } = require("./logs");
5
5
  const https = require("https");
6
6
  const esbuild = require("esbuild");
@@ -70,9 +70,12 @@ async function packageMacOS(appRoot, distDir, appName) {
70
70
  error("Fatal: Native compiled binary missing from bin/. Run build first.");
71
71
  process.exit(1);
72
72
  }
73
- fs.copyFileSync(compiledBinary, path.join(macosPath, appName));
74
- fs.chmodSync(path.join(macosPath, appName), "755");
75
-
73
+ const binaryPath = path.join(macosPath, appName);
74
+ fs.copyFileSync(compiledBinary, binaryPath);
75
+ fs.chmodSync(binaryPath, "755");
76
+
77
+
78
+
76
79
  const packageJsonPath = path.join(appRoot, "package.json");
77
80
  const package = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
78
81
 
@@ -105,14 +108,29 @@ async function packageMacOS(appRoot, distDir, appName) {
105
108
  handleJavaScriptPipeline(appRoot, resourcesPath);
106
109
 
107
110
  const bundledJs = path.join(resourcesPath, "index.js");
111
+ const backendName = appName.replace(/([a-z0-9])([A-Z])/g, '$1-$2').replace(/[\s_]+/g, '-').toLowerCase() + '-backend';
108
112
  if (fs.existsSync(bundledJs)) {
109
- await compileWithPkg(bundledJs, "darwin", resourcesPath, "positron-backend");
113
+ await compileWithPkg(bundledJs, "darwin", resourcesPath, backendName);
114
+ const binPathEscaped = path.join(resourcesPath, backendName).replace(/"/g, '\\"');
115
+
116
+ try {
117
+ let swiftScript = "";
118
+ if(fs.existsSync(path.join(__dirname, ['positronicon', 'png'].join('.')))) {
119
+ const iconPathEscaped = path.join(__dirname, ['positronicon', 'png'].join('.')).replace(/"/g, '\\"');
120
+ swiftScript = `import Cocoa; NSWorkspace.shared.setIcon(NSImage(contentsOfFile: "${iconPathEscaped}"), forFile: "${binPathEscaped}", options: []);`;
121
+ }
122
+ execFileSync("swift", ["-e", swiftScript], { stdio: "ignore" });
123
+ } catch (err) {
124
+ error("Failed to set custom icon on native binary:", err);
125
+ }
110
126
 
111
- // fs.renameSync(targetNodePath, path.join(resourcesPath, "positron-backend"));
112
127
  }
113
128
 
114
129
  fs.rmSync(path.join(resourcesPath, "icon.ico"), { force: true });
115
130
 
131
+ if(!process.argv.includes('--keep-package-json') || !process.argv.includes('--kpj')) {
132
+ fs.rmSync(path.join(resourcesPath, "package.json"), { force: true });
133
+ }
116
134
 
117
135
  success(`Successfully packaged macOS app at: ${appBundlePath}`);
118
136
  }
@@ -162,9 +180,10 @@ async function packageWindows(appRoot, distDir, appName) {
162
180
  }
163
181
 
164
182
  const bundledJs = path.join(resourcesPath, "index.js");
183
+ const backendName = appName.replace(/([a-z0-9])([A-Z])/g, '$1-$2').replace(/[\s_]+/g, '-').toLowerCase() + '-backend';
165
184
 
166
185
  if (fs.existsSync(bundledJs)) {
167
- await compileWithPkg(bundledJs, "win32", resourcesPath, "positron-backend");
186
+ await compileWithPkg(bundledJs, "win32", resourcesPath, backendName);
168
187
  } else {
169
188
  error(`[Packager] Fatal: Bundled JavaScript entry point missing at ${bundledJs}`);
170
189
  process.exit(1);
@@ -173,6 +192,10 @@ async function packageWindows(appRoot, distDir, appName) {
173
192
  fs.rmSync(path.join(resourcesPath, "icon.icns"), { force: true });
174
193
  fs.rmSync(path.join(resourcesPath, "icon.ico"), { force: true });
175
194
 
195
+ if(!process.argv.includes('--keep-package-json') || !process.argv.includes('--kpj')) {
196
+ fs.rmSync(path.join(resourcesPath, "package.json"), { force: true });
197
+ }
198
+
176
199
  const macBinaryPath = path.join(outputFolder, "positron-runtime");
177
200
  if (fs.existsSync(macBinaryPath)) {
178
201
  fs.rmSync(macBinaryPath);