Port auto launch

This commit is contained in:
everoddandeven 2024-10-23 23:13:31 +02:00
parent 9bb45d09e4
commit d6dee8e56d
12 changed files with 611 additions and 64 deletions

1
.gitignore vendored
View file

@ -8,6 +8,7 @@
/release
main.js
src/**/*.js
app/auto-launch/**/*.js
*.js.map
# dependencies

67
app/auto-launch/index.ts Normal file
View file

@ -0,0 +1,67 @@
import * as pathTools from 'path';
import autoLaunchHandler from './library/autoLaunchHandler';
import { AutoLaunchOptions } from './library/autoLaunchAPI/autoLaunchInit';
import AutoLaunchAPI from './library/autoLaunchAPI/autoLaunchAPI';
// Public: The main auto-launch class
export default class AutoLaunch {
private api: AutoLaunchAPI;
/* Public */
// {Object}
// :name - {String}
// :path - (Optional) {String}
// :options - (Optional) {Object}
// :launchInBackground, - (Optional) {String}. If set, either use default --hidden arg or specified one.
// :mac - (Optional) {Object}
// :useLaunchAgent - (Optional) {Boolean}. If `true`, use filed-based Launch Agent. Otherwise use AppleScript
// to add Login Item
// :extraArgs - (Optional) {Array}
constructor({ name , path, options }: { name: string, path: string, options: AutoLaunchOptions }) {
// Name is the only mandatory parameter and must neither be null nor empty
if (!name) { throw new Error('You must specify a name'); }
const opts = {
appName: name,
options: {
launchInBackground: (options && (options.launchInBackground != null)) ? options.launchInBackground : false,
mac: (options && (options.mac != null)) ? options.mac : {},
extraArguments: (options && (options.extraArguments != null)) ? options.extraArguments : []
},
appPath: ''
};
const versions = typeof process !== 'undefined' && process !== null ? process.versions : undefined;
if (path != null) {
// Verify that the path is absolute or is an AppX path
if ((!pathTools.isAbsolute(path)) && !process.windowsStore) {
throw new Error('path must be absolute');
}
opts.appPath = path;
} else if (
(versions != null)
&& ((versions.nw != null) || (versions['node-webkit'] != null) || (versions.electron != null))) {
// Autodetecting the appPath from the execPath.
// This appPath will need to be fixed later depending of the OS used
// TODO: is this the reason behind issue 92: https://github.com/Teamwork/node-auto-launch/issues/92
opts.appPath = process.execPath;
} else {
throw new Error('You must give a path (this is only auto-detected for NW.js and Electron apps)');
}
this.api = autoLaunchHandler(opts);
}
public enable(): Promise<void> {
return this.api.enable();
}
public disable(): Promise<void> {
return this.api.disable();
}
// Returns a Promise which resolves to a {Boolean}
public isEnabled(): Promise<boolean> {
return this.api.isEnabled();
}
}

View file

@ -0,0 +1,33 @@
import { AutoLaunchInit, AutoLaunchOptions } from "./autoLaunchInit";
export default abstract class AutoLaunchAPI {
/* Public */
public appName: string;
public appPath: string;
public options: AutoLaunchOptions;
// init - {Object}
// :appName - {String}
// :appPath - {String}
// :options - {Object}
// :launchInBackground - (Optional) {String} If set, either use default --hidden arg or specified one.
// :mac - (Optional) {Object}
// :useLaunchAgent - (Optional) {Boolean} If `true`, use filed-based Launch Agent. Otherwise use AppleScript
// to add Login Item
// :extraArguments - (Optional) {Array}
protected constructor(init: AutoLaunchInit) {
this.appName = init.appName;
this.appPath = init.appPath;
this.options = init.options;
}
// Returns a Promise
public abstract enable(): Promise<void>;
// Returns a Promise
public abstract disable(): Promise<void>;
// Returns a Promise which resolves to a {Boolean}
public abstract isEnabled(): Promise<boolean>;
}

View file

@ -0,0 +1,92 @@
import * as path from 'path';
//import * as untildify from 'untildify';
import * as fileBasedUtilities from '../fileBasedUtilities';
import AutoLaunchAPI from './autoLaunchAPI';
import { AutoLaunchInit } from './autoLaunchInit';
const LINUX_AUTOSTART_DIR = '~/.config/autostart';
const LINUX_DESKTOP = `
[Desktop Entry]
Type=Application
Version=1.0
Name={{APP_NAME}}
Comment={{APP_NAME}} startup script
Exec={{APP_PATH}} {{ARGS}}
StartupNotify=false
Terminal=false
`;
export default class AutoLaunchAPILinux extends AutoLaunchAPI {
/* Public */
constructor(init: AutoLaunchInit) {
super(init);
this.appPath = this.fixAppPath();
}
// Returns a Promise
public override enable(): Promise<void> {
const hiddenArg = this.options.launchInBackground;
const extraArgs = this.options.extraArguments;
const programArguments = [];
// Manage arguments
if (hiddenArg) {
programArguments.push((hiddenArg !== true) ? hiddenArg : '--hidden');
}
if (extraArgs) {
programArguments.push(extraArgs);
}
const args = programArguments.join(' ');
const desktop = LINUX_DESKTOP.trim()
.replace(/{{APP_NAME}}/g, this.appName)
.replace(/{{APP_PATH}}/g, this.appPath)
.replace(/{{ARGS}}/g, args);
return fileBasedUtilities.createFile({
directory: this.getAutostartDirectory(),
filePath: this.getDesktopFilePath(),
data: desktop
});
}
// Returns a Promise
public override disable(): Promise<void> {
return fileBasedUtilities.removeFile(this.getDesktopFilePath());
}
// Returns a Promise which resolves to a {Boolean}
public override isEnabled(): Promise<boolean> {
return fileBasedUtilities.fileExists(this.getDesktopFilePath());
}
/* Private */
// Returns a {String}
private getAutostartDirectory(): string {
return fileBasedUtilities.untildify(LINUX_AUTOSTART_DIR);
}
// Returns a {String}
private getDesktopFilePath(): string {
return path.join(this.getAutostartDirectory(), `${this.appName}.desktop`);
}
// Returns a {String}
private fixAppPath(): string {
let execPath = this.appPath;
// If this is an AppImage, the actual AppImage's file path must be used, otherwise the mount path will be used.
// This will fail on the next launch, since AppImages are mount temporarily when executed
// in an everchanging mount folder.
if (process.env?.APPIMAGE != null) {
execPath = process.env.APPIMAGE;
}
// As stated in the .desktop entry spec, Exec key's value must be properly escaped with reserved characters.
execPath = fileBasedUtilities.escapeFilePath(execPath);
return execPath;
}
}

View file

@ -0,0 +1,157 @@
const applescript = require('applescript');
import * as path from 'path';
import * as fileBasedUtilities from '../fileBasedUtilities';
import AutoLaunchAPI from './autoLaunchAPI';
import { AutoLaunchInit } from './autoLaunchInit';
const MAC_LAUNCHAGENTS_DIR = '~/Library/LaunchAgents/';
const MAC_PLIST_DATA = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>{{APP_NAME}}</string>
<key>ProgramArguments</key>
<array>
{{PROGRAM_ARGUMENTS_SECTION}}
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>`;
export default class AutoLaunchAPIMac extends AutoLaunchAPI {
/* Public */
constructor(init: AutoLaunchInit) {
super(init);
this.appName = this.fixAppName();
this.appPath = this.fixAppPath();
}
// Returns a Promise
public override enable(): Promise<void> {
const hiddenArg = this.options.launchInBackground;
const extraArgs = this.options.extraArguments;
// Add the file if we're using a Launch Agent
if (this.options?.mac?.useLaunchAgent) {
const programArguments = [this.appPath];
// Manage arguments
if (hiddenArg) {
programArguments.push((hiddenArg !== true) ? hiddenArg : '--hidden');
}
if (extraArgs) {
programArguments.push(...extraArgs);
}
const programArgumentsSection = programArguments
.map((argument) => ` <string>${argument}</string>`)
.join('\n');
const plistData = MAC_PLIST_DATA.trim()
.replace(/{{APP_NAME}}/g, this.appName)
.replace(/{{PROGRAM_ARGUMENTS_SECTION}}/g, programArgumentsSection);
return fileBasedUtilities.createFile({
directory: this.getLaunchAgentsDirectory(),
filePath: this.getPlistFilePath(),
data: plistData
});
}
// Otherwise, use default method; use AppleScript to tell System Events to add a Login Item
const isHidden = hiddenArg ? 'true' : 'false';
// TODO: Manage extra arguments
const properties = `{path:"${this.appPath}", hidden:${isHidden}, name:"${this.appName}"}`;
return this.execApplescriptCommand(`make login item at end with properties ${properties}`);
}
// Returns a Promise
public override disable(): Promise<void> {
// Delete the file if we're using a Launch Agent
if (this.options.mac?.useLaunchAgent) {
return fileBasedUtilities.removeFile(this.getPlistFilePath());
}
// Otherwise remove the Login Item
return this.execApplescriptCommand(`delete login item "${this.appName}"`);
}
// Returns a Promise which resolves to a {Boolean}
public override isEnabled(): Promise<boolean> {
// Check if the Launch Agent file exists
if (this.options.mac?.useLaunchAgent) {
return fileBasedUtilities.fileExists(this.getPlistFilePath());
}
// Otherwise check if a Login Item exists for our app
return this.execApplescriptCommand('get the name of every login item')
.then((loginItems) => (loginItems != null) && Array.from<any>(loginItems).includes(this.appName));
}
/* Private */
// commandSuffix - {String}
// Returns a Promise
private execApplescriptCommand(commandSuffix: string): Promise<any>{
return new Promise((resolve, reject) => {
applescript.execString(`tell application "System Events" to ${commandSuffix}`, (err: { message: string, strPath: string, appleScript: string } | null, result: any) => {
if (err != null) {
return reject(err);
}
return resolve(result);
});
});
}
// Returns a {String}
private getLaunchAgentsDirectory(): string {
return fileBasedUtilities.untildify(MAC_LAUNCHAGENTS_DIR);
}
// Returns a {String}
private getPlistFilePath(): string {
return path.join(this.getLaunchAgentsDirectory(), `${this.appName}.plist`);
}
// Corrects the path to point to the outer .app
// Returns a {String}
private fixAppPath(): string {
let execPath = this.appPath;
// This will match apps whose inner app and executable's basename is the outer app's basename plus "Helper"
// (the default Electron app structure for example)
// It will also match apps whose outer app's basename is different to the rest but the inner app and executable's
// basenames are matching (a typical distributed NW app for example)
// Does not match when the three are different
// Also matches when the path is pointing not to the exectuable in the inner app at all but to the Electron
// executable in the outer app
// eslint-disable-next-line max-len
execPath = execPath.replace(/(^.+?[^/]+?\.app)\/Contents\/(Frameworks\/((\1|[^/]+?) Helper)\.app\/Contents\/MacOS\/\3|MacOS\/Electron)/, '$1');
// When using a launch agent, it needs the inner executable path
if (!this.options.mac?.useLaunchAgent) {
execPath = execPath.replace(/\.app\/Contents\/MacOS\/[^/]*$/, '.app');
}
return execPath;
}
// Kept from Coffeescript, but should we honor the name given to autoLaunch or should we change it specifically for macOS?
// No explanation, see issue 92: https://github.com/Teamwork/node-auto-launch/issues/92
private fixAppName(): string {
let fixedName: string;
const tempPath = this.appPath.split('/');
fixedName = tempPath[tempPath.length - 1];
// Remove ".app" from the appName if it exists
if (fixedName.indexOf('.app', fixedName.length - '.app'.length) !== -1) {
fixedName = fixedName.substr(0, fixedName.length - '.app'.length);
}
return fixedName;
}
}

View file

@ -0,0 +1,105 @@
import * as fs from 'fs';
import * as path from 'path';
import * as Winreg from 'winreg';
import AutoLaunchAPI from './autoLaunchAPI';
import { AutoLaunchInit } from './autoLaunchInit';
const regKey = new Winreg({
hive: Winreg.HKCU,
key: '\\Software\\Microsoft\\Windows\\CurrentVersion\\Run'
});
export default class AutoLaunchAPIWindows extends AutoLaunchAPI {
/* Public */
constructor(init: AutoLaunchInit) {
super(init);
}
// Returns a Promise
public override enable(): Promise<void> {
return new Promise<void>((resolve, reject) => {
let args = '';
let pathToAutoLaunchedApp;
const hiddenArg = this.options.launchInBackground;
const extraArgs = this.options.extraArguments;
const updateDotExe = path.join(path.dirname(process.execPath), '..', 'update.exe');
// If they're using Electron and Squirrel.Windows, point to its Update.exe instead of the actual appPath
// Otherwise, we'll auto-launch an old version after the app has updated
if (((process.versions != null ? process.versions.electron : undefined) != null) && fs.existsSync(updateDotExe)) {
pathToAutoLaunchedApp = `"${updateDotExe}"`;
args = ` --processStart "${path.basename(process.execPath)}"`;
// Manage arguments
if (hiddenArg || extraArgs) {
args += ' --process-start-args';
if (hiddenArg) {
args += ` "${(hiddenArg !== true) ? hiddenArg : '--hidden'}"`;
}
// Add any extra arguments
if (extraArgs) {
args += ' "';
args += extraArgs.join('" "');
args += '"';
}
}
} else {
// If this is an AppX (from Microsoft Store), the path doesn't point to a directory per se,
// but it's made of "DEV_ID.APP_ID!PACKAGE_NAME". It's used to identify the app in the AppsFolder.
// To launch the app, explorer.exe must be call in combination with its path relative to AppsFolder
if (process.windowsStore) {
pathToAutoLaunchedApp = `"explorer.exe" shell:AppsFolder\\${this.appPath}`;
} else {
pathToAutoLaunchedApp = `"${this.appPath}"`;
}
// Manage arguments
if (hiddenArg) {
args = (hiddenArg !== true) ? hiddenArg : ' --hidden';
}
// Add any extra arguments
if (extraArgs) {
args += ' ';
args += extraArgs.join(' ');
}
}
regKey.set(this.appName, Winreg.REG_SZ, `"${pathToAutoLaunchedApp}"${args}`, (err) => {
if (err != null) {
return reject(err);
}
return resolve();
});
});
}
// Returns a Promise
public override disable(): Promise<void> {
return new Promise<void>((resolve, reject) => {
regKey.remove(this.appName, (err) => {
if (err != null) {
// The registry key should exist but, in case it fails because it doesn't exist,
// resolve false instead of rejecting with an error
if (err.message.indexOf('The system was unable to find the specified registry key or value') !== -1) {
return resolve();
}
return reject(err);
}
return resolve();
});
});
}
// Returns a Promise which resolves to a {Boolean}
public override isEnabled(): Promise<boolean> {
return new Promise((resolve) => {
regKey.valueExists(this.appName, (err, exists) => {
if (err != null) {
return resolve(false);
}
return resolve(exists);
});
});
}
}

View file

@ -0,0 +1,24 @@
// init - {Object}
// :appName - {String}
// :appPath - {String}
// :options - {Object}
// :launchInBackground - (Optional) {String} If set, either use default --hidden arg or specified one.
// :mac - (Optional) {Object}
// :useLaunchAgent - (Optional) {Boolean} If `true`, use filed-based Launch Agent. Otherwise use AppleScript
// to add Login Item
// :extraArguments - (Optional) {Array}
export interface AutoLaunchInit {
appName: string;
appPath: string;
options: AutoLaunchOptions;
}
export interface AutoLaunchOptions {
launchInBackground?: string | boolean;
mac?: {
useLaunchAgent?: boolean;
},
extraArguments?: string[];
}

View file

@ -0,0 +1,23 @@
import AutoLaunchAPI from './autoLaunchAPI/autoLaunchAPI';
import AutoLaunchAPILinux from './autoLaunchAPI/autoLaunchAPILinux';
import AutoLaunchAPIMac from './autoLaunchAPI/autoLaunchAPIMac';
import AutoLaunchAPIWindows from './autoLaunchAPI/autoLaunchAPIWindows';
import { AutoLaunchInit } from './autoLaunchAPI/autoLaunchInit';
/* This allows to select the AutoLaunch implementation specific to a */
//
// Returns a AutoLaunchAPI object
export default function autoLaunchHandler(options: AutoLaunchInit): AutoLaunchAPI {
if (/^win/.test(process.platform)) {
return new AutoLaunchAPIWindows(options);
}
if (/darwin/.test(process.platform)) {
return new AutoLaunchAPIMac(options);
}
if ((/linux/.test(process.platform)) || (/freebsd/.test(process.platform))) {
return new AutoLaunchAPILinux(options);
}
throw new Error('Unsupported platform');
}

View file

@ -0,0 +1,79 @@
import * as os from 'os';
import * as fs from 'fs';
// Public: a few utils for file-based auto-launching
// This is essentially enabling auto-launching
// options - {Object}
// :directory - {String}
// :filePath - {String}
// :data - {String}
// Returns a Promise
export function createFile({ directory, filePath, data }: { directory: string, filePath: string, data: string }): Promise<void> {
return new Promise<void>((resolve, reject) => {
fs.mkdir(directory, { recursive: true }, (mkdirErr) => {
if (mkdirErr != null) {
return reject(mkdirErr);
}
return fs.writeFile(filePath, data, (writeErr) => {
if (writeErr != null) {
return reject(writeErr);
}
return resolve();
});
});
});
}
// Verify auto-launch file exists or not
// filePath - {String}
// Returns a Promise
export function fileExists(filePath: string): Promise<boolean> {
return new Promise<boolean>((resolve) => {
fs.stat(filePath, (err, stat) => {
if (err != null) {
return resolve(false);
}
return resolve(stat != null);
});
});
}
// This is essentially disabling auto-launching
// filePath - {String}
// Returns a Promise
export function removeFile(filePath: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
fs.stat(filePath, (statErr) => {
// If it doesn't exist, this is good so resolve
if (statErr != null) {
return resolve();
}
return fs.unlink(filePath, (unlinkErr) => {
if (unlinkErr != null) {
return reject(unlinkErr);
}
return resolve();
});
});
});
}
// Escape reserved characters in path
// filePath - {String}
// Returns {String}
export function escapeFilePath(filePath: string): string {
return filePath.replace(/(\s+)/g, '\\$1');
// return filePath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // https://github.com/tc39/proposal-regex-escaping
}
export function untildify(pathWithTilde: string): string {
const homeDirectory = os.homedir();
if (typeof pathWithTilde !== 'string') {
throw new TypeError(`Expected a string, got ${typeof pathWithTilde}`);
}
return homeDirectory ? pathWithTilde.replace(/^~(?=$|\/|\\)/, homeDirectory) : pathWithTilde;
}

View file

@ -1,4 +1,6 @@
import { app, BrowserWindow, ipcMain, screen, dialog, Tray, Menu, MenuItemConstructorOptions, FileFilter, IpcMainInvokeEvent, Notification, NotificationConstructorOptions } from 'electron';
import { app, BrowserWindow, ipcMain, screen, dialog, Tray, Menu, MenuItemConstructorOptions,
IpcMainInvokeEvent, Notification, NotificationConstructorOptions
} from 'electron';
import { ChildProcessWithoutNullStreams, exec, ExecException, spawn } from 'child_process';
import * as path from 'path';
import * as fs from 'fs';
@ -7,8 +9,7 @@ import { createHash } from 'crypto';
import * as tar from 'tar';
import * as os from 'os';
import * as pidusage from 'pidusage';
//import AutoLaunch from 'auto-launch';
const AutoLaunch = require('auto-launch');
import AutoLaunch from './auto-launch';
interface Stats {
/**
@ -60,8 +61,13 @@ if (!gotInstanceLock) {
}
const autoLauncher = new AutoLaunch({
name: 'Monero Daemon',
path: `${process.execPath} --auto-launch`
name: 'monerod-gui',
path: process.execPath,
options: {
extraArguments: [
'--auto-launch'
]
}
});
const isAutoLaunched: boolean = process.argv.includes('--auto-launch');

70
app/package-lock.json generated
View file

@ -8,15 +8,17 @@
"name": "monerod-gui",
"version": "0.1.0",
"dependencies": {
"auto-launch": "^5.0.6",
"applescript": "^1.0.0",
"os": "^0.1.2",
"pidusage": "^3.0.2",
"tar": "^7.4.3",
"unbzip2-stream": "^1.4.3"
"unbzip2-stream": "^1.4.3",
"winreg": "^1.2.5"
},
"devDependencies": {
"@types/auto-launch": "^5.0.5",
"@types/pidusage": "^2.0.5"
"@types/pidusage": "^2.0.5",
"@types/winreg": "^1.2.36"
}
},
"node_modules/@isaacs/cliui": {
@ -67,6 +69,12 @@
"integrity": "sha512-MIiyZI4/MK9UGUXWt0jJcCZhVw7YdhBuTOuqP/BjuLDLZ2PmmViMIQgZiWxtaMicQfAz/kMrZ5T7PKxFSkTeUA==",
"dev": true
},
"node_modules/@types/winreg": {
"version": "1.2.36",
"resolved": "https://registry.npmjs.org/@types/winreg/-/winreg-1.2.36.tgz",
"integrity": "sha512-DtafHy5A8hbaosXrbr7YdjQZaqVewXmiasRS5J4tYMzt3s1gkh40ixpxgVFfKiQ0JIYetTJABat47v9cpr/sQg==",
"dev": true
},
"node_modules/ansi-regex": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
@ -94,21 +102,6 @@
"resolved": "https://registry.npmjs.org/applescript/-/applescript-1.0.0.tgz",
"integrity": "sha512-yvtNHdWvtbYEiIazXAdp/NY+BBb65/DAseqlNiJQjOx9DynuzOYDbVLBJvuc0ve0VL9x6B3OHF6eH52y9hCBtQ=="
},
"node_modules/auto-launch": {
"version": "5.0.6",
"resolved": "https://registry.npmjs.org/auto-launch/-/auto-launch-5.0.6.tgz",
"integrity": "sha512-OgxiAm4q9EBf9EeXdPBiVNENaWE3jUZofwrhAkWjHDYGezu1k3FRZHU8V2FBxGuSJOHzKmTJEd0G7L7/0xDGFA==",
"dependencies": {
"applescript": "^1.0.0",
"mkdirp": "^0.5.1",
"path-is-absolute": "^1.0.0",
"untildify": "^3.0.2",
"winreg": "1.2.4"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@ -310,14 +303,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minimist": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/minipass": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
@ -338,17 +323,6 @@
"node": ">= 18"
}
},
"node_modules/mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"dependencies": {
"minimist": "^1.2.6"
},
"bin": {
"mkdirp": "bin/cmd.js"
}
},
"node_modules/os": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/os/-/os-0.1.2.tgz",
@ -359,14 +333,6 @@
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@ -596,14 +562,6 @@
"through": "^2.3.8"
}
},
"node_modules/untildify": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz",
"integrity": "sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==",
"engines": {
"node": ">=4"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -619,9 +577,9 @@
}
},
"node_modules/winreg": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.4.tgz",
"integrity": "sha512-IHpzORub7kYlb8A43Iig3reOvlcBJGX9gZ0WycHhghHtA65X0LYnMRuJs+aH1abVnMJztQkvQNlltnbPi5aGIA=="
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.5.tgz",
"integrity": "sha512-uf7tHf+tw0B1y+x+mKTLHkykBgK2KMs3g+KlzmyMbLvICSHQyB/xOFjTT8qZ3oeTFyU7Bbj4FzXitGG6jvKhYw=="
},
"node_modules/wrap-ansi": {
"version": "8.1.0",

View file

@ -10,14 +10,16 @@
"main": "main.js",
"private": true,
"dependencies": {
"auto-launch": "^5.0.6",
"applescript": "^1.0.0",
"os": "^0.1.2",
"pidusage": "^3.0.2",
"tar": "^7.4.3",
"unbzip2-stream": "^1.4.3"
"unbzip2-stream": "^1.4.3",
"winreg": "^1.2.5"
},
"devDependencies": {
"@types/auto-launch": "^5.0.5",
"@types/pidusage": "^2.0.5"
"@types/pidusage": "^2.0.5",
"@types/winreg": "^1.2.36"
}
}