hlidskjalf 0.2.3 → 0.2.5
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/LICENSE +21 -0
- package/README.md +8 -0
- package/dist/index.js +4 -3
- package/package.json +3 -2
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Charlie Beckstrand
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# hlidskjalf
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
|
|
3
7
|
A Terminal User Interface for visualizing Turborepo tasks, built with [Ink](https://npm.im/ink).
|
|
4
8
|
|
|
5
9
|
<img src="https://raw.githubusercontent.com/charliebeckstrand/hlidskjalf/main/screenshot.png" alt="Screenshot" width="513" />
|
|
@@ -29,3 +33,7 @@ pnpm dev
|
|
|
29
33
|
| `filter` | Include specific workspaces (`--filter=web`). Append `...` for transitive deps (`--filter=web...`). |
|
|
30
34
|
| `order` | Sort by `alphabetical` (default) or `run` (`--order=run`) dependency order. |
|
|
31
35
|
| `title` | Custom title for the header (`--title="My App"`). Defaults to `Hlidskjalf`. |
|
|
36
|
+
|
|
37
|
+
## License
|
|
38
|
+
|
|
39
|
+
MIT
|
package/dist/index.js
CHANGED
|
@@ -8,8 +8,9 @@ import http from 'http';
|
|
|
8
8
|
import https from 'https';
|
|
9
9
|
import { existsSync, readdirSync, realpathSync, readFileSync } from 'fs';
|
|
10
10
|
import { resolve, join, sep } from 'path';
|
|
11
|
-
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
11
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
12
12
|
|
|
13
|
-
function useCursor(length,enabled){let[cursor,setCursor]=useState(0);return useInput((input,key)=>{key.upArrow||input==="k"?setCursor(i=>Math.max(0,i-1)):(key.downArrow||input==="j")&&setCursor(i=>Math.min(length-1,i+1));},{isActive:enabled}),cursor}var MAX_PARSE_LENGTH=4096,SAFE_URL=/^(https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\]|0\.0\.0\.0):\d{1,5})/;function cleanUrl(raw){return raw.replace(/[.,;:!?)}\]]+$/,"")}var DTS=/\bDTS\b/,matchers=[{pattern:/running on (https?:\/\/\S+)/,status:"ready"},{pattern:/listening on (https?:\/\/\S+)/,status:"ready"},{pattern:/started.*?(https?:\/\/localhost:\d+)/,status:"ready"},{pattern:/\bVITE\b.*?\bready in\b/i,status:"ready"},{pattern:/\bLocal:\s+(https?:\/\/\S+)/,status:"ready"},{pattern:/⚡\uFE0F?\s*Build success/,status:"watching"},{pattern:/Build start/,status:"building"},{pattern:/Watching for changes/,status:"watching"},{pattern:/\[ERROR\]/,status:"error"},{pattern:/[Ee]rror[\s:]/,status:"error"},{pattern:/process exit/,status:"error"}];function parseLine(line){let truncated=line.length>MAX_PARSE_LENGTH?line.slice(0,MAX_PARSE_LENGTH):line;if(DTS.test(truncated))return {};for(let{pattern,status}of matchers){let match=truncated.match(pattern);if(match){let urlMatch=(match[1]?cleanUrl(match[1]):void 0)?.match(SAFE_URL),url=urlMatch?urlMatch[1]:void 0;return {status,url}}}return {}}function sanitizeForDisplay(text){let NON_SGR_ESCAPES=/\x1b(?:\][^\x07\x1b]*(?:\x07|\x1b\\)|\[[?>=]*[\d;]*[A-Za-ln-z@~`]|\([A-Za-z]|[^[(\]\x1b])/g;return text.replace(NON_SGR_ESCAPES,"")}var MAX_LOGS=500,ERROR_RECOVERY_MS=5e3,MAX_RESTART_RETRIES=3,RESTART_DELAY_MS=1e3,STARTUP_TIMEOUT_MS=12e4,HEARTBEAT_INTERVAL_MS=1e4,IDLE_THRESHOLD_MS=3e5,MAX_BUFFER_SIZE=65536,MAX_LINE_LENGTH=8192,ENV_ALLOWLIST=new Set(["HOME","USER","LOGNAME","SHELL","PATH","LANG","LC_ALL","LC_CTYPE","TERM","TERM_PROGRAM","COLORTERM","NODE_ENV","NODE_OPTIONS","NODE_PATH","NPM_CONFIG_REGISTRY","PNPM_HOME","COREPACK_HOME","XDG_CONFIG_HOME","XDG_DATA_HOME","XDG_CACHE_HOME","TMPDIR","TMP","TEMP","EDITOR","DISPLAY","HOSTNAME"]);function safeEnv(){let filtered={};for(let key of Object.keys(process.env))ENV_ALLOWLIST.has(key)&&(filtered[key]=process.env[key]);return filtered.FORCE_COLOR="1",filtered}var ProcessRunner=class extends EventEmitter{entries=new Map;pendingRebuilds=new Set;heartbeatInterval=null;root;stopping=false;allWorkspaces=[];constructor(root){super(),this.root=root;}get(name){return this.entries.get(name)?.process}async start(workspaces){this.allWorkspaces=workspaces;let packages=workspaces.filter(w=>w.kind==="package"),apps=workspaces.filter(w=>w.kind!=="package");for(let workspace of workspaces)this.entries.set(workspace.name,{process:{workspace,status:"pending",logs:[]},child:null,errorTimer:null,restartTimer:null,startupTimer:null,lastGoodStatus:null,restartRetries:0,lastOutputAt:0});for(let workspace of packages)this.spawn(workspace);packages.length>0&&await this.waitForPackages(packages.map(p=>p.name));let failedPackages=new Set;for(let pkg of packages){let s=this.entries.get(pkg.name)?.process.status;(s==="error"||s==="stopped"||s==="timeout")&&failedPackages.add(pkg.name);}for(let workspace of apps){let failedDeps=workspace.deps.filter(d=>failedPackages.has(d));if(failedDeps.length>0){let entry=this.entries.get(workspace.name);entry&&(entry.process.logs.push(`[hlidskjalf] warning: dependency ${failedDeps.join(", ")} failed \u2014 starting anyway`),this.emit("change"));}this.spawn(workspace);}this.startHeartbeat();}async shutdown(){this.stopping=true,this.heartbeatInterval&&clearInterval(this.heartbeatInterval);for(let entry of this.entries.values())entry.errorTimer&&clearTimeout(entry.errorTimer),entry.restartTimer&&clearTimeout(entry.restartTimer),entry.startupTimer&&clearTimeout(entry.startupTimer);for(let child of this.pendingRebuilds)child.kill("SIGTERM");let waiting=[];for(let entry of this.entries.values()){let{child}=entry;!child||child.exitCode!==null||child.signalCode!==null||waiting.push(new Promise(resolve2=>{let escalate=setTimeout(()=>{child.exitCode===null&&child.kill("SIGKILL");},5e3);child.on("close",()=>{clearTimeout(escalate),resolve2();}),child.kill("SIGTERM");}));}await Promise.all(waiting);}entry(name){return this.entries.get(name)}waitForPackages(names){let remaining=new Set(names);return new Promise(resolve2=>{let check=()=>{for(let name of [...remaining]){let s=this.entry(name)?.process.status;(s==="watching"||s==="error"||s==="stopped"||s==="timeout")&&remaining.delete(name);}remaining.size===0&&(this.off("change",check),resolve2());};this.on("change",check),check();})}spawn(workspace){let child=spawn("pnpm",["--filter",workspace.name,"run","dev"],{cwd:this.root,stdio:"pipe",env:safeEnv()}),entry=this.entry(workspace.name);entry&&(entry.child=child),this.setStatus(workspace.name,"building");let startupTimer=setTimeout(()=>{let e=this.entry(workspace.name);e&&(e.startupTimer=null,e.process.status!=="watching"&&e.process.status!=="ready"&&(e.process.logs.push(`[hlidskjalf] startup timeout after ${STARTUP_TIMEOUT_MS/1e3}s`),this.setStatus(workspace.name,"timeout")));},STARTUP_TIMEOUT_MS);startupTimer.unref(),entry&&(entry.startupTimer=startupTimer);let buffer="",onData=data=>{if(buffer+=data.toString(),!buffer.includes(`
|
|
13
|
+
function useCursor(length,enabled){let[cursor,setCursor]=useState(0);return useInput((input,key)=>{key.upArrow||input==="k"?setCursor(i=>Math.max(0,i-1)):(key.downArrow||input==="j")&&setCursor(i=>Math.min(length-1,i+1));},{isActive:enabled}),cursor}var MAX_PARSE_LENGTH=4096,SAFE_URL=/^(https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\]|0\.0\.0\.0):\d{1,5})/;function cleanUrl(raw){return raw.replace(/[.,;:!?)}\]]+$/,"")}var DTS=/\bDTS\b/,matchers=[{pattern:/running on (https?:\/\/\S+)/,status:"ready"},{pattern:/listening on (https?:\/\/\S+)/,status:"ready"},{pattern:/started.*?(https?:\/\/localhost:\d+)/,status:"ready"},{pattern:/\bVITE\b.*?\bready in\b/i,status:"ready"},{pattern:/\bLocal:\s+(https?:\/\/\S+)/,status:"ready"},{pattern:/⚡\uFE0F?\s*Build success/,status:"watching"},{pattern:/Build start/,status:"building"},{pattern:/Watching for changes/,status:"watching"},{pattern:/\[ERROR\]/,status:"error"},{pattern:/[Ee]rror[\s:]/,status:"error"},{pattern:/process exit/,status:"error"}];function parseLine(line){let truncated=line.length>MAX_PARSE_LENGTH?line.slice(0,MAX_PARSE_LENGTH):line;if(DTS.test(truncated))return {};for(let{pattern,status}of matchers){let match=truncated.match(pattern);if(match){let urlMatch=(match[1]?cleanUrl(match[1]):void 0)?.match(SAFE_URL),url=urlMatch?urlMatch[1]:void 0;return {status,url}}}return {}}function sanitizeForDisplay(text){let NON_SGR_ESCAPES=/\x1b(?:\][^\x07\x1b]*(?:\x07|\x1b\\)|\[[?>=]*[\d;]*[A-Za-ln-z@~`]|\([A-Za-z]|[^[(\]\x1b])/g;return text.replace(NON_SGR_ESCAPES,"")}var MAX_LOGS=500,ERROR_RECOVERY_MS=5e3,MAX_RESTART_RETRIES=3,RESTART_DELAY_MS=1e3,STARTUP_TIMEOUT_MS=12e4,HEARTBEAT_INTERVAL_MS=1e4,METRICS_INTERVAL_MS=3e3,IDLE_THRESHOLD_MS=3e5,MAX_BUFFER_SIZE=65536,MAX_LINE_LENGTH=8192,ENV_ALLOWLIST=new Set(["HOME","USER","LOGNAME","SHELL","PATH","LANG","LC_ALL","LC_CTYPE","TERM","TERM_PROGRAM","COLORTERM","NODE_ENV","NODE_OPTIONS","NODE_PATH","NPM_CONFIG_REGISTRY","PNPM_HOME","COREPACK_HOME","XDG_CONFIG_HOME","XDG_DATA_HOME","XDG_CACHE_HOME","TMPDIR","TMP","TEMP","EDITOR","DISPLAY","HOSTNAME"]);function safeEnv(){let filtered={};for(let key of Object.keys(process.env))ENV_ALLOWLIST.has(key)&&(filtered[key]=process.env[key]);return filtered.FORCE_COLOR="1",filtered}var ProcessRunner=class extends EventEmitter{entries=new Map;pendingRebuilds=new Set;heartbeatInterval=null;metricsInterval=null;root;stopping=false;allWorkspaces=[];metricsEnabled;constructor(root,metrics=false){super(),this.root=root,this.metricsEnabled=metrics;}get(name){return this.entries.get(name)?.process}async start(workspaces){this.allWorkspaces=workspaces;let packages=workspaces.filter(w=>w.kind==="package"),apps=workspaces.filter(w=>w.kind!=="package");for(let workspace of workspaces)this.entries.set(workspace.name,{process:{workspace,status:"pending",logs:[]},child:null,errorTimer:null,restartTimer:null,startupTimer:null,lastGoodStatus:null,restartRetries:0,lastOutputAt:0});for(let workspace of packages)this.spawn(workspace);packages.length>0&&await this.waitForPackages(packages.map(p=>p.name));let failedPackages=new Set;for(let pkg of packages){let s=this.entries.get(pkg.name)?.process.status;(s==="error"||s==="stopped"||s==="timeout")&&failedPackages.add(pkg.name);}for(let workspace of apps){let failedDeps=workspace.deps.filter(d=>failedPackages.has(d));if(failedDeps.length>0){let entry=this.entries.get(workspace.name);entry&&(entry.process.logs.push(`[hlidskjalf] warning: dependency ${failedDeps.join(", ")} failed \u2014 starting anyway`),this.emit("change"));}this.spawn(workspace);}this.startHeartbeat(),this.metricsEnabled&&this.startMetrics();}async shutdown(){this.stopping=true,this.heartbeatInterval&&clearInterval(this.heartbeatInterval),this.metricsInterval&&clearInterval(this.metricsInterval);for(let entry of this.entries.values())entry.errorTimer&&clearTimeout(entry.errorTimer),entry.restartTimer&&clearTimeout(entry.restartTimer),entry.startupTimer&&clearTimeout(entry.startupTimer);for(let child of this.pendingRebuilds)child.kill("SIGTERM");let waiting=[];for(let entry of this.entries.values()){let{child}=entry;!child||child.exitCode!==null||child.signalCode!==null||waiting.push(new Promise(resolve2=>{let escalate=setTimeout(()=>{child.exitCode===null&&child.kill("SIGKILL");},5e3);child.on("close",()=>{clearTimeout(escalate),resolve2();}),child.kill("SIGTERM");}));}await Promise.all(waiting);}entry(name){return this.entries.get(name)}waitForPackages(names){let remaining=new Set(names);return new Promise(resolve2=>{let check=()=>{for(let name of [...remaining]){let s=this.entry(name)?.process.status;(s==="watching"||s==="error"||s==="stopped"||s==="timeout")&&remaining.delete(name);}remaining.size===0&&(this.off("change",check),resolve2());};this.on("change",check),check();})}spawn(workspace){let child=spawn("pnpm",["--filter",workspace.name,"run","dev"],{cwd:this.root,stdio:"pipe",env:safeEnv()}),entry=this.entry(workspace.name);entry&&(entry.child=child),this.setStatus(workspace.name,"building");let startupTimer=setTimeout(()=>{let e=this.entry(workspace.name);e&&(e.startupTimer=null,e.process.status!=="watching"&&e.process.status!=="ready"&&(e.process.logs.push(`[hlidskjalf] startup timeout after ${STARTUP_TIMEOUT_MS/1e3}s`),this.setStatus(workspace.name,"timeout")));},STARTUP_TIMEOUT_MS);startupTimer.unref(),entry&&(entry.startupTimer=startupTimer);let buffer="",onData=data=>{if(buffer+=data.toString(),!buffer.includes(`
|
|
14
14
|
`)&&buffer.length>MAX_BUFFER_SIZE){this.handleLine(workspace.name,buffer),buffer="";return}let lines=buffer.split(`
|
|
15
|
-
`);buffer=lines.pop()??"";for(let raw of lines){let line=raw.trimEnd();line&&this.handleLine(workspace.name,line);}};child.stdout?.on("data",onData),child.stderr?.on("data",onData),child.on("close",(code,signal)=>{buffer.trim()&&this.handleLine(workspace.name,buffer.trimEnd()),buffer="",!this.stopping&&this.handleUnexpectedExit(workspace,code,signal);}),child.on("error",()=>{let e=this.entry(workspace.name);e?.startupTimer&&(clearTimeout(e.startupTimer),e.startupTimer=null),this.setStatus(workspace.name,"error");});}handleLine(name,raw){if(this.stopping)return;let entry=this.entry(name);if(!entry)return;let line=raw.length>MAX_LINE_LENGTH?raw.slice(0,MAX_LINE_LENGTH):raw,{process:proc}=entry;proc.logs.push(sanitizeForDisplay(line)),proc.logs.length>MAX_LOGS&&proc.logs.splice(0,proc.logs.length-MAX_LOGS),entry.lastOutputAt=Date.now(),proc.status==="idle"&&(proc.status=entry.lastGoodStatus??"ready");let{status,url}=parseLine(stripVTControlCharacters(line));status&&(status==="error"?this.scheduleErrorRecovery(name):(entry.lastGoodStatus=status,this.clearErrorTimer(name),entry.restartRetries=0,(status==="watching"||status==="ready")&&entry.startupTimer&&(clearTimeout(entry.startupTimer),entry.startupTimer=null)),proc.status=status),url&&(proc.url=url),this.emit("change");}handleUnexpectedExit(workspace,code,signal){if(code===0){this.setStatus(workspace.name,"stopped");return}let entry=this.entry(workspace.name);if(!entry)return;entry.restartRetries+=1;let{restartRetries}=entry;if(restartRetries>MAX_RESTART_RETRIES){entry.process.logs.push(`[hlidskjalf] process exited ${MAX_RESTART_RETRIES} times \u2014 giving up.`),this.setStatus(workspace.name,"error");return}let delay=RESTART_DELAY_MS*2**(restartRetries-1);if(entry.process.logs.push(`[hlidskjalf] process exited unexpectedly (attempt ${restartRetries}/${MAX_RESTART_RETRIES}) \u2014 restarting in ${delay/1e3}s...`),this.setStatus(workspace.name,"error"),signal==="SIGABRT"){this.rebuildFsevents().then(()=>{this.stopping||this.spawn(workspace);}).catch(()=>this.setStatus(workspace.name,"error"));return}let timer=setTimeout(()=>{entry&&(entry.restartTimer=null),this.stopping||this.spawn(workspace);},delay);timer.unref(),entry.restartTimer=timer;}rebuildFsevents(){return new Promise(resolve2=>{let child=spawn("pnpm",["rebuild","fsevents"],{cwd:this.root,stdio:"pipe",env:safeEnv()});this.pendingRebuilds.add(child);let done=()=>{this.pendingRebuilds.delete(child),resolve2();};child.on("close",done),child.on("error",done);})}startHeartbeat(){this.heartbeatInterval=setInterval(()=>{let now=Date.now();for(let[name,entry]of this.entries){let{status}=entry.process,url=entry.process.url;if(status==="idle"&&url){this.probeUrl(url).then(alive=>{alive&&(entry.lastOutputAt=Date.now(),this.setStatus(name,entry.lastGoodStatus??"ready"));});continue}status!=="watching"&&status!=="ready"||entry.lastOutputAt&&now-entry.lastOutputAt>IDLE_THRESHOLD_MS&&(url?this.probeUrl(url).then(alive=>{alive?entry.lastOutputAt=Date.now():this.setStatus(name,"idle");}):this.setStatus(name,"idle"));}},HEARTBEAT_INTERVAL_MS),this.heartbeatInterval.unref();}probeUrl(url){return new Promise(resolve2=>{let req=(url.startsWith("https")?https:http).get(url,{timeout:3e3},res=>{res.resume(),resolve2(true);});req.on("error",()=>resolve2(false)),req.on("timeout",()=>{req.destroy(),resolve2(false);});})}scheduleErrorRecovery(name){this.clearErrorTimer(name);let entry=this.entry(name);if(!entry)return;let timer=setTimeout(()=>{entry.errorTimer=null,entry.process.status==="error"&&this.setStatus(name,entry.lastGoodStatus??"ready");},ERROR_RECOVERY_MS);timer.unref(),entry.errorTimer=timer;}clearErrorTimer(name){let entry=this.entry(name);entry?.errorTimer&&(clearTimeout(entry.errorTimer),entry.errorTimer=null);}setStatus(name,status){let entry=this.entry(name);entry&&(entry.process.status=status,status==="error"&&entry.process.workspace.kind==="package"&&this.notifyDependents(name),this.emit("change"));}notifyDependents(failedName){for(let workspace of this.allWorkspaces){if(!workspace.deps.includes(failedName))continue;let entry=this.entry(workspace.name);entry&&entry.process.logs.push(`[hlidskjalf] warning: dependency ${failedName} entered error state`);}}};function createRunner(root){return new ProcessRunner(root)}var VALID_PKG_NAME=/^(@[a-z0-9\-~][a-z0-9\-._~]*\/)?[a-z0-9\-~][a-z0-9\-._~]*$/;function isValidPackageName(name){return VALID_PKG_NAME.test(name)&&name.length<=214}function readJson(path){try{let raw=JSON.parse(readFileSync(path,"utf-8"));if(typeof raw!="object"||raw===null||Array.isArray(raw))return null;let obj=raw,name=typeof obj.name=="string"?obj.name:void 0,scripts=typeof obj.scripts=="object"&&obj.scripts!==null&&!Array.isArray(obj.scripts)?obj.scripts:void 0,dependencies=typeof obj.dependencies=="object"&&obj.dependencies!==null&&!Array.isArray(obj.dependencies)?obj.dependencies:void 0;return {name,scripts,dependencies}}catch{return null}}function workspaceDeps(pkg){return Object.entries(pkg.dependencies??{}).filter(([name,v])=>v.startsWith("workspace:")&&isValidPackageName(name)).map(([name])=>name)}var kindOrder={package:0,app:1,service:1};function discover(root){let results=[],dirs=[["packages","package"],["apps","app"],["services","service"]],resolvedRoot=resolve(root);for(let[dir,kind]of dirs){let base=join(resolvedRoot,dir);if(existsSync(base))for(let entry of readdirSync(base,{withFileTypes:true})){if(!entry.isDirectory())continue;let entryPath=join(base,entry.name);try{if(!realpathSync(entryPath).startsWith(resolvedRoot+sep))continue}catch{continue}let pkg=readJson(join(entryPath,"package.json"));pkg?.name&&isValidPackageName(pkg.name)&&pkg.name!=="hlidskjalf"&&pkg.scripts?.dev&&results.push({name:pkg.name,kind,deps:workspaceDeps(pkg)});}}return results}function sortByDeps(workspaces){let names=new Set(workspaces.map(w=>w.name));return [...workspaces].sort((a,b)=>{if(a.kind!==b.kind)return kindOrder[a.kind]-kindOrder[b.kind];let aDeps=a.deps.filter(d=>names.has(d)).length,bDeps=b.deps.filter(d=>names.has(d)).length;return aDeps-bDeps})}function sortByName(workspaces){return [...workspaces].sort((a,b)=>a.kind!==b.kind?kindOrder[a.kind]-kindOrder[b.kind]:a.name.localeCompare(b.name))}function filterWorkspaces(workspaces,patterns){let byName=new Map(workspaces.map(w=>[w.name,w])),matches=new Set;for(let pattern of patterns){let transitive=pattern.endsWith("..."),name=transitive?pattern.slice(0,-3):pattern;byName.has(name)&&matches.add(name),transitive&&collectDeps(name,byName,matches);}return workspaces.filter(w=>matches.has(w.name))}function collectDeps(name,byName,collected){let workspace=byName.get(name);if(workspace)for(let dep of workspace.deps)byName.has(dep)&&!collected.has(dep)&&(collected.add(dep),collectDeps(dep,byName,collected));}function useRunner(options2){let{exit}=useApp(),[loading,setLoading]=useState(true),[processes,setProcesses]=useState([]),runnerRef=useRef(null),stoppingRef=useRef(false),stop=useCallback(()=>{if(stoppingRef.current)return;stoppingRef.current=true;let runner=runnerRef.current;runner?runner.shutdown().catch(()=>{}).finally(()=>exit()):exit();},[exit]);return useEffect(()=>((async()=>{let workspaces=discover(options2.root);if(options2.filter&&(workspaces=filterWorkspaces(workspaces,options2.filter)),workspaces.length===0){console.error("No matching workspaces found."),exit();return}let startOrder=sortByDeps(workspaces),sorted=options2.order==="run"?startOrder:sortByName(workspaces),displayOrder=sorted.map(w=>w.name),runner=createRunner(options2.root);runnerRef.current=runner,setProcesses(sorted.map(w=>({workspace:w,status:"pending",logs:[]}))),runner.on("change",()=>{setProcesses(displayOrder.flatMap(name=>{let p=runner.get(name);return p?[p]:[]}));}),setLoading(false),await runner.start(startOrder);})().catch(err=>{console.error("Fatal:",err instanceof Error?err.message:"unexpected error"),exit();}),process.on("SIGTERM",stop),()=>{process.off("SIGTERM",stop);}),[exit,options2.filter,options2.order,options2.root,stop]),{processes,loading,stop}}var colors={accent:"#7C8EF2",accentBright:"#A3B1FF",success:"#50E3A4",warning:"#F5C542",error:"#F2716B",pending:"#6B7280",highlight:"#5EEAD4",muted:"#6B7280",dim:"#4B5563",separator:"#374151",url:"#93C5FD"},statusDisplay={pending:{color:colors.pending,label:"pending",icon:"\u25CB"},building:{color:colors.warning,label:"building",icon:"\u25D1"},watching:{color:colors.success,label:"watching",icon:"\u25CF"},ready:{color:colors.success,label:"watching",icon:"\u25CF"},error:{color:colors.error,label:"error",icon:"\u2716"},stopped:{color:colors.pending,label:"stopped",icon:"\u25CB"},idle:{color:colors.warning,label:"idle",icon:"\u25D1"},timeout:{color:colors.error,label:"timeout",icon:"\u2716"}};function Header({title:title2,ready=false,columns,hints}){let showHints=hints&&columns>=10+hints.length+4;return jsx(Box,{flexDirection:"column",paddingX:1,paddingTop:1,paddingBottom:1,borderStyle:"single",borderColor:colors.separator,borderTop:false,borderLeft:false,borderRight:false,children:jsxs(Box,{children:[jsxs(Box,{flexGrow:1,gap:1,children:[jsx(Text,{color:ready?colors.success:colors.accent,children:"\u25CF"}),jsx(Text,{color:colors.accentBright,bold:true,children:title2})]}),showHints&&jsx(Text,{color:colors.dim,children:hints})]})})}var kindLabel={package:"pkg",app:"app",service:"svc"},HINTS="\u2191/\u2193 j/k select q quit";function ProcessRow({process:proc,selected,nameWidth}){let{color,label,icon}=statusDisplay[proc.status];return jsxs(Box,{paddingX:1,children:[jsx(Text,{color:selected?colors.highlight:colors.dim,children:selected?"\u25B8":" "}),jsx(Text,{children:" "}),jsx(Box,{width:nameWidth,children:jsx(Text,{color:selected?colors.highlight:void 0,bold:selected,wrap:"truncate",children:proc.workspace.name})}),jsx(Box,{width:6,children:jsx(Text,{color:colors.muted,children:kindLabel[proc.workspace.kind]})}),jsx(Box,{width:14,children:jsxs(Text,{color,children:[icon," ",label]})}),jsx(Text,{color:colors.url,children:proc.url??""})]})}function LogPanel({process:proc,height}){let logLines=proc.logs.slice(-height),fillCount=height-logLines.length;return jsxs(Box,{flexDirection:"column",height:height+3,overflow:"hidden",marginX:1,marginTop:1,borderStyle:"round",borderColor:colors.separator,paddingX:1,children:[jsxs(Box,{marginBottom:1,children:[jsx(Text,{color:colors.accentBright,bold:true,children:"Logs"}),jsx(Text,{color:colors.dim,children:" \u203A "}),jsx(Text,{bold:true,children:proc.workspace.name})]}),logLines.map((line,i)=>jsx(Text,{wrap:"truncate",children:line},i)),Array.from({length:fillCount},(_,i)=>jsx(Text,{children:" "},`fill-${i}`))]})}function Dashboard({processes,selectedIndex,title:title2}){let{stdout}=useStdout(),cols=stdout?.columns??80,rows=stdout?.rows??24,allReady=useMemo(()=>processes.length>0&&processes.every(p=>p.status==="ready"||p.status==="watching"),[processes]),nameWidth=useMemo(()=>Math.max(14,...processes.map(p=>p.workspace.name.length+2)),[processes]),logHeight=Math.max(3,rows-processes.length-11),safeIndex=Math.min(selectedIndex,Math.max(0,processes.length-1)),selected=processes[safeIndex];return jsxs(Box,{flexDirection:"column",children:[jsx(Header,{title:title2,ready:allReady,columns:cols,hints:HINTS}),jsxs(Box,{paddingX:1,marginLeft:2,marginTop:1,children:[jsx(Box,{width:nameWidth,children:jsx(Text,{color:colors.muted,bold:true,children:"Name"})}),jsx(Box,{width:6,children:jsx(Text,{color:colors.muted,bold:true,children:"Kind"})}),jsx(Box,{width:14,children:jsx(Text,{color:colors.muted,bold:true,children:"Status"})}),jsx(Text,{color:colors.muted,bold:true,children:"URL"})]}),processes.map((proc,i)=>jsx(ProcessRow,{process:proc,selected:i===safeIndex,nameWidth},proc.workspace.name)),selected&&jsx(LogPanel,{process:selected,height:logHeight})]})}function Loading({title:title2}){let{stdout}=useStdout(),cols=stdout?.columns??80;return jsxs(Box,{flexDirection:"column",children:[jsx(Header,{title:title2,columns:cols}),jsxs(Box,{marginTop:1,paddingX:2,children:[jsx(Text,{color:colors.accent,children:"\u25D1 "}),jsx(Text,{color:colors.muted,children:"Discovering workspaces..."})]})]})}function App({options:options2}){let{processes,loading,stop}=useRunner(options2),cursor=useCursor(processes.length,!loading);return useInput((input,key)=>{(input==="q"||key.ctrl&&input==="c")&&stop();}),loading?jsx(Loading,{title:options2.title}):jsx(Dashboard,{processes,selectedIndex:cursor,title:options2.title})}var{values}=parseArgs({args:process.argv.slice(2),options:{filter:{type:"string",multiple:true},order:{type:"string",default:"alphabetical"},title:{type:"string",default:"Hlidskjalf"}}}),rawFilter=values.filter?.map(v=>v.replace(/^\{(.+)\}$/,"$1")),filter=rawFilter?.filter(v=>{let name=v.endsWith("...")?v.slice(0,-3):v;return isValidPackageName(name)?true:(console.error(`Ignoring invalid filter: ${name}`),false)}),order=values.order==="run"?"run":"alphabetical",title=values.title??"Hlidskjalf",options={root:process.cwd(),order,filter:filter?.length?filter:void 0,title},{waitUntilExit}=render(jsx(App,{options}),{exitOnCtrlC:false});await waitUntilExit();process.exit(0);
|
|
15
|
+
`);buffer=lines.pop()??"";for(let raw of lines){let line=raw.trimEnd();line&&this.handleLine(workspace.name,line);}};child.stdout?.on("data",onData),child.stderr?.on("data",onData),child.on("close",(code,signal)=>{buffer.trim()&&this.handleLine(workspace.name,buffer.trimEnd()),buffer="",!this.stopping&&this.handleUnexpectedExit(workspace,code,signal);}),child.on("error",()=>{let e=this.entry(workspace.name);e?.startupTimer&&(clearTimeout(e.startupTimer),e.startupTimer=null),this.setStatus(workspace.name,"error");});}handleLine(name,raw){if(this.stopping)return;let entry=this.entry(name);if(!entry)return;let line=raw.length>MAX_LINE_LENGTH?raw.slice(0,MAX_LINE_LENGTH):raw,{process:proc}=entry;proc.logs.push(sanitizeForDisplay(line)),proc.logs.length>MAX_LOGS&&proc.logs.splice(0,proc.logs.length-MAX_LOGS),entry.lastOutputAt=Date.now(),proc.status==="idle"&&(proc.status=entry.lastGoodStatus??"ready");let{status,url}=parseLine(stripVTControlCharacters(line));status&&(status==="error"?this.scheduleErrorRecovery(name):(entry.lastGoodStatus=status,this.clearErrorTimer(name),entry.restartRetries=0,(status==="watching"||status==="ready")&&entry.startupTimer&&(clearTimeout(entry.startupTimer),entry.startupTimer=null)),proc.status=status),url&&(proc.url=url),this.emit("change");}handleUnexpectedExit(workspace,code,signal){if(code===0){this.setStatus(workspace.name,"stopped");return}let entry=this.entry(workspace.name);if(!entry)return;entry.restartRetries+=1;let{restartRetries}=entry;if(restartRetries>MAX_RESTART_RETRIES){entry.process.logs.push(`[hlidskjalf] process exited ${MAX_RESTART_RETRIES} times \u2014 giving up.`),this.setStatus(workspace.name,"error");return}let delay=RESTART_DELAY_MS*2**(restartRetries-1);if(entry.process.logs.push(`[hlidskjalf] process exited unexpectedly (attempt ${restartRetries}/${MAX_RESTART_RETRIES}) \u2014 restarting in ${delay/1e3}s...`),this.setStatus(workspace.name,"error"),signal==="SIGABRT"){this.rebuildFsevents().then(()=>{this.stopping||this.spawn(workspace);}).catch(()=>this.setStatus(workspace.name,"error"));return}let timer=setTimeout(()=>{entry&&(entry.restartTimer=null),this.stopping||this.spawn(workspace);},delay);timer.unref(),entry.restartTimer=timer;}rebuildFsevents(){return new Promise(resolve2=>{let child=spawn("pnpm",["rebuild","fsevents"],{cwd:this.root,stdio:"pipe",env:safeEnv()});this.pendingRebuilds.add(child);let done=()=>{this.pendingRebuilds.delete(child),resolve2();};child.on("close",done),child.on("error",done);})}startHeartbeat(){this.heartbeatInterval=setInterval(()=>{let now=Date.now();for(let[name,entry]of this.entries){let{status}=entry.process,url=entry.process.url;if(status==="idle"&&url){this.probeUrl(url).then(alive=>{alive&&(entry.lastOutputAt=Date.now(),this.setStatus(name,entry.lastGoodStatus??"ready"));});continue}status!=="watching"&&status!=="ready"||entry.lastOutputAt&&now-entry.lastOutputAt>IDLE_THRESHOLD_MS&&(url?this.probeUrl(url).then(alive=>{alive?entry.lastOutputAt=Date.now():this.setStatus(name,"idle");}):this.setStatus(name,"idle"));}},HEARTBEAT_INTERVAL_MS),this.heartbeatInterval.unref();}probeUrl(url){return new Promise(resolve2=>{let req=(url.startsWith("https")?https:http).get(url,{timeout:3e3},res=>{res.resume(),resolve2(true);});req.on("error",()=>resolve2(false)),req.on("timeout",()=>{req.destroy(),resolve2(false);});})}scheduleErrorRecovery(name){this.clearErrorTimer(name);let entry=this.entry(name);if(!entry)return;let timer=setTimeout(()=>{entry.errorTimer=null,entry.process.status==="error"&&this.setStatus(name,entry.lastGoodStatus??"ready");},ERROR_RECOVERY_MS);timer.unref(),entry.errorTimer=timer;}clearErrorTimer(name){let entry=this.entry(name);entry?.errorTimer&&(clearTimeout(entry.errorTimer),entry.errorTimer=null);}setStatus(name,status){let entry=this.entry(name);entry&&(entry.process.status=status,status==="error"&&entry.process.workspace.kind==="package"&&this.notifyDependents(name),this.emit("change"));}stopProcess(name){if(this.stopping)return;let entry=this.entry(name);if(!entry)return;entry.restartTimer&&(clearTimeout(entry.restartTimer),entry.restartTimer=null),entry.errorTimer&&(clearTimeout(entry.errorTimer),entry.errorTimer=null),entry.startupTimer&&(clearTimeout(entry.startupTimer),entry.startupTimer=null);let{child}=entry;if(!child||child.exitCode!==null||child.signalCode!==null){this.setStatus(name,"stopped");return}entry.restartRetries=MAX_RESTART_RETRIES+1;let escalate=setTimeout(()=>{child.exitCode===null&&child.kill("SIGKILL");},5e3);escalate.unref(),child.on("close",()=>{clearTimeout(escalate),entry.child=null,entry.restartRetries=0,this.setStatus(name,"stopped");}),child.kill("SIGTERM"),entry.process.logs.push("[hlidskjalf] stopping process..."),this.emit("change");}restartProcess(name){if(this.stopping)return;let entry=this.entry(name);if(!entry)return;let workspace=entry.process.workspace,doRestart=()=>{entry.restartRetries=0,entry.process.url=void 0,entry.process.logs.push("[hlidskjalf] restarting process..."),this.spawn(workspace);},{child}=entry;if(!child||child.exitCode!==null||child.signalCode!==null){doRestart();return}entry.restartRetries=MAX_RESTART_RETRIES+1,entry.restartTimer&&(clearTimeout(entry.restartTimer),entry.restartTimer=null),entry.errorTimer&&(clearTimeout(entry.errorTimer),entry.errorTimer=null),entry.startupTimer&&(clearTimeout(entry.startupTimer),entry.startupTimer=null);let escalate=setTimeout(()=>{child.exitCode===null&&child.kill("SIGKILL");},5e3);escalate.unref(),child.on("close",()=>{clearTimeout(escalate),entry.child=null,doRestart();}),child.kill("SIGTERM"),entry.process.logs.push("[hlidskjalf] stopping process for restart..."),this.emit("change");}startMetrics(){this.collectMetrics(),this.metricsInterval=setInterval(()=>this.collectMetrics(),METRICS_INTERVAL_MS),this.metricsInterval.unref();}collectMetrics(){let rootPids=new Map;for(let[name,entry]of this.entries){let pid=entry.child?.pid;pid&&entry.child?.exitCode===null&&rootPids.set(pid,name);}if(rootPids.size===0)return;let child=spawn("ps",["-e","-o","pid,ppid,%cpu,rss","--no-headers"],{stdio:"pipe"}),output="";child.stdout?.on("data",data=>{output+=data.toString();}),child.on("close",()=>{if(this.stopping)return;let parsed=this.aggregateTreeMetrics(output,rootPids),changed=false;for(let[name,metrics]of parsed){let entry=this.entry(name);entry&&(entry.process.metrics=metrics,changed=true);}changed&&this.emit("change");}),child.on("error",()=>{});}aggregateTreeMetrics(output,rootPids){let children=new Map,procStats=new Map;for(let line of output.trim().split(`
|
|
16
|
+
`)){let parts=line.trim().split(/\s+/);if(parts.length<4)continue;let pid=Number.parseInt(parts[0],10),ppid=Number.parseInt(parts[1],10),cpu=Number.parseFloat(parts[2]),rssKb=Number.parseInt(parts[3],10);if(Number.isNaN(pid)||Number.isNaN(ppid))continue;!Number.isNaN(cpu)&&!Number.isNaN(rssKb)&&procStats.set(pid,{cpu,mem:rssKb*1024});let kids=children.get(ppid);kids||(kids=[],children.set(ppid,kids)),kids.push(pid);}let result=new Map;for(let[rootPid,name]of rootPids){let totalCpu=0,totalMem=0,stack=[rootPid];for(;stack.length>0;){let pid=stack.pop(),stats=procStats.get(pid);stats&&(totalCpu+=stats.cpu,totalMem+=stats.mem);let kids=children.get(pid);kids&&stack.push(...kids);}result.set(name,{cpu:totalCpu,mem:totalMem});}return result}notifyDependents(failedName){for(let workspace of this.allWorkspaces){if(!workspace.deps.includes(failedName))continue;let entry=this.entry(workspace.name);entry&&entry.process.logs.push(`[hlidskjalf] warning: dependency ${failedName} entered error state`);}}};function createRunner(root,metrics=false){return new ProcessRunner(root,metrics)}var VALID_PKG_NAME=/^(@[a-z0-9\-~][a-z0-9\-._~]*\/)?[a-z0-9\-~][a-z0-9\-._~]*$/;function isValidPackageName(name){return VALID_PKG_NAME.test(name)&&name.length<=214}function readJson(path){try{let raw=JSON.parse(readFileSync(path,"utf-8"));if(typeof raw!="object"||raw===null||Array.isArray(raw))return null;let obj=raw,name=typeof obj.name=="string"?obj.name:void 0,scripts=typeof obj.scripts=="object"&&obj.scripts!==null&&!Array.isArray(obj.scripts)?obj.scripts:void 0,dependencies=typeof obj.dependencies=="object"&&obj.dependencies!==null&&!Array.isArray(obj.dependencies)?obj.dependencies:void 0;return {name,scripts,dependencies}}catch{return null}}function workspaceDeps(pkg){return Object.entries(pkg.dependencies??{}).filter(([name,v])=>v.startsWith("workspace:")&&isValidPackageName(name)).map(([name])=>name)}var kindOrder={package:0,app:1,service:1};function discover(root){let results=[],dirs=[["packages","package"],["apps","app"],["services","service"]],resolvedRoot=resolve(root);for(let[dir,kind]of dirs){let base=join(resolvedRoot,dir);if(existsSync(base))for(let entry of readdirSync(base,{withFileTypes:true})){if(!entry.isDirectory())continue;let entryPath=join(base,entry.name);try{if(!realpathSync(entryPath).startsWith(resolvedRoot+sep))continue}catch{continue}let pkg=readJson(join(entryPath,"package.json"));pkg?.name&&isValidPackageName(pkg.name)&&pkg.name!=="hlidskjalf"&&pkg.scripts?.dev&&results.push({name:pkg.name,kind,deps:workspaceDeps(pkg)});}}return results}function sortByDeps(workspaces){let names=new Set(workspaces.map(w=>w.name));return [...workspaces].sort((a,b)=>{if(a.kind!==b.kind)return kindOrder[a.kind]-kindOrder[b.kind];let aDeps=a.deps.filter(d=>names.has(d)).length,bDeps=b.deps.filter(d=>names.has(d)).length;return aDeps-bDeps})}function sortByName(workspaces){return [...workspaces].sort((a,b)=>a.kind!==b.kind?kindOrder[a.kind]-kindOrder[b.kind]:a.name.localeCompare(b.name))}function filterWorkspaces(workspaces,patterns){let byName=new Map(workspaces.map(w=>[w.name,w])),matches=new Set;for(let pattern of patterns){let transitive=pattern.endsWith("..."),name=transitive?pattern.slice(0,-3):pattern;byName.has(name)&&matches.add(name),transitive&&collectDeps(name,byName,matches);}return workspaces.filter(w=>matches.has(w.name))}function collectDeps(name,byName,collected){let workspace=byName.get(name);if(workspace)for(let dep of workspace.deps)byName.has(dep)&&!collected.has(dep)&&(collected.add(dep),collectDeps(dep,byName,collected));}function useRunner(options2){let{exit}=useApp(),[loading,setLoading]=useState(true),[processes,setProcesses]=useState([]),runnerRef=useRef(null),stoppingRef=useRef(false),stop=useCallback(()=>{if(stoppingRef.current)return;stoppingRef.current=true;let runner=runnerRef.current;runner?runner.shutdown().catch(()=>{}).finally(()=>exit()):exit();},[exit]);useEffect(()=>((async()=>{let workspaces=discover(options2.root);if(options2.filter&&(workspaces=filterWorkspaces(workspaces,options2.filter)),workspaces.length===0){console.error("No matching workspaces found."),exit();return}let startOrder=sortByDeps(workspaces),sorted=options2.order==="run"?startOrder:sortByName(workspaces),displayOrder=sorted.map(w=>w.name),runner=createRunner(options2.root,options2.metrics);runnerRef.current=runner,setProcesses(sorted.map(w=>({workspace:w,status:"pending",logs:[]}))),runner.on("change",()=>{setProcesses(displayOrder.flatMap(name=>{let p=runner.get(name);return p?[p]:[]}));}),setLoading(false),await runner.start(startOrder);})().catch(err=>{console.error("Fatal:",err instanceof Error?err.message:"unexpected error"),exit();}),process.on("SIGTERM",stop),()=>{process.off("SIGTERM",stop);}),[exit,options2.filter,options2.metrics,options2.order,options2.root,stop]);let stopProcess=useCallback(name=>{runnerRef.current?.stopProcess(name);},[]),restartProcess=useCallback(name=>{runnerRef.current?.restartProcess(name);},[]);return {processes,loading,stop,stopProcess,restartProcess}}var colors={accent:"#7C8EF2",accentBright:"#A3B1FF",success:"#50E3A4",warning:"#F5C542",error:"#F2716B",pending:"#6B7280",highlight:"#5EEAD4",muted:"#6B7280",dim:"#4B5563",separator:"#374151",url:"#93C5FD"},statusDisplay={pending:{color:colors.pending,label:"pending",icon:"\u25CB"},building:{color:colors.warning,label:"building",icon:"\u25D1"},watching:{color:colors.success,label:"watching",icon:"\u25CF"},ready:{color:colors.success,label:"watching",icon:"\u25CF"},error:{color:colors.error,label:"error",icon:"\u2716"},stopped:{color:colors.pending,label:"stopped",icon:"\u25CB"},idle:{color:colors.warning,label:"idle",icon:"\u25D1"},timeout:{color:colors.error,label:"timeout",icon:"\u2716"}};function Header({title:title2,ready=false,columns,hints}){let showHints=hints&&columns>=10+hints.length+4;return jsx(Box,{flexDirection:"column",paddingX:1,paddingTop:1,paddingBottom:1,borderStyle:"single",borderColor:colors.separator,borderTop:false,borderLeft:false,borderRight:false,children:jsxs(Box,{gap:2,children:[jsxs(Box,{flexShrink:0,gap:1,children:[jsx(Text,{color:ready?colors.success:colors.accent,children:"\u25CF"}),jsx(Text,{color:colors.accentBright,bold:true,children:title2})]}),showHints&&jsx(Box,{flexGrow:1,justifyContent:"flex-end",children:jsx(Text,{color:colors.dim,wrap:"truncate-end",children:hints})})]})})}var kindLabel={package:"pkg",app:"app",service:"svc"},HINTS="\u2191/\u2193 j/k select s stop/start r restart q quit";function formatCpu(cpu){return `${cpu.toFixed(1)}%`}function formatMem(bytes){return bytes<1024*1024?`${(bytes/1024).toFixed(0)} K`:bytes<1024*1024*1024?`${(bytes/(1024*1024)).toFixed(1)} M`:`${(bytes/(1024*1024*1024)).toFixed(1)} G`}function memColor(bytes){return bytes>512*1024*1024?colors.error:bytes>256*1024*1024?colors.warning:colors.muted}function ProcessRow({process:proc,selected,nameWidth,showMetrics}){let{color,label,icon}=statusDisplay[proc.status];return jsxs(Box,{paddingX:1,children:[jsx(Text,{color:selected?colors.highlight:colors.dim,children:selected?"\u25B8":" "}),jsx(Text,{children:" "}),jsx(Box,{width:nameWidth,children:jsx(Text,{color:selected?colors.highlight:void 0,bold:selected,wrap:"truncate",children:proc.workspace.name})}),jsx(Box,{width:6,children:jsx(Text,{color:colors.muted,children:kindLabel[proc.workspace.kind]})}),jsx(Box,{width:14,children:jsxs(Text,{color,children:[icon," ",label]})}),showMetrics&&jsx(MetricsCells,{metrics:proc.metrics}),jsx(Text,{color:colors.url,children:proc.url??""})]})}function MetricsCells({metrics}){return metrics?jsxs(Fragment,{children:[jsx(Box,{width:8,children:jsx(Text,{color:metrics.cpu>80?colors.error:colors.muted,children:formatCpu(metrics.cpu)})}),jsx(Box,{width:9,children:jsx(Text,{color:memColor(metrics.mem),children:formatMem(metrics.mem)})})]}):jsxs(Fragment,{children:[jsx(Box,{width:8,children:jsx(Text,{color:colors.dim,children:"\u2014"})}),jsx(Box,{width:9,children:jsx(Text,{color:colors.dim,children:"\u2014"})})]})}function LogPanel({process:proc,height}){let logLines=proc.logs.slice(-height),fillCount=height-logLines.length;return jsxs(Box,{flexDirection:"column",height:height+3,overflow:"hidden",marginX:1,marginTop:1,borderStyle:"round",borderColor:colors.separator,paddingX:1,children:[jsxs(Box,{marginBottom:1,children:[jsx(Text,{color:colors.accentBright,bold:true,children:"Logs"}),jsx(Text,{color:colors.dim,children:" \u203A "}),jsx(Text,{bold:true,children:proc.workspace.name})]}),logLines.map((line,i)=>jsx(Text,{wrap:"truncate",children:line},i)),Array.from({length:fillCount},(_,i)=>jsx(Text,{children:" "},`fill-${i}`))]})}function Dashboard({processes,selectedIndex,title:title2,metrics=false}){let{stdout}=useStdout(),cols=stdout?.columns??80,rows=stdout?.rows??24,allReady=useMemo(()=>processes.length>0&&processes.every(p=>p.status==="ready"||p.status==="watching"),[processes]),nameWidth=useMemo(()=>Math.max(14,...processes.map(p=>p.workspace.name.length+2)),[processes]),logHeight=Math.max(3,rows-processes.length-11),safeIndex=Math.min(selectedIndex,Math.max(0,processes.length-1)),selected=processes[safeIndex];return jsxs(Box,{flexDirection:"column",children:[jsx(Header,{title:title2,ready:allReady,columns:cols,hints:HINTS}),jsxs(Box,{paddingX:1,marginLeft:2,marginTop:1,children:[jsx(Box,{width:nameWidth,children:jsx(Text,{color:colors.muted,bold:true,children:"Name"})}),jsx(Box,{width:6,children:jsx(Text,{color:colors.muted,bold:true,children:"Kind"})}),jsx(Box,{width:14,children:jsx(Text,{color:colors.muted,bold:true,children:"Status"})}),metrics&&jsxs(Fragment,{children:[jsx(Box,{width:8,children:jsx(Text,{color:colors.muted,bold:true,children:"CPU"})}),jsx(Box,{width:9,children:jsx(Text,{color:colors.muted,bold:true,children:"MEM"})})]}),jsx(Text,{color:colors.muted,bold:true,children:"URL"})]}),processes.map((proc,i)=>jsx(ProcessRow,{process:proc,selected:i===safeIndex,nameWidth,showMetrics:metrics},proc.workspace.name)),selected&&jsx(LogPanel,{process:selected,height:logHeight})]})}function Loading({title:title2}){let{stdout}=useStdout(),cols=stdout?.columns??80;return jsxs(Box,{flexDirection:"column",children:[jsx(Header,{title:title2,columns:cols}),jsxs(Box,{marginTop:1,paddingX:2,children:[jsx(Text,{color:colors.accent,children:"\u25D1 "}),jsx(Text,{color:colors.muted,children:"Discovering workspaces..."})]})]})}function App({options:options2}){let{processes,loading,stop,stopProcess,restartProcess}=useRunner(options2),cursor=useCursor(processes.length,!loading);return useInput((input,key)=>{(input==="q"||key.ctrl&&input==="c")&&stop();let selected=processes[cursor];selected&&(input==="s"&&(selected.status==="stopped"?restartProcess(selected.workspace.name):stopProcess(selected.workspace.name)),input==="r"&&restartProcess(selected.workspace.name));}),loading?jsx(Loading,{title:options2.title}):jsx(Dashboard,{processes,selectedIndex:cursor,title:options2.title,metrics:options2.metrics})}var{values}=parseArgs({args:process.argv.slice(2),options:{filter:{type:"string",multiple:true},order:{type:"string",default:"alphabetical"},title:{type:"string",default:"Hlidskjalf"},metrics:{type:"boolean",default:false}}}),rawFilter=values.filter?.map(v=>v.replace(/^\{(.+)\}$/,"$1")),filter=rawFilter?.filter(v=>{let name=v.endsWith("...")?v.slice(0,-3):v;return isValidPackageName(name)?true:(console.error(`Ignoring invalid filter: ${name}`),false)}),order=values.order==="run"?"run":"alphabetical",title=values.title??"Hlidskjalf",options={root:process.cwd(),order,filter:filter?.length?filter:void 0,title,metrics:values.metrics??false},{waitUntilExit}=render(jsx(App,{options}),{exitOnCtrlC:false});await waitUntilExit();process.exit(0);
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hlidskjalf",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "A Terminal User Interface for monitoring Turborepo tasks, built with Ink
|
|
3
|
+
"version": "0.2.5",
|
|
4
|
+
"description": "A Terminal User Interface for monitoring Turborepo tasks, built with Ink",
|
|
5
5
|
"type": "module",
|
|
6
|
+
"author": "Charlie Beckstrand",
|
|
6
7
|
"license": "MIT",
|
|
7
8
|
"keywords": [
|
|
8
9
|
"turborepo",
|