@abdoknbgit/tau-installer
Advanced tools
+552
-71
| import { spawn } from "node:child_process"; | ||
| import { randomUUID } from "node:crypto"; | ||
| import { | ||
| existsSync, | ||
| fstatSync, | ||
| linkSync, | ||
| lstatSync, | ||
| mkdirSync, | ||
| openSync, | ||
| readFileSync, | ||
| readdirSync, | ||
| readlinkSync, | ||
| renameSync, | ||
| rmdirSync, | ||
| closeSync, | ||
| unlinkSync, | ||
| utimesSync, | ||
| writeFileSync, | ||
| } from "node:fs"; | ||
@@ -29,2 +40,6 @@ import { homedir } from "node:os"; | ||
| const TERMINATION_GRACE_MS = 5_000; | ||
| const UPDATE_LOCK_STALE_MS = 15 * 60 * 1000; | ||
| const UPDATE_LOCK_HEARTBEAT_MS = 60 * 1000; | ||
| const LOCK_OWNER_PATTERN = /^owner-[0-9a-f-]{36}\.lease$/i; | ||
| export const INSTALLER_LOCK_CONTENDED_EXIT_CODE = 75; | ||
@@ -169,2 +184,393 @@ const EXACT_SEMVER_PATTERN = | ||
| function errnoCode(error) { | ||
| return error && typeof error === "object" && typeof error.code === "string" | ||
| ? error.code | ||
| : undefined; | ||
| } | ||
| function isMissing(error) { | ||
| return errnoCode(error) === "ENOENT"; | ||
| } | ||
| function isContended(error) { | ||
| return ["EEXIST", "ENOTEMPTY", "EACCES", "EPERM"].includes(errnoCode(error)); | ||
| } | ||
| function sameFile(left, right) { | ||
| if (left.dev !== 0 || left.ino !== 0 || right.dev !== 0 || right.ino !== 0) { | ||
| return left.dev === right.dev && left.ino === right.ino; | ||
| } | ||
| return ( | ||
| left.birthtimeMs === right.birthtimeMs && | ||
| left.mtimeMs === right.mtimeMs && | ||
| left.size === right.size && | ||
| left.mode === right.mode | ||
| ); | ||
| } | ||
| function defaultProcessIsAlive(pid, processKillImpl = process.kill) { | ||
| if (!Number.isSafeInteger(pid) || pid <= 0) return false; | ||
| try { | ||
| processKillImpl(pid, 0); | ||
| return true; | ||
| } catch (error) { | ||
| if (errnoCode(error) === "ESRCH") return false; | ||
| // EPERM proves the process exists. Unknown probe errors fail closed too. | ||
| return true; | ||
| } | ||
| } | ||
| function readOwnerPid(path) { | ||
| try { | ||
| const parsed = JSON.parse(readFileSync(path, "utf8")); | ||
| return Number.isSafeInteger(parsed?.pid) && parsed.pid > 0 ? parsed.pid : null; | ||
| } catch (error) { | ||
| if (isMissing(error) || error instanceof SyntaxError) return null; | ||
| throw error; | ||
| } | ||
| } | ||
| function readLegacyOwnerPid(path) { | ||
| try { | ||
| const value = readFileSync(path, "utf8").trim(); | ||
| if (!/^\d+$/.test(value)) return null; | ||
| const pid = Number(value); | ||
| return Number.isSafeInteger(pid) && pid > 0 ? pid : null; | ||
| } catch (error) { | ||
| if (isMissing(error)) return null; | ||
| throw error; | ||
| } | ||
| } | ||
| function removeEmptyDirectory(path) { | ||
| try { | ||
| rmdirSync(path); | ||
| return true; | ||
| } catch (error) { | ||
| if (isMissing(error)) return true; | ||
| if (isContended(error)) return false; | ||
| throw error; | ||
| } | ||
| } | ||
| function tryCreateLease(lockPath, { now, pid, randomUUIDImpl }) { | ||
| const token = randomUUIDImpl(); | ||
| const leasePath = join(lockPath, `owner-${token}.lease`); | ||
| const createDirectory = () => { | ||
| try { | ||
| mkdirSync(lockPath); | ||
| return true; | ||
| } catch (error) { | ||
| if (errnoCode(error) === "EEXIST") return false; | ||
| throw error; | ||
| } | ||
| }; | ||
| let created; | ||
| try { | ||
| created = createDirectory(); | ||
| } catch (error) { | ||
| if (!isMissing(error)) throw error; | ||
| mkdirSync(dirname(lockPath), { recursive: true }); | ||
| created = createDirectory(); | ||
| } | ||
| if (!created) return null; | ||
| try { | ||
| writeFileSync( | ||
| leasePath, | ||
| JSON.stringify({ token, pid, acquiredAt: now() }), | ||
| { encoding: "utf8", flag: "wx" }, | ||
| ); | ||
| return { lockPath, token, pid, leasePath, borrowed: false }; | ||
| } catch (error) { | ||
| try { | ||
| rmdirSync(lockPath); | ||
| } catch { | ||
| // A replacement owner makes the directory non-empty and preserves it. | ||
| } | ||
| if (isMissing(error) || isContended(error)) return null; | ||
| throw error; | ||
| } | ||
| } | ||
| function recoverLeaseDirectory( | ||
| lockPath, | ||
| observedDirectory, | ||
| { now, staleMs, isProcessAliveImpl }, | ||
| ) { | ||
| let entries; | ||
| try { | ||
| entries = readdirSync(lockPath, { withFileTypes: true }); | ||
| } catch (error) { | ||
| if (isMissing(error)) return true; | ||
| throw error; | ||
| } | ||
| if (entries.length === 0) { | ||
| let recheck; | ||
| try { | ||
| recheck = lstatSync(lockPath); | ||
| } catch (error) { | ||
| if (isMissing(error)) return true; | ||
| throw error; | ||
| } | ||
| if ( | ||
| !recheck.isDirectory() || | ||
| !sameFile(observedDirectory, recheck) || | ||
| now() - recheck.mtimeMs < staleMs | ||
| ) { | ||
| return false; | ||
| } | ||
| return removeEmptyDirectory(lockPath); | ||
| } | ||
| const staleOwners = []; | ||
| for (const entry of entries) { | ||
| if (!entry.isFile() || !LOCK_OWNER_PATTERN.test(entry.name)) return false; | ||
| const path = join(lockPath, entry.name); | ||
| let stats; | ||
| try { | ||
| stats = lstatSync(path); | ||
| } catch (error) { | ||
| if (isMissing(error)) continue; | ||
| throw error; | ||
| } | ||
| if (!stats.isFile() || now() - stats.mtimeMs < staleMs) return false; | ||
| const ownerPid = readOwnerPid(path); | ||
| if (ownerPid && isProcessAliveImpl(ownerPid)) return false; | ||
| staleOwners.push({ path, stats }); | ||
| } | ||
| for (const stale of staleOwners) { | ||
| let recheck; | ||
| try { | ||
| recheck = lstatSync(stale.path); | ||
| } catch (error) { | ||
| if (isMissing(error)) continue; | ||
| throw error; | ||
| } | ||
| if ( | ||
| !recheck.isFile() || | ||
| !sameFile(stale.stats, recheck) || | ||
| now() - recheck.mtimeMs < staleMs | ||
| ) { | ||
| return false; | ||
| } | ||
| const ownerPid = readOwnerPid(stale.path); | ||
| if (ownerPid && isProcessAliveImpl(ownerPid)) return false; | ||
| try { | ||
| unlinkSync(stale.path); | ||
| } catch (error) { | ||
| if (!isMissing(error)) throw error; | ||
| } | ||
| } | ||
| return removeEmptyDirectory(lockPath); | ||
| } | ||
| function restoreQuarantinedLegacyLock(quarantine, lockPath) { | ||
| try { | ||
| linkSync(quarantine, lockPath); | ||
| unlinkSync(quarantine); | ||
| } catch { | ||
| // Preserve an identity that could not be restored or verified. | ||
| } | ||
| } | ||
| function recoverLegacyLock( | ||
| lockPath, | ||
| observed, | ||
| { now, staleMs, isProcessAliveImpl, randomUUIDImpl }, | ||
| ) { | ||
| if (now() - observed.mtimeMs < staleMs) return false; | ||
| const ownerPid = readLegacyOwnerPid(lockPath); | ||
| if (ownerPid && isProcessAliveImpl(ownerPid)) return false; | ||
| const quarantine = `${lockPath}.legacy-stale-${randomUUIDImpl()}`; | ||
| let handle; | ||
| try { | ||
| handle = openSync(lockPath, "r"); | ||
| const opened = fstatSync(handle); | ||
| const recheck = lstatSync(lockPath); | ||
| if ( | ||
| !recheck.isFile() || | ||
| !sameFile(observed, opened) || | ||
| !sameFile(opened, recheck) || | ||
| now() - recheck.mtimeMs < staleMs | ||
| ) { | ||
| return false; | ||
| } | ||
| const recheckedPid = readLegacyOwnerPid(lockPath); | ||
| if (recheckedPid && isProcessAliveImpl(recheckedPid)) return false; | ||
| renameSync(lockPath, quarantine); | ||
| const moved = lstatSync(quarantine); | ||
| if (!moved.isFile() || !sameFile(opened, moved)) { | ||
| restoreQuarantinedLegacyLock(quarantine, lockPath); | ||
| return false; | ||
| } | ||
| } catch (error) { | ||
| if (isMissing(error)) return true; | ||
| throw error; | ||
| } finally { | ||
| if (handle !== undefined) closeSync(handle); | ||
| } | ||
| unlinkSync(quarantine); | ||
| return true; | ||
| } | ||
| function acquireDirectoryLease(lockPath, options) { | ||
| const fresh = tryCreateLease(lockPath, options); | ||
| if (fresh) return fresh; | ||
| let observed; | ||
| try { | ||
| observed = lstatSync(lockPath); | ||
| } catch (error) { | ||
| if (isMissing(error)) return tryCreateLease(lockPath, options); | ||
| throw error; | ||
| } | ||
| if (!observed.isDirectory()) return null; | ||
| return recoverLeaseDirectory(lockPath, observed, options) | ||
| ? tryCreateLease(lockPath, options) | ||
| : null; | ||
| } | ||
| function acquireMainLease(lockPath, options) { | ||
| const fresh = tryCreateLease(lockPath, options); | ||
| if (fresh) return fresh; | ||
| let observed; | ||
| try { | ||
| observed = lstatSync(lockPath); | ||
| } catch (error) { | ||
| if (isMissing(error)) return tryCreateLease(lockPath, options); | ||
| throw error; | ||
| } | ||
| const recovered = observed.isDirectory() | ||
| ? recoverLeaseDirectory(lockPath, observed, options) | ||
| : observed.isFile() | ||
| ? recoverLegacyLock(lockPath, observed, options) | ||
| : false; | ||
| return recovered ? tryCreateLease(lockPath, options) : null; | ||
| } | ||
| function readBorrowedLease(env, platform, isProcessAliveImpl) { | ||
| const path = env.TAU_UPDATE_LOCK_PATH; | ||
| const token = env.TAU_UPDATE_LOCK_TOKEN; | ||
| const pidText = env.TAU_UPDATE_LOCK_PID; | ||
| const supplied = [path, token, pidText].filter((value) => value !== undefined).length; | ||
| if (supplied === 0) return { status: "none" }; | ||
| if (supplied !== 3 || !LOCK_OWNER_PATTERN.test(`owner-${token}.lease`)) { | ||
| return { status: "invalid" }; | ||
| } | ||
| const pid = /^\d+$/.test(pidText) ? Number(pidText) : NaN; | ||
| const pathIsAbsolute = platform === "win32" ? win32.isAbsolute(path) : posix.isAbsolute(path); | ||
| if ( | ||
| !Number.isSafeInteger(pid) || | ||
| pid <= 0 || | ||
| !pathIsAbsolute | ||
| ) { | ||
| return { status: "invalid" }; | ||
| } | ||
| const leasePath = join(path, `owner-${token}.lease`); | ||
| try { | ||
| const lockStats = lstatSync(path); | ||
| const leaseStats = lstatSync(leasePath); | ||
| const contents = JSON.parse(readFileSync(leasePath, "utf8")); | ||
| if ( | ||
| !lockStats.isDirectory() || | ||
| !leaseStats.isFile() || | ||
| contents?.token !== token || | ||
| contents?.pid !== pid || | ||
| !isProcessAliveImpl(pid) | ||
| ) { | ||
| return { status: "invalid" }; | ||
| } | ||
| } catch { | ||
| return { status: "invalid" }; | ||
| } | ||
| return { | ||
| status: "borrowed", | ||
| lease: { lockPath: path, token, pid, leasePath, borrowed: true }, | ||
| }; | ||
| } | ||
| export function getInstallerUpdateLockPath( | ||
| env = process.env, | ||
| workingDirectory = homedir(), | ||
| ) { | ||
| const configured = env.CLAUDE_CONFIG_DIR?.trim(); | ||
| const configHome = configured ? resolve(configured) : join(resolve(workingDirectory), ".claude"); | ||
| return join(configHome, ".update.lock"); | ||
| } | ||
| /** Acquire the shared global-update lease, or validate an outer updater handoff. */ | ||
| export function acquireInstallerUpdateLease({ | ||
| env = process.env, | ||
| lockPath = getInstallerUpdateLockPath(env), | ||
| platform = process.platform, | ||
| staleMs = UPDATE_LOCK_STALE_MS, | ||
| now = Date.now, | ||
| pid = process.pid, | ||
| randomUUIDImpl = randomUUID, | ||
| isProcessAliveImpl = defaultProcessIsAlive, | ||
| } = {}) { | ||
| const borrowed = readBorrowedLease(env, platform, isProcessAliveImpl); | ||
| if (borrowed.status !== "none") return borrowed; | ||
| const options = { now, pid, randomUUIDImpl, staleMs, isProcessAliveImpl }; | ||
| const gate = acquireDirectoryLease(`${lockPath}.acquire`, options); | ||
| if (!gate) return { status: "contended" }; | ||
| try { | ||
| const lease = acquireMainLease(lockPath, options); | ||
| return lease ? { status: "acquired", lease } : { status: "contended" }; | ||
| } finally { | ||
| releaseInstallerUpdateLease(gate); | ||
| } | ||
| } | ||
| export function getInstallerLeaseEnvironment(lease) { | ||
| return { | ||
| TAU_UPDATE_LOCK_PATH: lease.lockPath, | ||
| TAU_UPDATE_LOCK_TOKEN: lease.token, | ||
| TAU_UPDATE_LOCK_PID: String(lease.pid), | ||
| }; | ||
| } | ||
| export function releaseInstallerUpdateLease(lease) { | ||
| if (!lease || lease.borrowed) return; | ||
| try { | ||
| unlinkSync(lease.leasePath); | ||
| } catch (error) { | ||
| if (!isMissing(error)) throw error; | ||
| } | ||
| removeEmptyDirectory(lease.lockPath); | ||
| } | ||
| function startInstallerLeaseHeartbeat( | ||
| lease, | ||
| { | ||
| heartbeatMs = UPDATE_LOCK_HEARTBEAT_MS, | ||
| setIntervalImpl = setInterval, | ||
| clearIntervalImpl = clearInterval, | ||
| onError = () => {}, | ||
| } = {}, | ||
| ) { | ||
| if (lease.borrowed) return () => {}; | ||
| const timer = setIntervalImpl(() => { | ||
| try { | ||
| const now = new Date(); | ||
| utimesSync(lease.leasePath, now, now); | ||
| } catch (error) { | ||
| if (!isMissing(error)) onError(error); | ||
| } | ||
| }, heartbeatMs); | ||
| timer.unref?.(); | ||
| return () => clearIntervalImpl(timer); | ||
| } | ||
| function getEmbeddedLauncherTargets(launcherPath, content) { | ||
@@ -236,8 +642,13 @@ const quotedTargets = [ | ||
| export function resolveWindowsTaskkillPath(env = process.env) { | ||
| const configuredRoot = env.SystemRoot?.trim() || env.WINDIR?.trim(); | ||
| const windowsRoot = | ||
| configuredRoot && win32.isAbsolute(configuredRoot) | ||
| ? configuredRoot | ||
| : "C:\\Windows"; | ||
| return win32.join(windowsRoot, "System32", "taskkill.exe"); | ||
| for (const value of [env.SystemRoot, env.WINDIR]) { | ||
| const windowsRoot = value?.trim(); | ||
| if ( | ||
| windowsRoot && | ||
| win32.isAbsolute(windowsRoot) && | ||
| !/[\0\r\n]/.test(windowsRoot) | ||
| ) { | ||
| return win32.join(windowsRoot, "System32", "taskkill.exe"); | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
@@ -346,2 +757,7 @@ | ||
| const command = resolveWindowsTaskkillPath(env); | ||
| // A missing/invalid SystemRoot is safer than guessing an OS drive or | ||
| // resolving a potentially shadowed taskkill.exe from PATH. Fall back to | ||
| // terminating npm directly; the next run's integrity repair handles any | ||
| // descendants that outlive it. | ||
| if (!command) return false; | ||
| const args = ["/PID", String(child.pid), "/T", ...(force ? ["/F"] : [])]; | ||
@@ -361,6 +777,28 @@ let killer; | ||
| let killerSettled = false; | ||
| let fallbackStarted = false; | ||
| const fallbackToDirectKill = (message) => { | ||
| // child_process can report both `error` and `close`. A failed taskkill | ||
| // must forward the fallback signal exactly once for this attempt. | ||
| if (fallbackStarted) return; | ||
| fallbackStarted = true; | ||
| stderr.write(`Unable to terminate npm's process tree: ${message}\n`); | ||
| killDirectly(fallbackSignal); | ||
| }; | ||
| killer.once?.("error", (error) => { | ||
| stderr.write(`Unable to terminate npm's process tree: ${error.message}\n`); | ||
| killDirectly(fallbackSignal); | ||
| if (killerSettled) return; | ||
| killerSettled = true; | ||
| fallbackToDirectKill(error.message); | ||
| }); | ||
| killer.once?.("close", (code, signal) => { | ||
| if (killerSettled) return; | ||
| killerSettled = true; | ||
| if (code === 0) return; | ||
| fallbackToDirectKill( | ||
| Number.isInteger(code) | ||
| ? `taskkill exited with code ${code}` | ||
| : `taskkill exited via ${signal ?? "an unknown failure"}`, | ||
| ); | ||
| }); | ||
| return true; | ||
@@ -720,3 +1158,7 @@ }; | ||
| clearTimeoutImpl = clearTimeout, | ||
| setIntervalImpl = setInterval, | ||
| clearIntervalImpl = clearInterval, | ||
| workingDirectory = homedir(), | ||
| lockPath, | ||
| isProcessAliveImpl = (pid) => defaultProcessIsAlive(pid, processKillImpl), | ||
| stdout = process.stdout, | ||
@@ -752,76 +1194,115 @@ stderr = process.stderr, | ||
| const globalPrefix = await detectGlobalPrefix(invocation, { | ||
| env: childEnv, | ||
| platform, | ||
| spawnImpl, | ||
| workingDirectory, | ||
| }); | ||
| if (!globalPrefix) { | ||
| stderr.write("Unable to determine npm's global prefix safely.\n"); | ||
| const expectedLockPath = lockPath ?? getInstallerUpdateLockPath(env, workingDirectory); | ||
| let lockResult; | ||
| try { | ||
| lockResult = acquireInstallerUpdateLease({ | ||
| env, | ||
| lockPath: expectedLockPath, | ||
| platform, | ||
| isProcessAliveImpl, | ||
| }); | ||
| } catch (error) { | ||
| stderr.write(`Unable to acquire Tau's update lock: ${error.message}\n`); | ||
| return 1; | ||
| } | ||
| if (lockResult.status === "invalid") { | ||
| stderr.write("Tau received an invalid update-lock handoff and refused to continue.\n"); | ||
| return 1; | ||
| } | ||
| if (lockResult.status === "contended") { | ||
| stderr.write("Another Tau installation or update is already in progress.\n"); | ||
| return INSTALLER_LOCK_CONTENDED_EXIT_CODE; | ||
| } | ||
| const binDirectory = | ||
| platform === "win32" ? globalPrefix : join(globalPrefix, "bin"); | ||
| cleanDanglingLaunchers(binDirectory, { platform }); | ||
| const lease = lockResult.lease; | ||
| Object.assign(childEnv, getInstallerLeaseEnvironment(lease)); | ||
| const stopLockHeartbeat = startInstallerLeaseHeartbeat(lease, { | ||
| setIntervalImpl, | ||
| clearIntervalImpl, | ||
| onError: (error) => stderr.write(`Unable to refresh Tau's update lock: ${error.message}\n`), | ||
| }); | ||
| const commandOptions = { | ||
| env: childEnv, | ||
| platform, | ||
| processKillImpl, | ||
| spawnImpl, | ||
| treeKillSpawnImpl, | ||
| signalSource, | ||
| terminationGraceMs, | ||
| setTimeoutImpl, | ||
| clearTimeoutImpl, | ||
| workingDirectory, | ||
| stderr, | ||
| }; | ||
| const installCode = await runForegroundNpmCommand( | ||
| invocation, | ||
| buildInstallArguments(tauVersion, npmVersionResult.version), | ||
| commandOptions, | ||
| ); | ||
| if (installCode !== 0) return installCode; | ||
| try { | ||
| const globalPrefix = await detectGlobalPrefix(invocation, { | ||
| env: childEnv, | ||
| platform, | ||
| spawnImpl, | ||
| workingDirectory, | ||
| }); | ||
| if (!globalPrefix) { | ||
| stderr.write("Unable to determine npm's global prefix safely.\n"); | ||
| return 1; | ||
| } | ||
| const packageRoot = getGlobalTauPackageRoot(globalPrefix, platform); | ||
| let lifecycleStatus = getLifecycleMarkerStatus(packageRoot); | ||
| if (tauVersion && lifecycleStatus.version && lifecycleStatus.version !== tauVersion) { | ||
| stderr.write( | ||
| `npm reported success, but installed Tau ${lifecycleStatus.version} instead of ${tauVersion}.\n`, | ||
| const binDirectory = | ||
| platform === "win32" ? globalPrefix : join(globalPrefix, "bin"); | ||
| cleanDanglingLaunchers(binDirectory, { platform }); | ||
| const commandOptions = { | ||
| env: childEnv, | ||
| platform, | ||
| processKillImpl, | ||
| spawnImpl, | ||
| treeKillSpawnImpl, | ||
| signalSource, | ||
| terminationGraceMs, | ||
| setTimeoutImpl, | ||
| clearTimeoutImpl, | ||
| workingDirectory, | ||
| stderr, | ||
| }; | ||
| const installCode = await runForegroundNpmCommand( | ||
| invocation, | ||
| buildInstallArguments(tauVersion, npmVersionResult.version), | ||
| commandOptions, | ||
| ); | ||
| return 1; | ||
| } | ||
| if (lifecycleStatus.ok) return 0; | ||
| if (lifecycleStatus.reason === "manifest-missing" || lifecycleStatus.reason === "manifest-invalid") { | ||
| stderr.write("npm reported success, but the global Tau package is missing or invalid.\n"); | ||
| return 1; | ||
| } | ||
| if (installCode !== 0) return installCode; | ||
| const dependencyPackages = ALLOWED_SCRIPTS.filter( | ||
| (packageName) => packageName !== TAU_PACKAGE, | ||
| ); | ||
| const dependencyRebuildCode = await runForegroundNpmCommand( | ||
| invocation, | ||
| buildRebuildArguments(dependencyPackages, npmVersionResult.version), | ||
| commandOptions, | ||
| ); | ||
| if (dependencyRebuildCode !== 0) return dependencyRebuildCode; | ||
| const packageRoot = getGlobalTauPackageRoot(globalPrefix, platform); | ||
| let lifecycleStatus = getLifecycleMarkerStatus(packageRoot); | ||
| if (tauVersion && lifecycleStatus.version && lifecycleStatus.version !== tauVersion) { | ||
| stderr.write( | ||
| `npm reported success, but installed Tau ${lifecycleStatus.version} instead of ${tauVersion}.\n`, | ||
| ); | ||
| return 1; | ||
| } | ||
| if (lifecycleStatus.ok) return 0; | ||
| if (lifecycleStatus.reason === "manifest-missing" || lifecycleStatus.reason === "manifest-invalid") { | ||
| stderr.write("npm reported success, but the global Tau package is missing or invalid.\n"); | ||
| return 1; | ||
| } | ||
| const tauRebuildCode = await runForegroundNpmCommand( | ||
| invocation, | ||
| buildRebuildArguments([TAU_PACKAGE], npmVersionResult.version), | ||
| commandOptions, | ||
| ); | ||
| if (tauRebuildCode !== 0) return tauRebuildCode; | ||
| const dependencyPackages = ALLOWED_SCRIPTS.filter( | ||
| (packageName) => packageName !== TAU_PACKAGE, | ||
| ); | ||
| const dependencyRebuildCode = await runForegroundNpmCommand( | ||
| invocation, | ||
| buildRebuildArguments(dependencyPackages, npmVersionResult.version), | ||
| commandOptions, | ||
| ); | ||
| if (dependencyRebuildCode !== 0) return dependencyRebuildCode; | ||
| lifecycleStatus = getLifecycleMarkerStatus(packageRoot); | ||
| if (!lifecycleStatus.ok) { | ||
| stderr.write( | ||
| "Tau's lifecycle scripts did not produce a valid completion marker.\n", | ||
| const tauRebuildCode = await runForegroundNpmCommand( | ||
| invocation, | ||
| buildRebuildArguments([TAU_PACKAGE], npmVersionResult.version), | ||
| commandOptions, | ||
| ); | ||
| return 1; | ||
| if (tauRebuildCode !== 0) return tauRebuildCode; | ||
| lifecycleStatus = getLifecycleMarkerStatus(packageRoot); | ||
| if (!lifecycleStatus.ok) { | ||
| stderr.write( | ||
| "Tau's lifecycle scripts did not produce a valid completion marker.\n", | ||
| ); | ||
| return 1; | ||
| } | ||
| return 0; | ||
| } finally { | ||
| stopLockHeartbeat(); | ||
| try { | ||
| releaseInstallerUpdateLease(lease); | ||
| } catch (error) { | ||
| stderr.write(`Unable to release Tau's update lock: ${error.message}\n`); | ||
| } | ||
| } | ||
| return 0; | ||
| } | ||
@@ -828,0 +1309,0 @@ |
+1
-1
| { | ||
| "name": "@abdoknbgit/tau-installer", | ||
| "version": "0.1.1", | ||
| "version": "0.1.2", | ||
| "description": "Install Tau globally with its reviewed npm lifecycle scripts allowed for this install only.", | ||
@@ -5,0 +5,0 @@ "type": "module", |
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
39523
52.81%1188
59.89%7
40%