@kisuke/cli 1.1.75-dev.84.1 → 1.1.77-dev.86.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/preuninstall.mjs +74 -50
package/package.json
CHANGED
package/scripts/preuninstall.mjs
CHANGED
|
@@ -26,6 +26,50 @@ const executable = process.platform === 'win32'
|
|
|
26
26
|
? path.join(bundleDir, 'kisuke.cmd')
|
|
27
27
|
: path.join(bundleDir, 'kisuke');
|
|
28
28
|
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Windows: kill the daemon BEFORE calling `kisuke uninstall`, so that by the
|
|
31
|
+
// time the uninstall command spawns its own (short-lived) node.exe the daemon
|
|
32
|
+
// is already gone and we don't fight two lock sources at once.
|
|
33
|
+
//
|
|
34
|
+
// Process tree started by Task Scheduler:
|
|
35
|
+
// wscript.exe → cmd.exe (daemon-task-runner.cmd) → node.exe (daemon.js)
|
|
36
|
+
//
|
|
37
|
+
// We need to kill the entire tree, not just node.exe.
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
if (process.platform === 'win32') {
|
|
40
|
+
const bundledNodeExe = path.join(bundleDir, 'bin', 'node.exe');
|
|
41
|
+
|
|
42
|
+
// Kill daemon by PID from pid file (fastest path)
|
|
43
|
+
try {
|
|
44
|
+
const kisukeDir = path.join(process.env.USERPROFILE || '', '.kisuke');
|
|
45
|
+
const pidFile = path.join(kisukeDir, 'kisuke.pid');
|
|
46
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
|
|
47
|
+
if (pid > 0) {
|
|
48
|
+
// taskkill /T kills the entire process tree rooted at the PID
|
|
49
|
+
spawnSync('taskkill', ['/F', '/T', '/PID', String(pid)], {
|
|
50
|
+
stdio: 'ignore',
|
|
51
|
+
timeout: 5_000,
|
|
52
|
+
});
|
|
53
|
+
try { fs.unlinkSync(pidFile); } catch {}
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
// No PID file or process already gone — fine
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Kill any remaining processes using the bundled node.exe.
|
|
60
|
+
// Catches orphaned daemon processes not tracked by the PID file.
|
|
61
|
+
try {
|
|
62
|
+
const escaped = bundledNodeExe.replace(/'/g, "''");
|
|
63
|
+
spawnSync('powershell', [
|
|
64
|
+
'-NoProfile', '-NonInteractive', '-Command',
|
|
65
|
+
`Get-Process | Where-Object { $_.Path -eq '${escaped}' } | Stop-Process -Force -ErrorAction SilentlyContinue`,
|
|
66
|
+
], { stdio: 'ignore', timeout: 10_000 });
|
|
67
|
+
} catch {}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Run `kisuke uninstall` to remove the scheduled task / launchd plist /
|
|
71
|
+
// systemd unit. On Windows the daemon is already dead at this point, so
|
|
72
|
+
// the only node.exe invocation here is this short-lived CLI command.
|
|
29
73
|
if (fs.existsSync(executable)) {
|
|
30
74
|
const result = spawnSync(executable, ['uninstall'], {
|
|
31
75
|
stdio: 'pipe',
|
|
@@ -41,61 +85,41 @@ if (fs.existsSync(executable)) {
|
|
|
41
85
|
console.warn(`kisuke uninstall warning: ${stderr}`);
|
|
42
86
|
}
|
|
43
87
|
}
|
|
88
|
+
}
|
|
44
89
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
// Stage 1: Kill daemon by PID from pid file
|
|
54
|
-
try {
|
|
55
|
-
const kisukeDir = path.join(process.env.USERPROFILE || '', '.kisuke');
|
|
56
|
-
const pidFile = path.join(kisukeDir, 'kisuke.pid');
|
|
57
|
-
const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
|
|
58
|
-
if (pid > 0) {
|
|
59
|
-
try { process.kill(pid, 'SIGTERM'); } catch {}
|
|
60
|
-
// Always follow up with taskkill /F — process.kill on Windows
|
|
61
|
-
// can succeed without the process fully terminating yet
|
|
62
|
-
spawnSync('taskkill', ['/F', '/PID', String(pid)], {
|
|
63
|
-
stdio: 'ignore',
|
|
64
|
-
timeout: 5_000,
|
|
65
|
-
});
|
|
66
|
-
try { fs.unlinkSync(pidFile); } catch {}
|
|
67
|
-
}
|
|
68
|
-
} catch {
|
|
69
|
-
// No PID file or process already gone — fine
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Stage 2: Kill any remaining processes using the bundled node.exe.
|
|
73
|
-
// Catches orphaned daemon processes not tracked by the PID file.
|
|
74
|
-
try {
|
|
75
|
-
const escaped = bundledNodeExe.replace(/'/g, "''");
|
|
76
|
-
spawnSync('powershell', [
|
|
77
|
-
'-NoProfile', '-NonInteractive', '-Command',
|
|
78
|
-
`Get-Process | Where-Object { $_.Path -eq '${escaped}' } | Stop-Process -Force -ErrorAction SilentlyContinue`,
|
|
79
|
-
], { stdio: 'ignore', timeout: 10_000 });
|
|
80
|
-
} catch {}
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// Windows: proactively remove node.exe so npm's cleanup phase can't fail on
|
|
92
|
+
// it. After the CLI command above, the only lock source is the brief kernel
|
|
93
|
+
// hold while Windows tears down the image mapping (and possibly Defender).
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
if (process.platform === 'win32') {
|
|
96
|
+
const bundledNodeExe = path.join(bundleDir, 'bin', 'node.exe');
|
|
81
97
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
98
|
+
if (fs.existsSync(bundledNodeExe)) {
|
|
99
|
+
let removed = false;
|
|
100
|
+
const deadline = Date.now() + 10_000;
|
|
101
|
+
while (Date.now() < deadline) {
|
|
102
|
+
try {
|
|
103
|
+
fs.unlinkSync(bundledNodeExe);
|
|
104
|
+
removed = true;
|
|
105
|
+
break;
|
|
106
|
+
} catch (e) {
|
|
107
|
+
if (e.code !== 'EBUSY' && e.code !== 'EPERM' && e.code !== 'EACCES') {
|
|
91
108
|
break;
|
|
92
|
-
} catch (e) {
|
|
93
|
-
if (e.code !== 'EBUSY' && e.code !== 'EPERM' && e.code !== 'EACCES') {
|
|
94
|
-
break; // Unexpected error — don't keep retrying
|
|
95
|
-
}
|
|
96
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 500);
|
|
97
109
|
}
|
|
110
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 500);
|
|
98
111
|
}
|
|
99
112
|
}
|
|
113
|
+
// Last resort: rename into %TEMP% — rename often succeeds even while
|
|
114
|
+
// the file is locked because most AV locks use FILE_SHARE_DELETE.
|
|
115
|
+
if (!removed && fs.existsSync(bundledNodeExe)) {
|
|
116
|
+
try {
|
|
117
|
+
const tmp = path.join(
|
|
118
|
+
process.env.TEMP || process.env.TMP || '.',
|
|
119
|
+
`kisuke-node-${Date.now()}.exe.old`,
|
|
120
|
+
);
|
|
121
|
+
fs.renameSync(bundledNodeExe, tmp);
|
|
122
|
+
} catch {}
|
|
123
|
+
}
|
|
100
124
|
}
|
|
101
125
|
}
|