@coreify/quitx 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/dist/cli.js +583 -24
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## 1.2.0
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `--dry-run` simulation mode:
|
|
13
|
+
- Simulate quitting applications without sending termination signals or killing processes.
|
|
14
|
+
- Formatted preview output (`[dry-run] Would quit <app>` / `[dry-run] Would force quit <app>`).
|
|
15
|
+
- Supported across interactive selector, targeted app list, and `--all` flag.
|
|
16
|
+
- Non-interactive configuration CLI:
|
|
17
|
+
- `quitx config show [--json]` to view current configuration as formatted list or JSON object.
|
|
18
|
+
- `quitx config get <key> [--json]` to retrieve individual configuration value.
|
|
19
|
+
- `quitx config set <key> <value> [--json]` with typed validation for booleans, force mode, and arrays.
|
|
20
|
+
- `quitx config reset [-y|--yes] [--json]` to restore default configuration non-interactively.
|
|
21
|
+
- Automatic update toggle:
|
|
22
|
+
- Added `autoUpdate` configuration property (default `true`).
|
|
23
|
+
- Configurable via interactive menu and `quitx config set autoUpdate <bool>`.
|
|
24
|
+
- Automatic background 6-hour update checks honor this setting.
|
|
25
|
+
- Package update checking system:
|
|
26
|
+
- Automatic 6-hour interval background check against npm registry (`CHECK_INTERVAL_MS`).
|
|
27
|
+
- Interactive manual update check command `quitx check-update` and `--check-update` flag with 60-second rate-limiting cooldown.
|
|
28
|
+
- `--no-update-check` flag to bypass update checks.
|
|
29
|
+
- Direct global package upgrade via `installUpdate` (`npm install --global @coreify/quitx@latest`).
|
|
30
|
+
- Persistent update cache in `~/.quitx/update.json` storing check timestamp and ignored versions.
|
|
31
|
+
- Full JSON output support for update queries (`quitx check-update --json`).
|
|
32
|
+
|
|
8
33
|
## 1.1.0
|
|
9
34
|
|
|
10
35
|
### Added
|
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { spinner, select, isCancel, log, intro, outro, multiselect, confirm, cancel } from '@clack/prompts';
|
|
3
|
+
import { readFile, mkdir, writeFile } from 'fs/promises';
|
|
4
4
|
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'fs';
|
|
5
5
|
import { homedir } from 'os';
|
|
6
|
-
import { join } from 'path';
|
|
6
|
+
import { join, dirname } from 'path';
|
|
7
7
|
import { execa } from 'execa';
|
|
8
8
|
import { spawn } from 'child_process';
|
|
9
9
|
|
|
@@ -18,7 +18,8 @@ var DEFAULT_CONFIG = {
|
|
|
18
18
|
groupBackground: true,
|
|
19
19
|
defaultSelectAll: true,
|
|
20
20
|
neverQuitMusic: false,
|
|
21
|
-
musicApps: []
|
|
21
|
+
musicApps: [],
|
|
22
|
+
autoUpdate: true
|
|
22
23
|
};
|
|
23
24
|
function loadConfig(filePath = CONFIG_FILE) {
|
|
24
25
|
try {
|
|
@@ -34,6 +35,7 @@ function loadConfig(filePath = CONFIG_FILE) {
|
|
|
34
35
|
const defaultSelectVal = typeof obj.defaultSelectAll === "boolean" ? obj.defaultSelectAll : obj.defaultSelect === "deselect-all" || obj.defaultSelection === "deselect-all" || obj.defaultSelect === false ? false : typeof obj.defaultSelect === "boolean" ? obj.defaultSelect : DEFAULT_CONFIG.defaultSelectAll;
|
|
35
36
|
const groupBgVal = typeof obj.groupBackground === "boolean" ? obj.groupBackground : typeof obj.groupBackgroundInstances === "boolean" ? obj.groupBackgroundInstances : DEFAULT_CONFIG.groupBackground;
|
|
36
37
|
const neverQuitMusicVal = typeof obj.neverQuitMusic === "boolean" ? obj.neverQuitMusic : DEFAULT_CONFIG.neverQuitMusic;
|
|
38
|
+
const autoUpdateVal = typeof obj.autoUpdate === "boolean" ? obj.autoUpdate : typeof obj.checkUpdate === "boolean" ? obj.checkUpdate : DEFAULT_CONFIG.autoUpdate;
|
|
37
39
|
return {
|
|
38
40
|
exclude: Array.isArray(obj.exclude) ? obj.exclude : [],
|
|
39
41
|
force: obj.force === "force" || obj.force === "normal" ? obj.force : DEFAULT_CONFIG.force,
|
|
@@ -43,7 +45,8 @@ function loadConfig(filePath = CONFIG_FILE) {
|
|
|
43
45
|
groupBackground: groupBgVal,
|
|
44
46
|
defaultSelectAll: defaultSelectVal,
|
|
45
47
|
neverQuitMusic: neverQuitMusicVal,
|
|
46
|
-
musicApps: Array.isArray(obj.musicApps) ? obj.musicApps : DEFAULT_CONFIG.musicApps
|
|
48
|
+
musicApps: Array.isArray(obj.musicApps) ? obj.musicApps : DEFAULT_CONFIG.musicApps,
|
|
49
|
+
autoUpdate: autoUpdateVal
|
|
47
50
|
};
|
|
48
51
|
} catch {
|
|
49
52
|
return { ...DEFAULT_CONFIG, exclude: [], musicApps: [] };
|
|
@@ -652,6 +655,13 @@ async function quitApps(apps, options = {}, executor, deferredScheduler = defaul
|
|
|
652
655
|
if (apps.length === 0) {
|
|
653
656
|
return [];
|
|
654
657
|
}
|
|
658
|
+
if (options.dryRun) {
|
|
659
|
+
return apps.map((app) => ({
|
|
660
|
+
app,
|
|
661
|
+
success: true,
|
|
662
|
+
forced: options.force ?? false
|
|
663
|
+
}));
|
|
664
|
+
}
|
|
655
665
|
const currentTerminalApps = [];
|
|
656
666
|
const otherApps = [];
|
|
657
667
|
for (const app of apps) {
|
|
@@ -731,14 +741,15 @@ function showOutro(message) {
|
|
|
731
741
|
function showCancel(message = "Cancelled.") {
|
|
732
742
|
cancel(message);
|
|
733
743
|
}
|
|
734
|
-
function renderResults(results) {
|
|
744
|
+
function renderResults(results, dryRun = false) {
|
|
735
745
|
for (const res of results) {
|
|
736
746
|
if (res.success) {
|
|
737
|
-
const label = res.forced ? `Force quit ${res.app.name}` : res.app.name;
|
|
747
|
+
const label = dryRun ? res.forced ? `[dry-run] Would force quit ${res.app.name}` : `[dry-run] Would quit ${res.app.name}` : res.forced ? `Force quit ${res.app.name}` : res.app.name;
|
|
738
748
|
log.success(label);
|
|
739
749
|
} else {
|
|
750
|
+
const prefix = dryRun ? "[dry-run] " : "";
|
|
740
751
|
const reason = res.error ? ` (${res.error})` : "";
|
|
741
|
-
log.error(
|
|
752
|
+
log.error(`${prefix}Could not quit ${res.app.name}${reason}`);
|
|
742
753
|
}
|
|
743
754
|
}
|
|
744
755
|
}
|
|
@@ -764,28 +775,37 @@ OPTIONS:
|
|
|
764
775
|
-y, --yes Skip confirmation prompts
|
|
765
776
|
-l, --list List running GUI apps and exit
|
|
766
777
|
-f, --force Force quit apps (SIGKILL) if normal quit fails
|
|
778
|
+
--dry-run Simulate quitting without terminating apps
|
|
767
779
|
-b, --background Include background processes in app list
|
|
768
780
|
--include-finder Include Finder in the app list
|
|
769
781
|
--include-trash Include Trash in the app list (empties on quit)
|
|
770
782
|
--exclude <apps> Add apps to persistent exclude list (comma-separated)
|
|
783
|
+
--check-update Check for package updates (60s rate limit)
|
|
784
|
+
--no-update-check Disable automatic update check for this run
|
|
771
785
|
--config Open interactive config manager
|
|
772
786
|
--json Output data in JSON format
|
|
773
787
|
-h, --help Show help information
|
|
774
788
|
-v, --version Show version number
|
|
775
789
|
|
|
776
790
|
COMMANDS:
|
|
777
|
-
config
|
|
791
|
+
config [action] Manage configuration (show, get, set, reset)
|
|
778
792
|
exclude Manage excluded applications interactively
|
|
793
|
+
check-update Check for package updates
|
|
779
794
|
|
|
780
795
|
EXAMPLES:
|
|
781
796
|
$ quitx Interactive app selector
|
|
782
797
|
$ quitx --all Quit all running apps (with confirmation)
|
|
798
|
+
$ quitx --all --dry-run Preview quitting all apps without killing them
|
|
783
799
|
$ quitx --all --yes Quit all running apps immediately
|
|
784
800
|
$ quitx --list List currently running GUI apps
|
|
785
801
|
$ quitx --list --background List all apps including background processes
|
|
786
802
|
$ quitx --list --include-finder List apps including Finder
|
|
787
803
|
$ quitx config Open interactive config manager
|
|
804
|
+
$ quitx config show Print current configuration
|
|
805
|
+
$ quitx config set force true Set default quit mode to force
|
|
806
|
+
$ quitx config get exclude Print excluded apps list
|
|
788
807
|
$ quitx exclude Open interactive menu to manage excluded apps
|
|
808
|
+
$ quitx check-update Check for package updates
|
|
789
809
|
$ quitx --exclude Spotify,Slack Add Spotify and Slack to exclude list
|
|
790
810
|
$ quitx Slack,Discord Quit specific apps directly
|
|
791
811
|
`.trim()
|
|
@@ -909,14 +929,20 @@ async function allCommand(options = {}) {
|
|
|
909
929
|
}
|
|
910
930
|
const s = spinner();
|
|
911
931
|
if (!options.json) {
|
|
912
|
-
s.start(
|
|
932
|
+
s.start(
|
|
933
|
+
options.dryRun ? `Simulating quit for ${apps.length} apps...` : `Quitting ${apps.length} apps...`
|
|
934
|
+
);
|
|
913
935
|
}
|
|
914
|
-
const results = await quitApps(apps, {
|
|
936
|
+
const results = await quitApps(apps, {
|
|
937
|
+
force: useForce,
|
|
938
|
+
dryRun: options.dryRun
|
|
939
|
+
});
|
|
915
940
|
if (!options.json) {
|
|
916
|
-
s.stop("Quitting complete");
|
|
917
|
-
renderResults(results);
|
|
941
|
+
s.stop(options.dryRun ? "Dry run complete" : "Quitting complete");
|
|
942
|
+
renderResults(results, options.dryRun);
|
|
918
943
|
const successCount = results.filter((r) => r.success).length;
|
|
919
|
-
|
|
944
|
+
const actionVerb = options.dryRun ? "Would quit" : "Quit";
|
|
945
|
+
showOutro(`${actionVerb} ${successCount} of ${apps.length} apps.`);
|
|
920
946
|
printThanks(options);
|
|
921
947
|
} else {
|
|
922
948
|
console.log(
|
|
@@ -1119,6 +1145,32 @@ async function toggleNeverQuitMusic() {
|
|
|
1119
1145
|
`Never quit music apps: ${config.neverQuitMusic ? "enabled" : "disabled"}`
|
|
1120
1146
|
);
|
|
1121
1147
|
}
|
|
1148
|
+
async function toggleAutoUpdate() {
|
|
1149
|
+
const config = loadConfig();
|
|
1150
|
+
const choice = await select({
|
|
1151
|
+
message: `Automatic Update Checks (current: ${config.autoUpdate ? "enabled" : "disabled"})`,
|
|
1152
|
+
options: [
|
|
1153
|
+
{
|
|
1154
|
+
value: "enabled",
|
|
1155
|
+
label: "Enabled",
|
|
1156
|
+
hint: "Check npm registry in background every 6 hours for updates."
|
|
1157
|
+
},
|
|
1158
|
+
{
|
|
1159
|
+
value: "disabled",
|
|
1160
|
+
label: "Disabled",
|
|
1161
|
+
hint: "Never perform automatic background update checks."
|
|
1162
|
+
},
|
|
1163
|
+
backOption()
|
|
1164
|
+
],
|
|
1165
|
+
initialValue: config.autoUpdate ? "enabled" : "disabled"
|
|
1166
|
+
});
|
|
1167
|
+
if (isCancel(choice) || choice === BACK_VALUE) return;
|
|
1168
|
+
config.autoUpdate = choice === "enabled";
|
|
1169
|
+
saveConfig(config);
|
|
1170
|
+
log.success(
|
|
1171
|
+
`Automatic update checks: ${config.autoUpdate ? "enabled" : "disabled"}`
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1122
1174
|
async function manageExcludedApps() {
|
|
1123
1175
|
const choice = await select({
|
|
1124
1176
|
message: "Manage excluded apps",
|
|
@@ -1305,6 +1357,11 @@ async function configCommand() {
|
|
|
1305
1357
|
label: "Never quit music apps",
|
|
1306
1358
|
hint: `current: ${config.neverQuitMusic ? "enabled" : "disabled"}`
|
|
1307
1359
|
},
|
|
1360
|
+
{
|
|
1361
|
+
value: "auto-update",
|
|
1362
|
+
label: "Automatic update checks",
|
|
1363
|
+
hint: `current: ${config.autoUpdate ? "enabled" : "disabled"}`
|
|
1364
|
+
},
|
|
1308
1365
|
{
|
|
1309
1366
|
value: "default-select",
|
|
1310
1367
|
label: "Deselect apps by default",
|
|
@@ -1346,6 +1403,7 @@ async function configCommand() {
|
|
|
1346
1403
|
if (choice === "background") await toggleIncludeBackground();
|
|
1347
1404
|
if (choice === "group-background") await toggleGroupBackground();
|
|
1348
1405
|
if (choice === "never-quit-music") await toggleNeverQuitMusic();
|
|
1406
|
+
if (choice === "auto-update") await toggleAutoUpdate();
|
|
1349
1407
|
if (choice === "default-select") await toggleDefaultSelection();
|
|
1350
1408
|
if (choice === "finder") await toggleIncludeFinder();
|
|
1351
1409
|
if (choice === "trash") await toggleIncludeTrash();
|
|
@@ -1357,6 +1415,140 @@ async function configCommand() {
|
|
|
1357
1415
|
printThanks();
|
|
1358
1416
|
return 0;
|
|
1359
1417
|
}
|
|
1418
|
+
var VALID_CONFIG_KEYS = [
|
|
1419
|
+
"exclude",
|
|
1420
|
+
"force",
|
|
1421
|
+
"includeFinder",
|
|
1422
|
+
"includeTrash",
|
|
1423
|
+
"includeBackground",
|
|
1424
|
+
"groupBackground",
|
|
1425
|
+
"defaultSelectAll",
|
|
1426
|
+
"neverQuitMusic",
|
|
1427
|
+
"musicApps",
|
|
1428
|
+
"autoUpdate"
|
|
1429
|
+
];
|
|
1430
|
+
async function handleConfigCli(options = {}) {
|
|
1431
|
+
const action = options.configAction;
|
|
1432
|
+
if (!action) {
|
|
1433
|
+
return await configCommand();
|
|
1434
|
+
}
|
|
1435
|
+
const config = loadConfig();
|
|
1436
|
+
if (action === "show") {
|
|
1437
|
+
if (options.json) {
|
|
1438
|
+
console.log(JSON.stringify(config, null, 2));
|
|
1439
|
+
return 0;
|
|
1440
|
+
}
|
|
1441
|
+
console.log("quitx configuration:");
|
|
1442
|
+
for (const [key, value] of Object.entries(config)) {
|
|
1443
|
+
const displayVal = Array.isArray(value) ? value.length === 0 ? "[]" : `[${value.join(", ")}]` : String(value);
|
|
1444
|
+
console.log(` ${key}: ${displayVal}`);
|
|
1445
|
+
}
|
|
1446
|
+
return 0;
|
|
1447
|
+
}
|
|
1448
|
+
if (action === "get") {
|
|
1449
|
+
if (!options.configKey) {
|
|
1450
|
+
console.error("\u2716 Missing config key. Usage: quitx config get <key>");
|
|
1451
|
+
return 1;
|
|
1452
|
+
}
|
|
1453
|
+
const key = options.configKey;
|
|
1454
|
+
if (!VALID_CONFIG_KEYS.includes(key)) {
|
|
1455
|
+
console.error(
|
|
1456
|
+
`\u2716 Unknown config key: "${options.configKey}". Valid keys: ${VALID_CONFIG_KEYS.join(", ")}`
|
|
1457
|
+
);
|
|
1458
|
+
return 1;
|
|
1459
|
+
}
|
|
1460
|
+
const val = config[key];
|
|
1461
|
+
if (options.json) {
|
|
1462
|
+
console.log(JSON.stringify({ [key]: val }, null, 2));
|
|
1463
|
+
} else if (Array.isArray(val)) {
|
|
1464
|
+
console.log(val.join(", "));
|
|
1465
|
+
} else {
|
|
1466
|
+
console.log(String(val));
|
|
1467
|
+
}
|
|
1468
|
+
return 0;
|
|
1469
|
+
}
|
|
1470
|
+
if (action === "set") {
|
|
1471
|
+
if (!options.configKey) {
|
|
1472
|
+
console.error(
|
|
1473
|
+
"\u2716 Missing config key. Usage: quitx config set <key> <value>"
|
|
1474
|
+
);
|
|
1475
|
+
return 1;
|
|
1476
|
+
}
|
|
1477
|
+
if (options.configValue === void 0) {
|
|
1478
|
+
console.error(
|
|
1479
|
+
`\u2716 Missing value for "${options.configKey}". Usage: quitx config set <key> <value>`
|
|
1480
|
+
);
|
|
1481
|
+
return 1;
|
|
1482
|
+
}
|
|
1483
|
+
const key = options.configKey;
|
|
1484
|
+
if (!VALID_CONFIG_KEYS.includes(key)) {
|
|
1485
|
+
console.error(
|
|
1486
|
+
`\u2716 Unknown config key: "${options.configKey}". Valid keys: ${VALID_CONFIG_KEYS.join(", ")}`
|
|
1487
|
+
);
|
|
1488
|
+
return 1;
|
|
1489
|
+
}
|
|
1490
|
+
const raw = options.configValue.trim();
|
|
1491
|
+
if (key === "force") {
|
|
1492
|
+
const lower = raw.toLowerCase();
|
|
1493
|
+
if (lower === "force" || lower === "true" || lower === "1") {
|
|
1494
|
+
config.force = "force";
|
|
1495
|
+
} else if (lower === "normal" || lower === "false" || lower === "0") {
|
|
1496
|
+
config.force = "normal";
|
|
1497
|
+
} else {
|
|
1498
|
+
console.error(
|
|
1499
|
+
`\u2716 Invalid value for force: "${raw}". Must be "normal" or "force".`
|
|
1500
|
+
);
|
|
1501
|
+
return 1;
|
|
1502
|
+
}
|
|
1503
|
+
} else if (key === "includeFinder" || key === "includeTrash" || key === "includeBackground" || key === "groupBackground" || key === "defaultSelectAll" || key === "neverQuitMusic" || key === "autoUpdate") {
|
|
1504
|
+
const lower = raw.toLowerCase();
|
|
1505
|
+
if (["true", "1", "yes", "on", "enable", "enabled"].includes(lower)) {
|
|
1506
|
+
config[key] = true;
|
|
1507
|
+
} else if (["false", "0", "no", "off", "disable", "disabled"].includes(lower)) {
|
|
1508
|
+
config[key] = false;
|
|
1509
|
+
} else {
|
|
1510
|
+
console.error(
|
|
1511
|
+
`\u2716 Invalid boolean for ${key}: "${raw}". Use true or false.`
|
|
1512
|
+
);
|
|
1513
|
+
return 1;
|
|
1514
|
+
}
|
|
1515
|
+
} else if (key === "exclude" || key === "musicApps") {
|
|
1516
|
+
const items = raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1517
|
+
config[key] = items;
|
|
1518
|
+
}
|
|
1519
|
+
saveConfig(config);
|
|
1520
|
+
if (options.json) {
|
|
1521
|
+
console.log(JSON.stringify({ [key]: config[key] }, null, 2));
|
|
1522
|
+
} else {
|
|
1523
|
+
const displayVal = Array.isArray(config[key]) ? `[${config[key].join(", ")}]` : String(config[key]);
|
|
1524
|
+
console.log(`Set ${key} = ${displayVal}`);
|
|
1525
|
+
}
|
|
1526
|
+
return 0;
|
|
1527
|
+
}
|
|
1528
|
+
if (action === "reset") {
|
|
1529
|
+
if (!options.yes) {
|
|
1530
|
+
const confirm2 = await select({
|
|
1531
|
+
message: "Reset all settings to defaults?",
|
|
1532
|
+
options: [
|
|
1533
|
+
{ value: "yes", label: "Yes, reset all" },
|
|
1534
|
+
{ value: "no", label: "No, keep current settings" }
|
|
1535
|
+
],
|
|
1536
|
+
initialValue: "no"
|
|
1537
|
+
});
|
|
1538
|
+
if (isCancel(confirm2) || confirm2 !== "yes") {
|
|
1539
|
+
return 0;
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
saveConfig({ ...DEFAULT_CONFIG, exclude: [], musicApps: [] });
|
|
1543
|
+
if (options.json) {
|
|
1544
|
+
console.log(JSON.stringify({ reset: true }, null, 2));
|
|
1545
|
+
} else {
|
|
1546
|
+
log.success("Config reset to defaults.");
|
|
1547
|
+
}
|
|
1548
|
+
return 0;
|
|
1549
|
+
}
|
|
1550
|
+
return 0;
|
|
1551
|
+
}
|
|
1360
1552
|
async function manageExcludeInteractive() {
|
|
1361
1553
|
showIntro();
|
|
1362
1554
|
const choice = await select({
|
|
@@ -1554,15 +1746,21 @@ async function interactiveCommand(options = {}) {
|
|
|
1554
1746
|
}
|
|
1555
1747
|
if (!options.json) {
|
|
1556
1748
|
s.start(
|
|
1557
|
-
`Quitting ${targetApps.length} ${targetApps.length === 1 ? "app" : "apps"}...`
|
|
1749
|
+
options.dryRun ? `Simulating quit for ${targetApps.length} ${targetApps.length === 1 ? "app" : "apps"}...` : `Quitting ${targetApps.length} ${targetApps.length === 1 ? "app" : "apps"}...`
|
|
1558
1750
|
);
|
|
1559
1751
|
}
|
|
1560
|
-
const results = await quitApps(targetApps, {
|
|
1752
|
+
const results = await quitApps(targetApps, {
|
|
1753
|
+
force: useForce,
|
|
1754
|
+
dryRun: options.dryRun
|
|
1755
|
+
});
|
|
1561
1756
|
if (!options.json) {
|
|
1562
|
-
s.stop("Quitting complete");
|
|
1563
|
-
renderResults(results);
|
|
1757
|
+
s.stop(options.dryRun ? "Dry run complete" : "Quitting complete");
|
|
1758
|
+
renderResults(results, options.dryRun);
|
|
1564
1759
|
const successCount = results.filter((r) => r.success).length;
|
|
1565
|
-
|
|
1760
|
+
const actionVerb = options.dryRun ? "Would quit" : "Quit";
|
|
1761
|
+
showOutro(
|
|
1762
|
+
`Done. ${actionVerb} ${successCount} of ${targetApps.length} apps.`
|
|
1763
|
+
);
|
|
1566
1764
|
printThanks(options);
|
|
1567
1765
|
} else {
|
|
1568
1766
|
console.log(
|
|
@@ -1599,6 +1797,199 @@ async function listCommand(options = {}) {
|
|
|
1599
1797
|
renderList(apps, options.json);
|
|
1600
1798
|
return 0;
|
|
1601
1799
|
}
|
|
1800
|
+
var CHECK_INTERVAL_MS = 6 * 60 * 60 * 1e3;
|
|
1801
|
+
var MANUAL_RATE_LIMIT_MS = 60 * 1e3;
|
|
1802
|
+
var REQUEST_TIMEOUT_MS = 1500;
|
|
1803
|
+
var REGISTRY_URL = "https://registry.npmjs.org/@coreify%2Fquitx/latest";
|
|
1804
|
+
var FALLBACK_REGISTRY_URL = "https://registry.npmjs.org/quitx/latest";
|
|
1805
|
+
var RELEASE_URL = "https://github.com/coreify/quitx/releases/latest";
|
|
1806
|
+
function updateCacheDirectory() {
|
|
1807
|
+
const configured = process.env.XDG_CACHE_HOME;
|
|
1808
|
+
if (configured) return join(configured, "quitx");
|
|
1809
|
+
return join(homedir(), ".quitx");
|
|
1810
|
+
}
|
|
1811
|
+
function parseVersion(version) {
|
|
1812
|
+
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([\dA-Za-z.-]+))?(?:\+[\dA-Za-z.-]+)?$/u.exec(
|
|
1813
|
+
version.trim()
|
|
1814
|
+
);
|
|
1815
|
+
if (!match) return void 0;
|
|
1816
|
+
const numbers = [
|
|
1817
|
+
Number(match[1]),
|
|
1818
|
+
Number(match[2]),
|
|
1819
|
+
Number(match[3])
|
|
1820
|
+
];
|
|
1821
|
+
return match[4] ? { numbers, prerelease: match[4].split(".") } : { numbers };
|
|
1822
|
+
}
|
|
1823
|
+
function compareVersions(left, right) {
|
|
1824
|
+
const leftVersion = parseVersion(left);
|
|
1825
|
+
const rightVersion = parseVersion(right);
|
|
1826
|
+
if (!leftVersion || !rightVersion) return 0;
|
|
1827
|
+
for (let index = 0; index < 3; index++) {
|
|
1828
|
+
const difference = leftVersion.numbers[index] - rightVersion.numbers[index];
|
|
1829
|
+
if (difference !== 0) return Math.sign(difference);
|
|
1830
|
+
}
|
|
1831
|
+
if (!leftVersion.prerelease && !rightVersion.prerelease) return 0;
|
|
1832
|
+
if (!leftVersion.prerelease) return 1;
|
|
1833
|
+
if (!rightVersion.prerelease) return -1;
|
|
1834
|
+
const length = Math.max(
|
|
1835
|
+
leftVersion.prerelease.length,
|
|
1836
|
+
rightVersion.prerelease.length
|
|
1837
|
+
);
|
|
1838
|
+
for (let index = 0; index < length; index++) {
|
|
1839
|
+
const leftPart = leftVersion.prerelease[index];
|
|
1840
|
+
const rightPart = rightVersion.prerelease[index];
|
|
1841
|
+
if (leftPart === void 0) return -1;
|
|
1842
|
+
if (rightPart === void 0) return 1;
|
|
1843
|
+
if (leftPart === rightPart) continue;
|
|
1844
|
+
const leftNumber = /^\d+$/u.test(leftPart) ? Number(leftPart) : void 0;
|
|
1845
|
+
const rightNumber = /^\d+$/u.test(rightPart) ? Number(rightPart) : void 0;
|
|
1846
|
+
if (leftNumber !== void 0 && rightNumber !== void 0) {
|
|
1847
|
+
return Math.sign(leftNumber - rightNumber);
|
|
1848
|
+
}
|
|
1849
|
+
if (leftNumber !== void 0) return -1;
|
|
1850
|
+
if (rightNumber !== void 0) return 1;
|
|
1851
|
+
return leftPart.localeCompare(rightPart);
|
|
1852
|
+
}
|
|
1853
|
+
return 0;
|
|
1854
|
+
}
|
|
1855
|
+
async function readCache(path) {
|
|
1856
|
+
try {
|
|
1857
|
+
const value = JSON.parse(await readFile(path, "utf8"));
|
|
1858
|
+
if (typeof value !== "object" || value === null) return {};
|
|
1859
|
+
const record = value;
|
|
1860
|
+
return {
|
|
1861
|
+
...typeof record.checkedAt === "number" && Number.isFinite(record.checkedAt) ? { checkedAt: record.checkedAt } : {},
|
|
1862
|
+
...typeof record.ignoredVersion === "string" && parseVersion(record.ignoredVersion) ? { ignoredVersion: record.ignoredVersion } : {},
|
|
1863
|
+
...typeof record.latestVersion === "string" && parseVersion(record.latestVersion) ? { latestVersion: record.latestVersion } : {}
|
|
1864
|
+
};
|
|
1865
|
+
} catch {
|
|
1866
|
+
return {};
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
async function writeCache(path, value) {
|
|
1870
|
+
try {
|
|
1871
|
+
await mkdir(dirname(path), { recursive: true });
|
|
1872
|
+
await writeFile(path, JSON.stringify(value), { mode: 384 });
|
|
1873
|
+
} catch {
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
async function fetchLatestVersion(fetcher = fetch) {
|
|
1877
|
+
const urls = [REGISTRY_URL, FALLBACK_REGISTRY_URL];
|
|
1878
|
+
for (const url of urls) {
|
|
1879
|
+
const controller = new AbortController();
|
|
1880
|
+
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
|
|
1881
|
+
timer.unref();
|
|
1882
|
+
try {
|
|
1883
|
+
const response = await fetcher(url, {
|
|
1884
|
+
headers: { accept: "application/json" },
|
|
1885
|
+
signal: controller.signal
|
|
1886
|
+
});
|
|
1887
|
+
if (!response.ok) continue;
|
|
1888
|
+
const value = await response.json();
|
|
1889
|
+
if (typeof value === "object" && value !== null && "version" in value && typeof value.version === "string" && parseVersion(value.version)) {
|
|
1890
|
+
return value.version;
|
|
1891
|
+
}
|
|
1892
|
+
} catch {
|
|
1893
|
+
} finally {
|
|
1894
|
+
clearTimeout(timer);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
throw new Error("Unable to fetch latest version from npm registry");
|
|
1898
|
+
}
|
|
1899
|
+
async function checkForUpdate(currentVersion, options = {}) {
|
|
1900
|
+
if (!parseVersion(currentVersion)) return void 0;
|
|
1901
|
+
const now = options.now ?? Date.now();
|
|
1902
|
+
const cachePath = join(
|
|
1903
|
+
options.cacheDirectory ?? updateCacheDirectory(),
|
|
1904
|
+
"update.json"
|
|
1905
|
+
);
|
|
1906
|
+
const cache = await readCache(cachePath);
|
|
1907
|
+
let latestVersion = cache.latestVersion;
|
|
1908
|
+
if (options.force || !cache.checkedAt || now - cache.checkedAt >= CHECK_INTERVAL_MS) {
|
|
1909
|
+
try {
|
|
1910
|
+
latestVersion = await fetchLatestVersion(options.fetcher ?? fetch);
|
|
1911
|
+
await writeCache(cachePath, {
|
|
1912
|
+
...cache,
|
|
1913
|
+
checkedAt: now,
|
|
1914
|
+
latestVersion
|
|
1915
|
+
});
|
|
1916
|
+
} catch {
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
if (!latestVersion || latestVersion === cache.ignoredVersion || compareVersions(latestVersion, currentVersion) <= 0) {
|
|
1920
|
+
return void 0;
|
|
1921
|
+
}
|
|
1922
|
+
return {
|
|
1923
|
+
currentVersion,
|
|
1924
|
+
latestVersion,
|
|
1925
|
+
releaseUrl: RELEASE_URL
|
|
1926
|
+
};
|
|
1927
|
+
}
|
|
1928
|
+
async function checkUpdateManually(currentVersion, options = {}) {
|
|
1929
|
+
const now = options.now ?? Date.now();
|
|
1930
|
+
const cachePath = join(
|
|
1931
|
+
options.cacheDirectory ?? updateCacheDirectory(),
|
|
1932
|
+
"update.json"
|
|
1933
|
+
);
|
|
1934
|
+
const cache = await readCache(cachePath);
|
|
1935
|
+
let latestVersion = cache.latestVersion;
|
|
1936
|
+
const isRateLimited = Boolean(
|
|
1937
|
+
!options.force && cache.checkedAt && now - cache.checkedAt < MANUAL_RATE_LIMIT_MS
|
|
1938
|
+
);
|
|
1939
|
+
if (!isRateLimited) {
|
|
1940
|
+
try {
|
|
1941
|
+
latestVersion = await fetchLatestVersion(options.fetcher ?? fetch);
|
|
1942
|
+
await writeCache(cachePath, {
|
|
1943
|
+
...cache,
|
|
1944
|
+
checkedAt: now,
|
|
1945
|
+
latestVersion
|
|
1946
|
+
});
|
|
1947
|
+
} catch {
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
const effectiveLatest = latestVersion ?? currentVersion;
|
|
1951
|
+
const updateAvailable = Boolean(
|
|
1952
|
+
latestVersion && compareVersions(latestVersion, currentVersion) > 0
|
|
1953
|
+
);
|
|
1954
|
+
return {
|
|
1955
|
+
currentVersion,
|
|
1956
|
+
latestVersion: effectiveLatest,
|
|
1957
|
+
rateLimited: isRateLimited,
|
|
1958
|
+
releaseUrl: RELEASE_URL,
|
|
1959
|
+
updateAvailable
|
|
1960
|
+
};
|
|
1961
|
+
}
|
|
1962
|
+
async function ignoreUpdateVersion(version, cacheDir = updateCacheDirectory()) {
|
|
1963
|
+
const cachePath = join(cacheDir, "update.json");
|
|
1964
|
+
const cache = await readCache(cachePath);
|
|
1965
|
+
await writeCache(cachePath, { ...cache, ignoredVersion: version });
|
|
1966
|
+
}
|
|
1967
|
+
function installUpdate(version, spawner = spawn) {
|
|
1968
|
+
if (!parseVersion(version)) {
|
|
1969
|
+
return Promise.reject(new Error(`Invalid update version: ${version}`));
|
|
1970
|
+
}
|
|
1971
|
+
return new Promise((resolve, reject) => {
|
|
1972
|
+
const pkg = `@coreify/quitx@${version}`;
|
|
1973
|
+
const child = spawner("npm", ["install", "--global", pkg], {
|
|
1974
|
+
stdio: "inherit",
|
|
1975
|
+
windowsHide: true
|
|
1976
|
+
});
|
|
1977
|
+
child.once("error", reject);
|
|
1978
|
+
child.once("exit", (code, signal) => {
|
|
1979
|
+
if (code === 0) resolve();
|
|
1980
|
+
else {
|
|
1981
|
+
reject(
|
|
1982
|
+
new Error(
|
|
1983
|
+
signal ? `Update stopped by ${signal}` : `npm install exited with code ${code ?? "unknown"}`
|
|
1984
|
+
)
|
|
1985
|
+
);
|
|
1986
|
+
}
|
|
1987
|
+
});
|
|
1988
|
+
});
|
|
1989
|
+
}
|
|
1990
|
+
function updateNotice(update) {
|
|
1991
|
+
return `Update available: ${update.currentVersion} \u2192 ${update.latestVersion}. Run: npm install --global @coreify/quitx@latest`;
|
|
1992
|
+
}
|
|
1602
1993
|
|
|
1603
1994
|
// src/utils/platform.ts
|
|
1604
1995
|
function isMacOS(platform = process.platform) {
|
|
@@ -1636,10 +2027,42 @@ function parseCliArgs(args) {
|
|
|
1636
2027
|
for (let i = 0; i < args.length; i++) {
|
|
1637
2028
|
const arg = args[i];
|
|
1638
2029
|
if (!arg) continue;
|
|
1639
|
-
if (arg === "
|
|
2030
|
+
if (arg === "--dry-run") {
|
|
2031
|
+
options.dryRun = true;
|
|
2032
|
+
} else if (arg === "config") {
|
|
1640
2033
|
options.manageConfig = true;
|
|
2034
|
+
const next = args[i + 1];
|
|
2035
|
+
if (next && !next.startsWith("-") && ["show", "get", "set", "reset"].includes(next.toLowerCase())) {
|
|
2036
|
+
options.configAction = next.toLowerCase();
|
|
2037
|
+
i++;
|
|
2038
|
+
if (options.configAction === "get") {
|
|
2039
|
+
const key = args[++i];
|
|
2040
|
+
if (key && !key.startsWith("-")) {
|
|
2041
|
+
options.configKey = key;
|
|
2042
|
+
} else if (key) {
|
|
2043
|
+
i--;
|
|
2044
|
+
}
|
|
2045
|
+
} else if (options.configAction === "set") {
|
|
2046
|
+
const key = args[++i];
|
|
2047
|
+
if (key && !key.startsWith("-")) {
|
|
2048
|
+
options.configKey = key;
|
|
2049
|
+
const val = args[++i];
|
|
2050
|
+
if (val && !val.startsWith("-")) {
|
|
2051
|
+
options.configValue = val;
|
|
2052
|
+
} else if (val) {
|
|
2053
|
+
i--;
|
|
2054
|
+
}
|
|
2055
|
+
} else if (key) {
|
|
2056
|
+
i--;
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
1641
2060
|
} else if (arg === "exclude") {
|
|
1642
2061
|
options.manageExclude = true;
|
|
2062
|
+
} else if (arg === "check-update" || arg === "--check-update") {
|
|
2063
|
+
options.checkUpdate = true;
|
|
2064
|
+
} else if (arg === "--no-update-check") {
|
|
2065
|
+
options.noUpdateCheck = true;
|
|
1643
2066
|
} else if (arg === "-a" || arg === "--all") {
|
|
1644
2067
|
options.all = true;
|
|
1645
2068
|
} else if (arg === "-y" || arg === "--yes") {
|
|
@@ -1683,7 +2106,13 @@ function parseCliArgs(args) {
|
|
|
1683
2106
|
}
|
|
1684
2107
|
}
|
|
1685
2108
|
if (rawPositional.length > 0) {
|
|
1686
|
-
if (options.
|
|
2109
|
+
if (options.manageConfig) {
|
|
2110
|
+
if (!options.configAction) {
|
|
2111
|
+
throw new Error(
|
|
2112
|
+
`Unknown config command: "${rawPositional[0]}". Usage: quitx config [show|get|set|reset]`
|
|
2113
|
+
);
|
|
2114
|
+
}
|
|
2115
|
+
} else if (options.manageExclude) {
|
|
1687
2116
|
if (rawPositional.length > 1 && !rawPositional.some((a) => a.includes(","))) {
|
|
1688
2117
|
throw new Error(
|
|
1689
2118
|
"Multiple exclude applications must be comma-separated, not space-separated. Example: quitx exclude Spotify,Discord"
|
|
@@ -1715,6 +2144,130 @@ function parseCliArgs(args) {
|
|
|
1715
2144
|
}
|
|
1716
2145
|
return options;
|
|
1717
2146
|
}
|
|
2147
|
+
async function handleManualUpdateCheck(version, options) {
|
|
2148
|
+
const isInteractive = !options.json && Boolean(process.stdout.isTTY && process.stdin.isTTY);
|
|
2149
|
+
if (options.json) {
|
|
2150
|
+
const result2 = await checkUpdateManually(version, { force: options.force });
|
|
2151
|
+
console.log(JSON.stringify(result2, null, 2));
|
|
2152
|
+
return 0;
|
|
2153
|
+
}
|
|
2154
|
+
if (isInteractive) {
|
|
2155
|
+
showIntro();
|
|
2156
|
+
const checkSpinner = spinner();
|
|
2157
|
+
checkSpinner.start("Checking npm registry for updates");
|
|
2158
|
+
const result2 = await checkUpdateManually(version, { force: options.force });
|
|
2159
|
+
if (result2.rateLimited) {
|
|
2160
|
+
checkSpinner.stop(
|
|
2161
|
+
`Checked recently (rate limited, 60s cooldown). Latest: v${result2.latestVersion}`
|
|
2162
|
+
);
|
|
2163
|
+
} else if (result2.updateAvailable) {
|
|
2164
|
+
checkSpinner.stop(
|
|
2165
|
+
`Update available: ${result2.currentVersion} \u2192 ${result2.latestVersion}`
|
|
2166
|
+
);
|
|
2167
|
+
} else {
|
|
2168
|
+
checkSpinner.stop(`quitx is up to date (v${result2.currentVersion})`);
|
|
2169
|
+
}
|
|
2170
|
+
if (result2.updateAvailable) {
|
|
2171
|
+
console.log(
|
|
2172
|
+
"\n Upgrade Available: Run npm install --global @coreify/quitx@latest\n"
|
|
2173
|
+
);
|
|
2174
|
+
const answer = await select({
|
|
2175
|
+
message: `Install v${result2.latestVersion} now?`,
|
|
2176
|
+
options: [
|
|
2177
|
+
{ value: "install", label: "Install update now" },
|
|
2178
|
+
{ value: "skip", label: "Skip for now" }
|
|
2179
|
+
],
|
|
2180
|
+
initialValue: "install"
|
|
2181
|
+
});
|
|
2182
|
+
if (isCancel(answer) || answer === "skip") {
|
|
2183
|
+
printThanks(options);
|
|
2184
|
+
return 0;
|
|
2185
|
+
}
|
|
2186
|
+
if (answer === "install") {
|
|
2187
|
+
console.log(`Updating to ${result2.latestVersion}...`);
|
|
2188
|
+
try {
|
|
2189
|
+
await installUpdate(result2.latestVersion);
|
|
2190
|
+
showOutro(
|
|
2191
|
+
`Updated to ${result2.latestVersion}. Restart quitx to use it.`
|
|
2192
|
+
);
|
|
2193
|
+
} catch (err) {
|
|
2194
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2195
|
+
console.error(`Failed to install update: ${msg}`);
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
printThanks(options);
|
|
2200
|
+
return 0;
|
|
2201
|
+
}
|
|
2202
|
+
const result = await checkUpdateManually(version, { force: options.force });
|
|
2203
|
+
if (result.updateAvailable) {
|
|
2204
|
+
console.log(
|
|
2205
|
+
`Update available: ${result.currentVersion} \u2192 ${result.latestVersion}`
|
|
2206
|
+
);
|
|
2207
|
+
console.log("Run: npm install --global @coreify/quitx@latest");
|
|
2208
|
+
} else {
|
|
2209
|
+
console.log(`quitx is up to date (${result.currentVersion})`);
|
|
2210
|
+
}
|
|
2211
|
+
if (result.rateLimited) {
|
|
2212
|
+
console.log(
|
|
2213
|
+
"(Checked recently with 60s rate limit; use --force to bypass)"
|
|
2214
|
+
);
|
|
2215
|
+
}
|
|
2216
|
+
printThanks(options);
|
|
2217
|
+
return 0;
|
|
2218
|
+
}
|
|
2219
|
+
async function handleAutoUpdateCheck(currentVersion, options) {
|
|
2220
|
+
if (options.noUpdateCheck || options.checkUpdate || options.help || options.version || options.dryRun) {
|
|
2221
|
+
return;
|
|
2222
|
+
}
|
|
2223
|
+
const config = loadConfig();
|
|
2224
|
+
if (config.autoUpdate === false) {
|
|
2225
|
+
return;
|
|
2226
|
+
}
|
|
2227
|
+
try {
|
|
2228
|
+
const update = await checkForUpdate(currentVersion);
|
|
2229
|
+
if (!update) return;
|
|
2230
|
+
if (options.json || options.yes || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
2231
|
+
console.error(updateNotice(update));
|
|
2232
|
+
return;
|
|
2233
|
+
}
|
|
2234
|
+
console.log(
|
|
2235
|
+
`
|
|
2236
|
+
Update available \xB7 ${update.currentVersion} \u2192 ${update.latestVersion}
|
|
2237
|
+
Release notes: ${update.releaseUrl}`
|
|
2238
|
+
);
|
|
2239
|
+
const action = await select({
|
|
2240
|
+
message: "Update quitx?",
|
|
2241
|
+
options: [
|
|
2242
|
+
{ value: "update", label: "Update now", hint: "npm install --global" },
|
|
2243
|
+
{ value: "skip", label: "Skip" },
|
|
2244
|
+
{
|
|
2245
|
+
value: "ignore",
|
|
2246
|
+
label: "Skip this version",
|
|
2247
|
+
hint: `hide ${update.latestVersion}`
|
|
2248
|
+
}
|
|
2249
|
+
],
|
|
2250
|
+
initialValue: "update"
|
|
2251
|
+
});
|
|
2252
|
+
if (isCancel(action) || action === "skip") {
|
|
2253
|
+
return;
|
|
2254
|
+
}
|
|
2255
|
+
if (action === "ignore") {
|
|
2256
|
+
await ignoreUpdateVersion(update.latestVersion);
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
console.log(`Updating to ${update.latestVersion}...`);
|
|
2260
|
+
try {
|
|
2261
|
+
await installUpdate(update.latestVersion);
|
|
2262
|
+
showOutro(`Updated to ${update.latestVersion}. Restart quitx to use it.`);
|
|
2263
|
+
process.exit(0);
|
|
2264
|
+
} catch (err) {
|
|
2265
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2266
|
+
console.error(`Failed to install update: ${msg}`);
|
|
2267
|
+
}
|
|
2268
|
+
} catch {
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
1718
2271
|
async function main(argv = process.argv.slice(2)) {
|
|
1719
2272
|
if (!isMacOS()) {
|
|
1720
2273
|
console.error(
|
|
@@ -1742,15 +2295,21 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1742
2295
|
printThanks(options);
|
|
1743
2296
|
return 0;
|
|
1744
2297
|
}
|
|
2298
|
+
const version = await getPackageVersion();
|
|
1745
2299
|
if (options.version) {
|
|
1746
|
-
const version = await getPackageVersion();
|
|
1747
2300
|
renderVersion(version);
|
|
1748
2301
|
printThanks(options);
|
|
1749
2302
|
return 0;
|
|
1750
2303
|
}
|
|
2304
|
+
if (options.checkUpdate) {
|
|
2305
|
+
return await handleManualUpdateCheck(version, options);
|
|
2306
|
+
}
|
|
2307
|
+
if (!options.noUpdateCheck && !options.json) {
|
|
2308
|
+
await handleAutoUpdateCheck(version, options);
|
|
2309
|
+
}
|
|
1751
2310
|
try {
|
|
1752
2311
|
if (options.manageConfig) {
|
|
1753
|
-
return await
|
|
2312
|
+
return await handleConfigCli(options);
|
|
1754
2313
|
}
|
|
1755
2314
|
if (options.manageExclude || options.exclude && options.exclude.length > 0) {
|
|
1756
2315
|
return await excludeCommand(options);
|
|
@@ -1784,4 +2343,4 @@ if (isMain) {
|
|
|
1784
2343
|
});
|
|
1785
2344
|
}
|
|
1786
2345
|
|
|
1787
|
-
export { getPackageVersion, main, parseCliArgs };
|
|
2346
|
+
export { getPackageVersion, handleAutoUpdateCheck, handleManualUpdateCheck, main, parseCliArgs };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coreify/quitx",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A macOS CLI for viewing running GUI applications and quitting them properly.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"scripts": {
|
|
36
36
|
"build": "tsup",
|
|
37
37
|
"build:watch": "tsup --watch",
|
|
38
|
-
"check": "npm run format:check && npm run lint && npm run typecheck && npm run test:coverage && npm run build",
|
|
38
|
+
"check": "npm run format && npm run format:check && npm run lint && npm run typecheck && npm run test:coverage && npm run build",
|
|
39
39
|
"format": "prettier --write .",
|
|
40
40
|
"format:check": "prettier --check .",
|
|
41
41
|
"lint": "eslint .",
|