@abdoknbgit/tau-installer
Advanced tools
+50
-9
@@ -28,3 +28,3 @@ import { spawn } from "node:child_process"; | ||
| const WINDOWS_BIN_EXTENSIONS = Object.freeze(["", ".cmd", ".ps1"]); | ||
| const WINDOWS_TERMINATION_GRACE_MS = 5_000; | ||
| const TERMINATION_GRACE_MS = 5_000; | ||
@@ -315,4 +315,5 @@ const EXACT_SEMVER_PATTERN = | ||
| env, | ||
| processKillImpl, | ||
| treeKillSpawnImpl, | ||
| terminationGraceMs = WINDOWS_TERMINATION_GRACE_MS, | ||
| terminationGraceMs = TERMINATION_GRACE_MS, | ||
| setTimeoutImpl = setTimeout, | ||
@@ -327,2 +328,4 @@ clearTimeoutImpl = clearTimeout, | ||
| let windowsForceStarted = false; | ||
| let posixTerminationStarted = false; | ||
| let posixForceStarted = false; | ||
@@ -372,2 +375,26 @@ const killDirectly = (signal) => { | ||
| const killPosixGroup = (signal) => { | ||
| if (!Number.isInteger(child.pid) || child.pid <= 0) return false; | ||
| try { | ||
| // POSIX accepts a negative PID as a process-group ID. Foreground npm | ||
| // commands are spawned detached below so npm and lifecycle descendants | ||
| // share this dedicated group. | ||
| processKillImpl(-child.pid, signal); | ||
| return true; | ||
| } catch (error) { | ||
| if (error?.code === "ESRCH") return true; | ||
| stderr.write(`Unable to terminate npm's process group: ${error.message}\n`); | ||
| return false; | ||
| } | ||
| }; | ||
| const forcePosixGroup = () => { | ||
| if (posixForceStarted) return; | ||
| posixForceStarted = true; | ||
| if (!killPosixGroup("SIGKILL")) { | ||
| killDirectly("SIGKILL"); | ||
| } | ||
| }; | ||
| for (const signal of signals) { | ||
@@ -391,10 +418,14 @@ const handler = () => { | ||
| killDirectly(signal); | ||
| if (posixTerminationStarted) { | ||
| forcePosixGroup(); | ||
| return; | ||
| } | ||
| posixTerminationStarted = true; | ||
| if (!forceTimer) { | ||
| forceTimer = setTimeoutImpl(() => { | ||
| killDirectly("SIGKILL"); | ||
| }, 5_000); | ||
| forceTimer.unref?.(); | ||
| if (!killPosixGroup("SIGTERM")) { | ||
| killDirectly("SIGTERM"); | ||
| } | ||
| forceTimer = setTimeoutImpl(forcePosixGroup, terminationGraceMs); | ||
| forceTimer.unref?.(); | ||
| }; | ||
@@ -406,2 +437,7 @@ handlers.set(signal, handler); | ||
| return () => { | ||
| // npm can exit after SIGTERM while a lifecycle descendant remains alive. | ||
| // Before dropping the timer, kill anything still using the process group. | ||
| if (platform !== "win32" && posixTerminationStarted) { | ||
| forcePosixGroup(); | ||
| } | ||
| if (forceTimer) clearTimeoutImpl(forceTimer); | ||
@@ -601,2 +637,3 @@ for (const [signal, handler] of handlers) { | ||
| platform, | ||
| processKillImpl, | ||
| spawnImpl, | ||
@@ -620,2 +657,3 @@ treeKillSpawnImpl, | ||
| cwd: workingDirectory, | ||
| detached: platform !== "win32", | ||
| shell: false, | ||
@@ -640,2 +678,3 @@ stdio: "inherit", | ||
| env, | ||
| processKillImpl, | ||
| treeKillSpawnImpl, | ||
@@ -679,5 +718,6 @@ terminationGraceMs, | ||
| spawnImpl = spawn, | ||
| processKillImpl = process.kill, | ||
| treeKillSpawnImpl = spawn, | ||
| signalSource = process, | ||
| terminationGraceMs = WINDOWS_TERMINATION_GRACE_MS, | ||
| terminationGraceMs = TERMINATION_GRACE_MS, | ||
| setTimeoutImpl = setTimeout, | ||
@@ -734,2 +774,3 @@ clearTimeoutImpl = clearTimeout, | ||
| platform, | ||
| processKillImpl, | ||
| spawnImpl, | ||
@@ -736,0 +777,0 @@ treeKillSpawnImpl, |
+1
-1
| { | ||
| "name": "@abdoknbgit/tau-installer", | ||
| "version": "0.1.0", | ||
| "version": "0.1.1", | ||
| "description": "Install Tau globally with its reviewed npm lifecycle scripts allowed for this install only.", | ||
@@ -5,0 +5,0 @@ "type": "module", |
+10
-7
| # Tau Installer | ||
| `@abdoknbgit/tau-installer` installs Tau globally while allowing only Tau's | ||
| reviewed npm lifecycle scripts for that single install command. | ||
| `@abdoknbgit/tau-installer` installs Tau globally. On npm 11.16 and newer, it | ||
| allows only Tau's reviewed npm lifecycle scripts for that single install | ||
| command. Older npm versions use npm's normal lifecycle-script behavior because | ||
| they do not support the command-scoped policy. | ||
@@ -32,5 +34,5 @@ Node.js 20.18.1 or newer is required. | ||
| The installer has no dependencies and no lifecycle scripts of its own. It runs | ||
| the invoking npm CLI with `shell: false` and passes the reviewed list through a | ||
| command-line `--allow-scripts` option. It never runs `npm config set` and never | ||
| changes the user's persistent npm configuration. | ||
| the invoking npm CLI with `shell: false`. On npm 11.16 and newer, it passes the | ||
| reviewed list through a command-line `--allow-scripts` option. It never runs | ||
| `npm config set` and never changes the user's persistent npm configuration. | ||
@@ -40,4 +42,5 @@ For this command only, the installer also disables inherited `ignore-scripts` | ||
| and package-lock-only modes, forces executable bin links, includes supported | ||
| optional dependencies, and enables strict script policy. Required reviewed | ||
| scripts can run, while an unreviewed dependency script stops the install. | ||
| optional dependencies, and, where supported, enables strict script policy. On | ||
| npm 11.16 and newer, required reviewed scripts can run while an unreviewed | ||
| dependency script stops the install. | ||
@@ -44,0 +47,0 @@ The installer checks the invoking npm version first. npm 11.16 and newer receive |
25864
6.24%743
5.24%52
6.12%