infra-kit 0.1.132 → 0.1.133

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/dist/mcp.js CHANGED
@@ -1,2 +1,2 @@
1
- import{P as l,f as m}from"./chunk-GQHUWUJL.js";import"./chunk-RXEMP3VI.js";import"./chunk-RSF5XAQJ.js";import{t as c,u as p,v as s}from"./chunk-V37MZDFR.js";import"./chunk-Z6KTUVIC.js";import"./chunk-LSEYIAWK.js";import"./chunk-6FU2TRU5.js";import"./chunk-CHETBZ6M.js";import"./chunk-6EPRH34O.js";import"./chunk-LTKR3F4N.js";import{StdioServerTransport as g}from"@modelcontextprotocol/sdk/server/stdio.js";import S from"node:process";import o from"node:process";var d=r=>{o.on("SIGINT",()=>{r.info({msg:"Received SIGINT. Shutting down..."}),o.exit(0)}),o.on("SIGTERM",()=>{r.info({msg:"Received SIGTERM. Shutting down..."}),o.exit(0)}),o.on("uncaughtException",e=>{r.fatal({err:e,msg:"Uncaught Exception"}),r.error(`Uncaught Exception! Check ${c}. Shutting down...`),r.flush(),o.exit(1)}),o.on("unhandledRejection",(e,n)=>{r.fatal({reason:e,promise:n,msg:"Unhandled Rejection"}),r.error(`Unhandled Rejection! Check ${c}. Shutting down...`),r.flush(),o.exit(1)})};import{McpServer as h}from"@modelcontextprotocol/sdk/server/mcp.js";var u=async r=>{};var f=async r=>{};var a=({toolName:r,handler:e})=>async n=>{s.info({msg:`Tool execution started: ${r}`,params:n});try{await m();let i=await e({...n,confirmedCommand:!0});return s.info({msg:`Tool execution successful: ${r}`}),i}catch(i){throw s.error({err:i,params:n,msg:`Tool execution failed: ${r}`}),i}};var v=async r=>{for(let e of l())r.registerTool(e.name,{description:e.description,inputSchema:e.inputSchema,outputSchema:e.outputSchema},a({toolName:e.name,handler:e.handler}))};async function x(){let r=new h({name:"infra-kit",version:"1.0.0"},{capabilities:{resources:{},tools:{},prompts:{}}});return await u(r),await f(r),await v(r),r}var t=p(),T=async()=>{let r;try{r=await x(),t.info("MCP Server instance created")}catch(e){t.error({err:e,msg:"Failed to create MCP server"}),t.error("Fatal error during server creation."),S.exit(1)}try{let e=new g;await r.connect(e),t.info({msg:"Server connected to transport. Ready."})}catch(e){t.error({err:e,msg:"Failed to initialize server"}),t.error("Fatal error during server transport init."),S.exit(1)}};d(t);T();
1
+ import{Q as l,f as m}from"./chunk-WSO6DN6Q.js";import"./chunk-RXEMP3VI.js";import"./chunk-RSF5XAQJ.js";import{t as c,u as p,v as s}from"./chunk-V37MZDFR.js";import"./chunk-Z6KTUVIC.js";import"./chunk-6FDGBQGX.js";import"./chunk-6FU2TRU5.js";import"./chunk-CHETBZ6M.js";import"./chunk-6EPRH34O.js";import"./chunk-LTKR3F4N.js";import{StdioServerTransport as g}from"@modelcontextprotocol/sdk/server/stdio.js";import S from"node:process";import o from"node:process";var d=r=>{o.on("SIGINT",()=>{r.info({msg:"Received SIGINT. Shutting down..."}),o.exit(0)}),o.on("SIGTERM",()=>{r.info({msg:"Received SIGTERM. Shutting down..."}),o.exit(0)}),o.on("uncaughtException",e=>{r.fatal({err:e,msg:"Uncaught Exception"}),r.error(`Uncaught Exception! Check ${c}. Shutting down...`),r.flush(),o.exit(1)}),o.on("unhandledRejection",(e,n)=>{r.fatal({reason:e,promise:n,msg:"Unhandled Rejection"}),r.error(`Unhandled Rejection! Check ${c}. Shutting down...`),r.flush(),o.exit(1)})};import{McpServer as h}from"@modelcontextprotocol/sdk/server/mcp.js";var u=async r=>{};var f=async r=>{};var a=({toolName:r,handler:e})=>async n=>{s.info({msg:`Tool execution started: ${r}`,params:n});try{await m();let i=await e({...n,confirmedCommand:!0});return s.info({msg:`Tool execution successful: ${r}`}),i}catch(i){throw s.error({err:i,params:n,msg:`Tool execution failed: ${r}`}),i}};var v=async r=>{for(let e of l())r.registerTool(e.name,{description:e.description,inputSchema:e.inputSchema,outputSchema:e.outputSchema},a({toolName:e.name,handler:e.handler}))};async function x(){let r=new h({name:"infra-kit",version:"1.0.0"},{capabilities:{resources:{},tools:{},prompts:{}}});return await u(r),await f(r),await v(r),r}var t=p(),T=async()=>{let r;try{r=await x(),t.info("MCP Server instance created")}catch(e){t.error({err:e,msg:"Failed to create MCP server"}),t.error("Fatal error during server creation."),S.exit(1)}try{let e=new g;await r.connect(e),t.info({msg:"Server connected to transport. Ready."})}catch(e){t.error({err:e,msg:"Failed to initialize server"}),t.error("Fatal error during server transport init."),S.exit(1)}};d(t);T();
2
2
  //# sourceMappingURL=mcp.js.map
@@ -0,0 +1,8 @@
1
+ import{a as h,c as f}from"./chunk-JD5T4VCB.js";import{a as c}from"./chunk-FAR2L74S.js";import{render as W}from"ink";import A from"node:process";import{Box as m,Static as z,Text as l}from"ink";import{useEffect as F,useState as O}from"react";import{jsx as d,jsxs as R}from"react/jsx-runtime";var y=r=>{let{phase:e,narration:t,readyLines:i}=r,[s,a]=O(0);return F(()=>{if(i)return;let n=setInterval(()=>{a(o=>o+1)},T);return()=>{clearInterval(n)}},[i]),i?d(z,{items:i,children:(n,o)=>d(l,{children:n},o)}):R(m,{flexDirection:"column",children:[R(m,{children:[d(l,{color:"cyan",children:L[s%L.length]}),d(l,{dimColor:!0,children:` ${e}`})]}),t?d(l,{dimColor:!0,children:t}):null]})},L=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"],T=80;import{Box as C,Static as E,Text as x}from"ink";import{Fragment as U,jsx as u,jsxs as N}from"react/jsx-runtime";var S=r=>{let{headerLines:e,logLines:t,footerLines:i}=r,s=[...e,...t],a=P(i,r.maxFooterLines);return N(U,{children:[u(E,{items:s,children:(n,o)=>u(x,{children:n},o)}),u(C,{flexDirection:"column",children:a.map((n,o)=>u(x,{children:n},o))})]})},P=(r,e)=>{if(e==null||e<1||r.length<=e)return r;let t=r.slice(0,e-1);return[...t,` \u2026 +${r.length-t.length} more`]};import H from"node:process";var _="\x1B[2K";var b="\x1B[?25h",I=(r,e)=>`\x1B[${r};${e}r`,w=()=>"\x1B[r",v=(r,e)=>`\x1B[${r};${e}H`,k=r=>r<=0?"":`${`
2
+ `.repeat(r)}\x1B[${r}A`,g=(r,e)=>{if(r.length===0)return"";let t=e-r.length+1;return`\x1B7${r.map((s,a)=>`${v(t+a,1)}${_}${s}`).join("")}\x1B8`},$=(r,e)=>e-r<1?"":`${k(r)}${I(1,e-r)}`;var p=class{renderer;stdout;verbose;footerLines=[];installed=!1;resizeBound=!1;constructor(e){this.verbose=e.verbose,this.stdout=e.stdout??H.stdout,this.renderer=new f({appendLog:e.appendLog,verbose:this.verbose,isTTY:!!this.stdout.isTTY,write:t=>{this.stdout.write(t)}})}logFn=(e,t="info")=>{this.log(e,t)};rows(){let e=this.stdout.rows;return e!=null&&e>0?e:24}writeLines(e){for(let t of e)this.stdout.write(`${t}
3
+ `)}narrate(e){this.renderer.teeOnly(e,"info"),this.verbose&&this.stdout.write(`${e}
4
+ `)}log(e,t="info"){(t!=="debug"||this.verbose)&&this.stdout.write(`${e}
5
+ `),this.renderer.teeOnly(e,t)}bootStep(e){this.narrate(e)}stopSpinner(){}applyRegion(){let e=this.rows(),t=$(this.footerLines.length,e);return t===""?!1:(this.stdout.write(`${t}${g(this.footerLines,e)}`),!0)}bindResize(){this.resizeBound||typeof this.stdout.on!="function"||(this.stdout.on("resize",this.onResize),this.resizeBound=!0)}ready(e){this.footerLines=this.renderer.formatReadyLines(e);for(let t of this.footerLines)this.renderer.teeOnly(t,"info");if(!this.stdout.isTTY){this.writeLines(this.footerLines);return}if(!this.applyRegion()){this.writeLines(this.footerLines);return}this.installed=!0,this.bindResize()}event(e){let t=` ${h(new Date)} ${e.tag} ${e.text}`;this.stdout.write(`${t}
6
+ `),this.renderer.teeOnly(`${e.tag} ${e.text}`,"info")}refresh(e){this.footerLines=this.renderer.formatReadyLines(e),this.installed&&this.stdout.write(g(this.footerLines,this.rows()))}onResize=()=>{this.installed&&(this.applyRegion()||this.dispose())};dispose(){if(this.resizeBound&&typeof this.stdout.off=="function"&&(this.stdout.off("resize",this.onResize),this.resizeBound=!1),this.installed){let e=this.rows();this.stdout.write(`${w()}${v(e,1)}
7
+ ${b}`),this.installed=!1}}};import{jsx as D}from"react/jsx-runtime";var B=class{renderer;renderFn;stdout;verbose;now;appendLog;instance=null;finalized=!1;handoff=null;persistent=!1;phase="";narration="";headerLines=[];logLines=[];footerLines=[];constructor(e){this.verbose=e.verbose??!1,this.renderFn=e.render??W,this.stdout=e.stdout??c(A.stdout),this.appendLog=e.appendLog,this.now=e.now??(()=>new Date),this.renderer=new f({appendLog:e.appendLog,verbose:this.verbose,isTTY:!0,write:t=>{this.stdout.write(t)},now:this.now})}logFn=(e,t="info")=>{this.log(e,t)};renderBoot(e){let t=D(y,{phase:this.phase,narration:this.narration,readyLines:e});if(this.instance){this.instance.rerender(t);return}this.instance=this.renderFn(t,{stdout:this.stdout,exitOnCtrlC:!1,patchConsole:!1})}unmount(){this.instance&&(this.instance.unmount(),this.instance=null)}narrate(e){if(this.handoff){this.handoff.narrate(e);return}if(this.renderer.teeOnly(e,"info"),this.persistent){this.verbose&&(this.logLines.push(e),this.rerenderPersistent());return}this.finalized||(this.narration=e,this.instance&&this.renderBoot())}log(e,t="info"){if(this.handoff){this.handoff.log(e,t);return}let i=t!=="debug"||this.verbose;if(this.persistent){this.renderer.teeOnly(e,t),i&&(this.logLines.push(e),this.rerenderPersistent());return}i&&this.unmount(),this.renderer.log(e,t)}bootStep(e){this.renderer.teeOnly(e,"info"),!this.finalized&&(this.phase=e,this.renderBoot())}stopSpinner(){this.persistent||this.unmount()}ready(e){if(e.hasUiChild){this.readyHandoff(e);return}this.readyPersistent(e)}readyHandoff(e){this.finalized=!0,this.unmount(),this.handoff=new p({appendLog:this.appendLog,verbose:this.verbose,stdout:this.stdout}),this.handoff.ready(e)}readyPersistent(e){this.headerLines=this.renderer.formatHeaderLines(e),this.footerLines=this.renderer.formatFooterLines(e),this.logLines=[];for(let t of this.headerLines)this.renderer.teeOnly(t,"info");this.finalized=!0,this.persistent=!0,typeof this.stdout.on=="function"&&this.stdout.on("resize",this.onResize),this.rerenderPersistent()}maxFooterLines(){return Math.max(3,(this.stdout.rows??24)-6)}rerenderPersistent(){let e=D(S,{headerLines:this.headerLines,logLines:this.logLines,footerLines:this.footerLines,maxFooterLines:this.maxFooterLines()});if(this.instance){this.instance.rerender(e);return}this.instance=this.renderFn(e,{stdout:this.stdout,exitOnCtrlC:!1,patchConsole:!1})}onResize=()=>{this.persistent&&this.rerenderPersistent()};event(e){if(this.handoff){this.handoff.event(e);return}if(this.persistent){let t=` ${h(this.now())} ${e.tag} ${e.text}`;this.renderer.teeOnly(`${e.tag} ${e.text}`,"info"),this.logLines.push(t),this.rerenderPersistent();return}this.unmount(),this.renderer.event(e)}refresh(e){if(this.handoff){this.handoff.refresh(e);return}this.persistent&&(this.footerLines=this.renderer.formatFooterLines(e),this.rerenderPersistent())}dispose(){if(this.handoff){this.handoff.dispose(),this.handoff=null;return}this.persistent&&(this.rerenderPersistent(),typeof this.stdout.off=="function"&&this.stdout.off("resize",this.onResize)),this.finalized=!0,this.persistent=!1,this.unmount()}};export{B as PersistentInkDevUi};
8
+ //# sourceMappingURL=persistent-ink-dev-ui-FUJQDFNY.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/tui/dev-ui/persistent-ink-dev-ui.tsx", "../src/tui/dev-ui/boot-region.tsx", "../src/tui/dev-ui/live-region.tsx", "../src/tui/dev-ui/scroll-region-dev-ui.ts", "../src/dev/scroll-region.ts"],
4
+ "sourcesContent": ["import { render as inkRender } from 'ink'\nimport process from 'node:process'\nimport type { ReactElement } from 'react'\n\nimport type { DevUi, LogLevel, ReadySummary } from 'src/dev/dev-ui'\nimport { DevRenderer, formatClock } from 'src/dev/render'\nimport { createSafeStream } from 'src/tui/safe-stderr'\n\nimport { BootRegion } from './boot-region'\nimport { LiveRegion } from './live-region'\nimport { ScrollRegionDevUi } from './scroll-region-dev-ui'\n\n/**\n * Ink-backed {@link DevUi} for `infra-kit dev` that keeps a PERSISTENT live footer after `ready` on a\n * backend-only session. It is the default UI on any interactive TTY: the boot phase is identical to the\n * plain path, but at {@link ready} it branches on `summary.hasUiChild`.\n *\n * - UI session (`hasUiChild`): unmount Ink and HAND OFF to a {@link ScrollRegionDevUi}, which pins the\n * ready header as a DECSTBM scroll-region footer. The `turbo run dev` child's piped output makes the\n * tail unbounded, and Ink repaints its live region on every appended line; the scroll region appends\n * with a single write instead. The scroll-region UI owns `ready`/`event`/`log`/`refresh`/`dispose`\n * for the rest of the session.\n * - Backend-only (`!hasUiChild`): mount {@link LiveRegion} and STAY mounted \u2014 the header is committed\n * once via `<Static>`, logs accrete above the footer, and `refresh` repaints the footer in place as\n * health flips.\n *\n * The embedded {@link DevRenderer} owns the file-tee format (`teeOnly`) exactly as in `InkDevUi`; in\n * persistent mode it is used ONLY for teeing (never its direct stdout write, which would collide with\n * the live region). Every component here is OUTPUT-ONLY (no `useInput`) \u2014 see live-region.tsx.\n */\n\n/** The subset of an Ink render handle this UI drives \u2014 shared by `ink` and `ink-testing-library`. */\ninterface RenderHandle {\n rerender: (tree: ReactElement) => void\n unmount: () => void\n}\n\n/** Render seam: `ink`'s `render` by default; `ink-testing-library`'s `render` is injected in tests. */\ntype RenderFn = (tree: ReactElement, options?: unknown) => RenderHandle\n\nexport interface PersistentInkDevUiDeps {\n /** File tee \u2014 the same appender the runner hands the plain renderer. */\n appendLog: (text: string) => void\n /** Mirrors the renderer's verbose gate: only in verbose does a `debug` log reach the terminal. */\n verbose?: boolean\n /** Clock seam (for deterministic tee + event timestamps in tests). */\n now?: () => Date\n /** Render seam (default `ink`'s `render`); tests inject `ink-testing-library`'s `render`. */\n render?: RenderFn\n /** Target stream (default: `process.stdout` behind the scrollback-safe filter). */\n stdout?: NodeJS.WriteStream\n}\n\nexport class PersistentInkDevUi implements DevUi {\n private readonly renderer: DevRenderer\n private readonly renderFn: RenderFn\n private readonly stdout: NodeJS.WriteStream\n private readonly verbose: boolean\n private readonly now: () => Date\n /** The file tee appender \u2014 kept so a UI-session handoff can hand the same sink to {@link ScrollRegionDevUi}. */\n private readonly appendLog: (text: string) => void\n /** Live Ink handle, or `null` when not mounted. */\n private instance: RenderHandle | null = null\n /** True once boot is done: never mount the boot region again. */\n private finalized = false\n /**\n * The scroll-region UI a UI session hands off to at {@link ready}, or `null` for a backend-only\n * session. Once set, every post-ready seam (`log`/`event`/`refresh`/`dispose`) delegates to it.\n */\n private handoff: ScrollRegionDevUi | null = null\n /** True while the persistent live region owns the terminal (backend-only session after ready). */\n private persistent = false\n private phase = ''\n private narration = ''\n private headerLines: string[] = []\n private logLines: string[] = []\n private footerLines: string[] = []\n\n constructor(deps: PersistentInkDevUiDeps) {\n this.verbose = deps.verbose ?? false\n this.renderFn = deps.render ?? (inkRender as RenderFn)\n // NOT raw `process.stdout`: Ink writes `ESC[3J` (erase saved lines) whenever a frame overflows the\n // viewport, which wipes the terminal's SCROLLBACK \u2014 the user's history, and every previous command's\n // output when `dev` runs as a session-shell child. `createSafeStream` drops that one escape and\n // forwards everything else live. The handoff at `ready` passes this same stream to ScrollRegionDevUi,\n // so both UIs are covered by this single default.\n this.stdout = deps.stdout ?? createSafeStream(process.stdout as NodeJS.WriteStream)\n this.appendLog = deps.appendLog\n this.now =\n deps.now ??\n ((): Date => {\n return new Date()\n })\n this.renderer = new DevRenderer({\n appendLog: deps.appendLog,\n verbose: this.verbose,\n isTTY: true,\n write: (text) => {\n this.stdout.write(text)\n },\n now: this.now,\n })\n }\n\n /** `LogFn`-shaped adapter for the build runner seam. */\n readonly logFn = (message: string, level: LogLevel = 'info'): void => {\n this.log(message, level)\n }\n\n // ---- boot (identical to InkDevUi) --------------------------------------\n\n /** Mount the Ink boot region (or rerender it in place) with the current phase + narration. */\n private renderBoot(readyLines?: string[]): void {\n const tree = <BootRegion phase={this.phase} narration={this.narration} readyLines={readyLines} />\n\n if (this.instance) {\n this.instance.rerender(tree)\n\n return\n }\n this.instance = this.renderFn(tree, { stdout: this.stdout, exitOnCtrlC: false, patchConsole: false })\n }\n\n /** Unmount Ink if mounted (idempotent). */\n private unmount(): void {\n if (this.instance) {\n this.instance.unmount()\n this.instance = null\n }\n }\n\n narrate(message: string): void {\n if (this.handoff) {\n this.handoff.narrate(message)\n\n return\n }\n this.renderer.teeOnly(message, 'info')\n if (this.persistent) {\n // Visible only in verbose (matches the plain renderer); tee already happened above.\n if (this.verbose) {\n this.logLines.push(message)\n this.rerenderPersistent()\n }\n\n return\n }\n if (this.finalized) {\n return\n }\n this.narration = message\n if (this.instance) {\n this.renderBoot()\n }\n }\n\n log(message: string, level: LogLevel = 'info'): void {\n if (this.handoff) {\n this.handoff.log(message, level)\n\n return\n }\n const willWrite = level !== 'debug' || this.verbose\n\n if (this.persistent) {\n // Never the embedded renderer's direct stdout write here \u2014 append to the live region instead.\n this.renderer.teeOnly(message, level)\n if (willWrite) {\n this.logLines.push(message)\n this.rerenderPersistent()\n }\n\n return\n }\n if (willWrite) {\n this.unmount()\n }\n this.renderer.log(message, level)\n }\n\n bootStep(phase: string): void {\n this.renderer.teeOnly(phase, 'info')\n if (this.finalized) {\n return\n }\n this.phase = phase\n this.renderBoot()\n }\n\n stopSpinner(): void {\n if (!this.persistent) {\n this.unmount()\n }\n }\n\n // ---- ready + persistence -----------------------------------------------\n\n ready(summary: ReadySummary): void {\n if (summary.hasUiChild) {\n this.readyHandoff(summary)\n\n return\n }\n this.readyPersistent(summary)\n }\n\n /**\n * UI session: unmount Ink and hand off to a {@link ScrollRegionDevUi}, which pins the ready header as\n * a scroll-region footer above the `turbo run dev` child's high-volume piped tail. From here on every\n * post-ready seam delegates to the handoff (see {@link log}/{@link event}/{@link refresh}/{@link dispose}).\n */\n private readyHandoff(summary: ReadySummary): void {\n this.finalized = true\n this.unmount()\n this.handoff = new ScrollRegionDevUi({ appendLog: this.appendLog, verbose: this.verbose, stdout: this.stdout })\n this.handoff.ready(summary)\n }\n\n /** Backend-only session: commit the header, tee it, mount the live footer, and STAY mounted. */\n private readyPersistent(summary: ReadySummary): void {\n this.headerLines = this.renderer.formatHeaderLines(summary)\n this.footerLines = this.renderer.formatFooterLines(summary)\n this.logLines = []\n for (const line of this.headerLines) {\n this.renderer.teeOnly(line, 'info')\n }\n this.finalized = true\n this.persistent = true\n // Re-clamp the footer when the terminal resizes. Guarded: an injected (test/piped) stream may not\n // be an EventEmitter TTY.\n if (typeof this.stdout.on === 'function') {\n this.stdout.on('resize', this.onResize)\n }\n this.rerenderPersistent()\n }\n\n /**\n * Cap for the pinned footer: it is a live region and cannot scroll, so it must never exceed the\n * viewport. Reserve rows for the scrolling log tail above it; fall back to an 80\u00D724 assumption when\n * the stream reports no row count (piped / test stdout).\n */\n private maxFooterLines(): number {\n return Math.max(3, (this.stdout.rows ?? 24) - 6)\n }\n\n /** Mount or repaint the live region from the current header / logs / footer snapshot. */\n private rerenderPersistent(): void {\n const tree = (\n <LiveRegion\n headerLines={this.headerLines}\n logLines={this.logLines}\n footerLines={this.footerLines}\n maxFooterLines={this.maxFooterLines()}\n />\n )\n\n if (this.instance) {\n this.instance.rerender(tree)\n\n return\n }\n this.instance = this.renderFn(tree, { stdout: this.stdout, exitOnCtrlC: false, patchConsole: false })\n }\n\n /** Re-clamp + repaint the footer on terminal resize (SIGWINCH), so a shrink never overflows the viewport. */\n private readonly onResize = (): void => {\n if (this.persistent) {\n this.rerenderPersistent()\n }\n }\n\n event(input: { tag: string; text: string }): void {\n if (this.handoff) {\n this.handoff.event(input)\n\n return\n }\n if (this.persistent) {\n const line = ` ${formatClock(this.now())} ${input.tag} ${input.text}`\n\n this.renderer.teeOnly(`${input.tag} ${input.text}`, 'info')\n this.logLines.push(line)\n this.rerenderPersistent()\n\n return\n }\n this.unmount()\n this.renderer.event(input)\n }\n\n /** Repaint the live footer in place from a fresh summary (header + logs untouched). No-op if not persistent. */\n refresh(summary: ReadySummary): void {\n if (this.handoff) {\n this.handoff.refresh(summary)\n\n return\n }\n // A session still in boot has no live footer to refresh.\n if (!this.persistent) {\n return\n }\n this.footerLines = this.renderer.formatFooterLines(summary)\n this.rerenderPersistent()\n }\n\n dispose(): void {\n if (this.handoff) {\n this.handoff.dispose()\n this.handoff = null\n\n return\n }\n if (this.persistent) {\n // Commit the last footer state, then release the terminal.\n this.rerenderPersistent()\n if (typeof this.stdout.off === 'function') {\n this.stdout.off('resize', this.onResize)\n }\n }\n this.finalized = true\n this.persistent = false\n this.unmount()\n }\n}\n", "import { Box, Static, Text } from 'ink'\nimport { useEffect, useState } from 'react'\n\n/**\n * The Ink boot region for `infra-kit dev`: an animated spinner + phase line while the server boots,\n * then the ready header committed to scrollback via `<Static>`.\n *\n * HARD INVARIANT \u2014 this component (and every boot component under `src/tui/dev-ui/`) is OUTPUT-ONLY.\n * It MUST NEVER call `useInput`. `useInput` arms Ink's raw mode + Ctrl-C interception, which would\n * steal SIGINT from the process signal handler that owns dev-server shutdown. The spinner animates via\n * a self-contained interval only; there is no keyboard input during boot. Do not add `useInput` here.\n */\n\nexport interface BootRegionProps {\n /** Current boot phase, shown beside the spinner. */\n phase: string\n /** Latest narration detail (dim subtitle under the spinner); empty hides the line. */\n narration: string\n /**\n * When set, boot is done: the pre-formatted ready-header lines are committed to scrollback via\n * `<Static>` and the transient spinner region is gone. The renderer unmounts right after.\n */\n readyLines?: string[]\n}\n\n/** Animated boot spinner + phase/narration, or the committed ready header once `readyLines` is set. */\nexport const BootRegion = (props: BootRegionProps) => {\n const { phase, narration, readyLines } = props\n\n const [frame, setFrame] = useState(0)\n\n useEffect(() => {\n // No spinner once the ready header is committed.\n if (readyLines) {\n return\n }\n\n const timer = setInterval(() => {\n setFrame((f) => {\n return f + 1\n })\n }, SPINNER_INTERVAL_MS)\n\n return () => {\n clearInterval(timer)\n }\n }, [readyLines])\n\n if (readyLines) {\n // `<Static>` writes each line once, above the (now empty) live region, so the header persists in\n // scrollback after unmount. Lines are pre-formatted by DevRenderer.formatReadyLines.\n return (\n <Static items={readyLines}>\n {(line, index) => {\n return <Text key={index}>{line}</Text>\n }}\n </Static>\n )\n }\n\n return (\n <Box flexDirection=\"column\">\n <Box>\n <Text color=\"cyan\">{SPINNER_FRAMES[frame % SPINNER_FRAMES.length]}</Text>\n <Text dimColor>{` ${phase}`}</Text>\n </Box>\n {narration ? <Text dimColor>{narration}</Text> : null}\n </Box>\n )\n}\n\nconst SPINNER_FRAMES = ['\u280B', '\u2819', '\u2839', '\u2838', '\u283C', '\u2834', '\u2826', '\u2827', '\u2807', '\u280F']\nconst SPINNER_INTERVAL_MS = 80\n", "import { Box, Static, Text } from 'ink'\n\n/**\n * The persistent Ink live region for `infra-kit dev` (backend-only sessions): the committed header +\n * growing log scrollback above, and a live health footer re-rendered in place below.\n *\n * HARD INVARIANT \u2014 this component (and every component under `src/tui/dev-ui/`) is OUTPUT-ONLY. It\n * MUST NEVER call `useInput`. `useInput` arms Ink's raw mode + Ctrl-C interception, which would steal\n * SIGINT from the process signal handler that owns dev-server shutdown. There is no keyboard input in\n * the live region \u2014 it is props only, with no state, no effects, no input. Do not add `useInput` here.\n */\n\nexport interface LiveRegionProps {\n /** Reference header, committed once to scrollback via `<Static>` (from `formatHeaderLines`). */\n headerLines: string[]\n /** Log lines committed above the footer as they arrive (also via the SAME `<Static>`). */\n logLines: string[]\n /** Live footer, re-rendered in place on every health change (from `formatFooterLines`). */\n footerLines: string[]\n /**\n * Cap on rendered footer rows so a many-backend session never eats a short viewport (the footer is a\n * live region \u2014 it cannot scroll). Overflow collapses into a trailing dim `\u2026 +N more` line. Omit for\n * no cap. The pinned footer must stay short; the full health picture is always in the log file.\n */\n maxFooterLines?: number\n}\n\n/**\n * A SINGLE `<Static>` streams the header then the growing logs (Ink writes only newly-appended items\n * each commit, so the header lands first and logs accrete beneath it \u2014 one block, never two). The\n * footer is an ordinary `<Box>`, so it repaints in place on each rerender without touching scrollback.\n */\nexport const LiveRegion = (props: LiveRegionProps) => {\n const { headerLines, logLines, footerLines } = props\n\n const staticItems = [...headerLines, ...logLines]\n const footer = clampFooter(footerLines, props.maxFooterLines)\n\n return (\n <>\n <Static items={staticItems}>\n {(line, index) => {\n return <Text key={index}>{line}</Text>\n }}\n </Static>\n <Box flexDirection=\"column\">\n {footer.map((line, index) => {\n return <Text key={index}>{line}</Text>\n })}\n </Box>\n </>\n )\n}\n\n/** Cap the footer to `max` rows, collapsing the overflow into one trailing ` \u2026 +N more` line. */\nexport const clampFooter = (lines: string[], max?: number): string[] => {\n if (max == null || max < 1 || lines.length <= max) {\n return lines\n }\n\n const shown = lines.slice(0, max - 1)\n\n return [...shown, ` \u2026 +${lines.length - shown.length} more`]\n}\n", "import process from 'node:process'\n\nimport type { DevUi, LogLevel, ReadySummary } from 'src/dev/dev-ui'\nimport { DevRenderer, formatClock } from 'src/dev/render'\nimport { SHOW_CURSOR, installRegion, moveTo, paintFooter, resetScrollRegion } from 'src/dev/scroll-region'\n\n/**\n * A {@link DevUi} that pins the `infra-kit dev` ready header to the bottom of the terminal as a STICKY\n * FOOTER using a DECSTBM scroll region, for sessions that run a `turbo run dev` UI child\n * (`summary.hasUiChild`). {@link PersistentInkDevUi} hands off to it at `ready`.\n *\n * Deliberately NO React / NO Ink. A UI session's tail is unbounded \u2014 every vite/HMR line the piped\n * child emits arrives as an `event` \u2014 and Ink's reconciler repaints its live region on each one. The\n * scroll region instead confines ALL scrolling (`log` + `event` lines) to the rows above the footer\n * using terminal escapes alone, so an append costs one `write` and the footer never repaints. See\n * {@link file://../../dev/scroll-region.ts}.\n *\n * The embedded {@link DevRenderer} is the single source of the file-tee format (`teeOnly`) and the\n * ready-header layout (`formatReadyLines`) \u2014 reused, never duplicated \u2014 exactly like the Ink UIs. Every\n * terminal write goes through the injected `stdout` so tests drive a fake stream.\n */\n\nexport interface ScrollRegionDevUiDeps {\n /** File tee \u2014 the same appender the runner hands every renderer. */\n appendLog: (text: string) => void\n /** Only in verbose does boot narration reach the terminal (it is always tee'd to the file). */\n verbose: boolean\n /** Target stream (default `process.stdout`). Its `isTTY`/`rows` gate the whole feature. */\n stdout?: NodeJS.WriteStream\n}\n\nexport class ScrollRegionDevUi implements DevUi {\n /** Embedded plain renderer: owns the file-tee format + the ready-header layout. Never drives a spinner. */\n private readonly renderer: DevRenderer\n private readonly stdout: NodeJS.WriteStream\n private readonly verbose: boolean\n /** The current footer block (the full ready header), recomputed on `ready`/`refresh`. */\n private footerLines: string[] = []\n /** True while a scroll region is installed and the footer is live (false when disabled / disposed). */\n private installed = false\n /** True once the resize listener is attached, so it is added at most once and removed exactly once. */\n private resizeBound = false\n\n constructor(deps: ScrollRegionDevUiDeps) {\n this.verbose = deps.verbose\n this.stdout = deps.stdout ?? process.stdout\n this.renderer = new DevRenderer({\n appendLog: deps.appendLog,\n verbose: this.verbose,\n // Colors follow the real stream: a TTY footer is colored; a piped / captured stream stays plain.\n isTTY: Boolean(this.stdout.isTTY),\n write: (text) => {\n this.stdout.write(text)\n },\n })\n }\n\n /** `LogFn`-shaped adapter for the build runner seam. */\n readonly logFn = (message: string, level: LogLevel = 'info'): void => {\n this.log(message, level)\n }\n\n /**\n * Row count, falling back to 24 (the classic terminal default) for any non-positive or absent value.\n *\n * `rows` is NOT merely `undefined` on a row-less stream: a pty allocated without a window size \u2014 what\n * `script(1)` gives us, and what some CI runners and terminal emulators report before their first\n * `SIGWINCH` \u2014 sets `isTTY === true` alongside `rows === 0`. A `??` fallback passes that `0` straight\n * through, `installRegion` then reads the terminal as \"too short\", and the footer silently never\n * installs on a terminal that is in fact perfectly tall enough.\n */\n private rows(): number {\n const reported = this.stdout.rows\n\n return reported != null && reported > 0 ? reported : 24\n }\n\n /** Write each line as its own terminal row (used for the non-TTY / too-short plain fallback). */\n private writeLines(lines: readonly string[]): void {\n for (const line of lines) {\n this.stdout.write(`${line}\\n`)\n }\n }\n\n narrate(message: string): void {\n this.renderer.teeOnly(message, 'info')\n if (this.verbose) {\n this.stdout.write(`${message}\\n`)\n }\n }\n\n /** A general message: lands inside the scroll region and scrolls normally, plus a file tee. */\n log(message: string, level: LogLevel = 'info'): void {\n if (level !== 'debug' || this.verbose) {\n this.stdout.write(`${message}\\n`)\n }\n this.renderer.teeOnly(message, level)\n }\n\n /** Boot narration only \u2014 no spinner (a spinner would fight the child for the cursor). */\n bootStep(phase: string): void {\n this.narrate(phase)\n }\n\n /** No-op: this UI never runs a spinner. */\n stopSpinner(): void {\n // Nothing to stop.\n }\n\n /**\n * Install (or re-install) the scroll region and paint the footer for the CURRENT terminal size.\n * Returns `false` when the terminal is too short to host both a region and the footer \u2014 the caller\n * then falls back to a one-shot plain print (or disposes, on a shrink).\n */\n private applyRegion(): boolean {\n const rows = this.rows()\n const region = installRegion(this.footerLines.length, rows)\n\n if (region === '') return false\n this.stdout.write(`${region}${paintFooter(this.footerLines, rows)}`)\n\n return true\n }\n\n /** Attach the resize listener once (guarded \u2014 an injected stream may not be an EventEmitter TTY). */\n private bindResize(): void {\n if (this.resizeBound || typeof this.stdout.on !== 'function') return\n this.stdout.on('resize', this.onResize)\n this.resizeBound = true\n }\n\n ready(summary: ReadySummary): void {\n this.footerLines = this.renderer.formatReadyLines(summary)\n for (const line of this.footerLines) {\n this.renderer.teeOnly(line, 'info')\n }\n\n // Not a real terminal (piped / test capture): just print the header once, no region.\n if (!this.stdout.isTTY) {\n this.writeLines(this.footerLines)\n\n return\n }\n\n // Too short for a region \u2192 print plainly and leave the feature disabled (installed stays false).\n if (!this.applyRegion()) {\n this.writeLines(this.footerLines)\n\n return\n }\n this.installed = true\n this.bindResize()\n }\n\n /** One tagged, timestamped tail line \u2014 scrolls inside the region like any other `log`. */\n event(input: { tag: string; text: string }): void {\n const line = ` ${formatClock(new Date())} ${input.tag} ${input.text}`\n\n this.stdout.write(`${line}\\n`)\n this.renderer.teeOnly(`${input.tag} ${input.text}`, 'info')\n }\n\n /** Recompute the footer and repaint it in place \u2014 nothing else is reprinted. No-op when not installed. */\n refresh(summary: ReadySummary): void {\n this.footerLines = this.renderer.formatReadyLines(summary)\n if (!this.installed) return\n this.stdout.write(paintFooter(this.footerLines, this.rows()))\n }\n\n /**\n * Re-clamp on terminal resize (SIGWINCH, surfaced as the stream's `resize` event): re-run the region\n * install + repaint for the new size. If the new size is too short to host the footer, dispose\n * gracefully rather than paint a broken region.\n */\n private readonly onResize = (): void => {\n if (!this.installed) return\n if (!this.applyRegion()) {\n this.dispose()\n }\n }\n\n /**\n * Release the terminal: drop the resize listener, reset the scroll region, drop the cursor onto the\n * last row with a fresh newline, and restore the cursor. Idempotent and safe if never installed \u2014 the\n * region escapes are emitted only while a region is actually live, so a second call (or a disposal of\n * a never-installed instance) writes nothing and leaves the terminal usable.\n */\n dispose(): void {\n if (this.resizeBound && typeof this.stdout.off === 'function') {\n this.stdout.off('resize', this.onResize)\n this.resizeBound = false\n }\n if (this.installed) {\n const rows = this.rows()\n\n this.stdout.write(`${resetScrollRegion()}${moveTo(rows, 1)}\\n${SHOW_CURSOR}`)\n this.installed = false\n }\n }\n}\n", "/**\n * Pure, side-effect-free ANSI builders for a terminal SCROLL REGION (DECSTBM) sticky footer.\n *\n * `infra-kit dev` uses these to pin the ready-header block to the bottom of the terminal while a\n * `turbo run dev` UI child owns the same TTY via inherited stdio. DECSTBM (`ESC [ top ; bottom r`)\n * confines normal scrolling \u2014 from both the runner's own writes AND the inherited child's raw bytes \u2014\n * to the region ABOVE the footer, so the footer rows are never scrolled away. That is why this works\n * where a piping approach could not: the child keeps inherited stdio and needs no per-framework flags.\n *\n * Every function here is a pure string builder: no I/O, no `process` access, no terminal queries. The\n * caller ({@link file://../tui/dev-ui/scroll-region-dev-ui.ts}) owns all writes, the row count, and the\n * TTY guard. Each builder is unit-tested with exact-string assertions.\n */\n\n/** Save the cursor position (DECSC). Paired with {@link RESTORE_CURSOR} around a footer repaint. */\nexport const SAVE_CURSOR = '\\x1B7'\n/** Restore the cursor position saved by {@link SAVE_CURSOR} (DECRC). */\nexport const RESTORE_CURSOR = '\\x1B8'\n/** Erase the entire current line without moving the cursor (EL 2), so a repaint leaves no stale glyphs. */\nexport const CLEAR_LINE = '\\x1B[2K'\n/** Hide the cursor (DECTCEM off) \u2014 avoids a cursor flicker while the footer repaints. */\nexport const HIDE_CURSOR = '\\x1B[?25l'\n/** Show the cursor (DECTCEM on) \u2014 restored on teardown so the terminal is left usable. */\nexport const SHOW_CURSOR = '\\x1B[?25h'\n\n/**\n * Set the vertical scroll region (DECSTBM) to lines `top..bottom` inclusive, 1-indexed. Scrolling is\n * thereafter confined to that band; the rows below `bottom` become a static footer area.\n */\nexport const setScrollRegion = (top: number, bottom: number): string => {\n return `\\x1B[${top};${bottom}r`\n}\n\n/** Reset the scroll region to the full screen (DECSTBM with no params) \u2014 the teardown counterpart. */\nexport const resetScrollRegion = (): string => {\n return '\\x1B[r'\n}\n\n/** Absolute cursor move (CUP) to `row`,`col`, both 1-indexed. */\nexport const moveTo = (row: number, col: number): string => {\n return `\\x1B[${row};${col}H`\n}\n\n/**\n * Scroll existing content up by `n` blank lines, then return the cursor to its starting row, so the\n * bottom `n` rows are cleared for a footer WITHOUT overwriting whatever was already on screen. Emits\n * `n` newlines (which scroll the viewport) followed by cursor-up-`n` (CUU). Returns `''` for `n <= 0`.\n */\nexport const reserveRows = (n: number): string => {\n if (n <= 0) return ''\n\n return `${'\\n'.repeat(n)}\\x1B[${n}A`\n}\n\n/**\n * Paint `lines` into the bottom `lines.length` rows of a `rows`-tall terminal, cursor-safe: save the\n * cursor, absolutely position + clear + write each line (top-most footer row first), then restore the\n * cursor so surrounding scroll output is undisturbed. Returns `''` when there are no lines to paint.\n */\nexport const paintFooter = (lines: readonly string[], rows: number): string => {\n if (lines.length === 0) return ''\n\n const base = rows - lines.length + 1\n const painted = lines\n .map((line, i) => {\n return `${moveTo(base + i, 1)}${CLEAR_LINE}${line}`\n })\n .join('')\n\n return `${SAVE_CURSOR}${painted}${RESTORE_CURSOR}`\n}\n\n/**\n * Reserve `footerHeight` rows at the bottom and confine scrolling to the rows above them: emit\n * {@link reserveRows} then a {@link setScrollRegion} of `1..rows - footerHeight`. Returns `''` when the\n * terminal is too short to host both a scroll region and the footer (`rows - footerHeight < 1`); the\n * caller MUST treat `''` as \"skip the whole feature\" and fall back to a one-shot plain print.\n */\nexport const installRegion = (footerHeight: number, rows: number): string => {\n if (rows - footerHeight < 1) return ''\n\n return `${reserveRows(footerHeight)}${setScrollRegion(1, rows - footerHeight)}`\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["inkRender", "process", "Box", "Static", "Text", "useEffect", "useState", "jsx", "jsxs", "BootRegion", "props", "phase", "narration", "readyLines", "frame", "setFrame", "timer", "f", "SPINNER_INTERVAL_MS", "line", "index", "SPINNER_FRAMES", "Box", "Static", "Text", "Fragment", "jsx", "jsxs", "LiveRegion", "props", "headerLines", "logLines", "footerLines", "staticItems", "footer", "clampFooter", "line", "index", "lines", "max", "shown", "process", "CLEAR_LINE", "SHOW_CURSOR", "setScrollRegion", "top", "bottom", "resetScrollRegion", "moveTo", "row", "col", "reserveRows", "n", "paintFooter", "lines", "rows", "base", "line", "i", "CLEAR_LINE", "installRegion", "footerHeight", "ScrollRegionDevUi", "deps", "process", "DevRenderer", "text", "message", "level", "reported", "lines", "line", "phase", "rows", "region", "installRegion", "paintFooter", "summary", "input", "formatClock", "resetScrollRegion", "moveTo", "SHOW_CURSOR", "jsx", "PersistentInkDevUi", "deps", "inkRender", "createSafeStream", "process", "DevRenderer", "text", "message", "level", "readyLines", "tree", "BootRegion", "willWrite", "phase", "summary", "ScrollRegionDevUi", "line", "LiveRegion", "input", "formatClock"]
7
+ }
@@ -1,2 +1,2 @@
1
- import{h as i}from"./chunk-IGTHMZPR.js";import{a as t}from"./chunk-LSEYIAWK.js";import"./chunk-6FU2TRU5.js";import"./chunk-CHETBZ6M.js";import a from"node:fs";import o from"node:process";import{fileURLToPath as s}from"node:url";var m=r=>{let n=r.indexOf("--parent-pid");if(n===-1)return;let e=Number(r[n+1]);return Number.isInteger(e)&&e>0?e:void 0},p=()=>a.realpathSync(s(new URL("./cli.js",import.meta.url))),d=async()=>{await i(t.version,{selfRealPath:p(),parentPid:m(o.argv)})};d().catch(()=>{o.exitCode=0});
1
+ import{h as i}from"./chunk-3OMHS5RE.js";import{a as t}from"./chunk-6FDGBQGX.js";import"./chunk-6FU2TRU5.js";import"./chunk-CHETBZ6M.js";import a from"node:fs";import o from"node:process";import{fileURLToPath as s}from"node:url";var m=r=>{let n=r.indexOf("--parent-pid");if(n===-1)return;let e=Number(r[n+1]);return Number.isInteger(e)&&e>0?e:void 0},p=()=>a.realpathSync(s(new URL("./cli.js",import.meta.url))),d=async()=>{await i(t.version,{selfRealPath:p(),parentPid:m(o.argv)})};d().catch(()=>{o.exitCode=0});
2
2
  //# sourceMappingURL=update-check.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "infra-kit",
3
3
  "type": "module",
4
- "version": "0.1.132",
4
+ "version": "0.1.133",
5
5
  "files": [
6
6
  "dist"
7
7
  ],
@@ -1,2 +0,0 @@
1
- import{render as be}from"ink";import ge from"node:process";import Y from"node:process";var Z="\x1B[3J",j=t=>typeof t=="string"?t.split(Z).join(""):t,ee=t=>new Proxy(t,{get(e,o,s){if(o==="write")return(i,...c)=>e.write(j(i),...c);let l=Reflect.get(e,o,s);return typeof l=="function"?l.bind(e):l}}),G=()=>ee(Y.stderr);import{Box as R,Text as v,useApp as re,useInput as ne}from"ink";import{useEffect as oe,useState as $}from"react";import{useMemo as te,useState as K}from"react";var k=(t,e)=>{let[o,s]=K(""),[l,i]=K(0),c=te(()=>{let d=o.trim().toLowerCase();return d?t.filter(f=>e(f).toLowerCase().includes(d)):t},[t,o,e]),h=Math.min(l,Math.max(0,c.length-1));return{filtered:c,activeIndex:h,query:o,handleNavigation:(d,f)=>{let u=c.length;return f.upArrow?(u>0&&i((h-1+u)%u),!0):f.downArrow?(u>0&&i((h+1)%u),!0):f.backspace||f.delete?(s(o.slice(0,-1)),i(0),!0):d&&!f.ctrl&&!f.meta?(s(o+d),i(0),!0):!1}}};import{jsx as C,jsxs as D}from"react/jsx-runtime";var _=t=>{let{items:e,onSubmit:o,onCancel:s,required:l=!0,allowSelectAll:i=!0}=t,c={hint:"Select branches \u2014 type to filter, \u2191\u2193 to move, Space to toggle, Ctrl-A all, Enter to submit, Esc to cancel",prompt:"\u276F ",empty:"No matching branches",requiredHint:"select at least one"},h=e.reduce((r,n)=>Math.max(r,n.label.length),0),{exit:x}=re(),{filtered:d,activeIndex:f,query:u,handleNavigation:T}=k(e,ie),[p,m]=$(()=>new Set),[g,B]=$(!1),[w,b]=$(!1);oe(()=>{w&&x()},[w,x]);let E=r=>{m(n=>{let a=new Set(n);return a.has(r)?a.delete(r):a.add(r),a})},M=()=>{m(r=>e.every(a=>r.has(a.value))?new Set:new Set(e.map(a=>a.value)))},q=()=>{if(l&&p.size===0){B(!0);return}o([...p]),b(!0)};return ne((r,n)=>{if(!w){if(n.escape||n.ctrl&&r==="c"){s(),b(!0);return}if(n.ctrl&&r==="a"){i&&M();return}if(r===" "){let a=d[f];a&&E(a.value);return}if(n.return){q();return}T(r,n)}}),w?null:D(R,{flexDirection:"column",children:[C(v,{dimColor:!0,children:c.hint}),D(R,{children:[C(v,{color:"cyan",children:c.prompt}),C(v,{children:u})]}),C(R,{flexDirection:"column",marginTop:1,children:d.length===0?C(v,{dimColor:!0,children:c.empty}):d.map((r,n)=>{let a=n===f,W=p.has(r.value);return D(v,{color:a?"green":void 0,children:[a?"\u203A ":" ",W?"[x] ":"[ ] ",r.label.padEnd(h)," ",C(v,{dimColor:!0,children:r.description??""})]},r.value)})}),g?C(v,{dimColor:!0,children:c.requiredHint}):null]})};function ie(t){return`${t.label} ${t.description??""}`}import{Box as F,Text as y,useApp as le,useInput as ce}from"ink";import{useEffect as se,useState as ae}from"react";import{jsx as I,jsxs as H}from"react/jsx-runtime";var z=t=>{let{items:e,onSelect:o,onCancel:s}=t,l={hint:"Select a branch \u2014 type to filter, \u2191\u2193 to move, Enter to select, Esc to cancel",prompt:"\u276F ",empty:"No matching branches"},i=e.reduce((p,m)=>Math.max(p,m.label.length),0),{exit:c}=le(),{filtered:h,activeIndex:x,query:d,handleNavigation:f}=k(e,me),[u,T]=ae(!1);return se(()=>{u&&c()},[u,c]),ce((p,m)=>{if(!u){if(m.escape||m.ctrl&&p==="c"){s(),T(!0);return}if(m.return){let g=h[x];g&&(o(g.value),T(!0));return}f(p,m)}}),u?null:H(F,{flexDirection:"column",children:[I(y,{dimColor:!0,children:l.hint}),H(F,{children:[I(y,{color:"cyan",children:l.prompt}),I(y,{children:d})]}),I(F,{flexDirection:"column",marginTop:1,children:h.length===0?I(y,{dimColor:!0,children:l.empty}):h.map((p,m)=>{let g=m===x;return H(y,{color:ue(p.type,g),children:[g?"\u203A ":" ",p.label.padEnd(i)," ",I(y,{dimColor:!0,children:p.description??""})]},p.value)})})]})};function ue(t,e){return t==="hotfix"?"red":e?"green":void 0}function me(t){return`${t.label} ${t.description??""}`}import{Box as N,Text as S,useApp as fe,useInput as pe,useWindowSize as he}from"ink";import{useEffect as V,useMemo as xe,useState as L}from"react";var de=(t,e,o)=>{let s=0,l="";for(let i=e;i<o;i+=1){let c=t[i];if(!c)break;let h=c.group!==l;s+=1+(h?1:0)+(h&&i>e?1:0),l=c.group}return s},Q=(t,e,o)=>{let s=e;for(;s<t.length&&de(t,e,s+1)<=o;)s+=1;return Math.max(s,e+1)},U=(t,e,o,s)=>{let l=Math.max(1,s-6),i=Math.min(e,o);for(;o>=Q(t,i,l)&&i<o;)i+=1;let c=Q(t,i,l);return{visible:t.slice(i,c),start:i,hiddenBefore:i,hiddenAfter:Math.max(0,t.length-c)}};import{jsx as P,jsxs as A}from"react/jsx-runtime";var X=t=>{let{items:e,onSelect:o,onCancel:s}=t,l={hint:"Select a command \u2014 type to filter, \u2191\u2193 to move, Enter to run, Esc to cancel",prompt:"\u276F ",empty:"No matching commands",more:"\u2026"},i=24,{exit:c}=fe(),{rows:h}=he(),[x,d]=L(""),[f,u]=L(0),[T,p]=L(0),[m,g]=L(!1);V(()=>{m&&c()},[m,c]);let B=xe(()=>{let r=x.trim().toLowerCase();return r?e.filter(n=>`${n.name} ${n.description}`.toLowerCase().includes(r)):e},[e,x]),w=Math.min(f,Math.max(0,B.length-1)),b=U(B,T,w,h);V(()=>{b.start!==T&&p(b.start)},[b.start,T]);let E=()=>{s(),g(!0)},M=r=>{let n=B.length;n>0&&u((w+r+n)%n)};if(pe((r,n)=>{if(!m){if(n.ctrl&&r==="c"){E();return}if(n.escape&&x){d(""),u(0);return}if(n.escape){E();return}if(n.return){let a=B[w];a&&(o(a.name),g(!0));return}if(n.upArrow){M(-1);return}if(n.downArrow){M(1);return}if(n.backspace||n.delete){d(x.slice(0,-1)),u(0);return}r&&!n.ctrl&&!n.meta&&(d(x+r),u(0))}}),m)return null;let q="";return A(N,{flexDirection:"column",children:[P(S,{dimColor:!0,children:l.hint}),A(N,{children:[P(S,{color:"cyan",children:l.prompt}),P(S,{children:x})]}),A(N,{flexDirection:"column",marginTop:1,children:[B.length===0?P(S,{dimColor:!0,children:l.empty}):null,b.hiddenBefore>0?P(S,{dimColor:!0,children:`${l.more} ${b.hiddenBefore} above`}):null,b.visible.map((r,n)=>{let a=b.start+n===w,W=r.group!==q;return q=r.group,A(N,{flexDirection:"column",children:[W&&n>0?P(S,{children:" "}):null,W?P(S,{color:"yellow",children:`\u2014 ${r.group} \u2014`}):null,A(S,{color:a?"green":void 0,children:[a?"\u203A ":" ",r.name.padEnd(i)," ",P(S,{dimColor:!0,children:r.description})]})]},r.name)}),b.hiddenAfter>0?P(S,{dimColor:!0,children:`${l.more} ${b.hiddenAfter} below`}):null]})]})};import{jsx as J}from"react/jsx-runtime";var O=async t=>{let{waitUntilExit:e}=be(t,{stdout:G()});try{await e()}catch{}finally{ge.stdin.ref()}},_e=async t=>{let e=null;return await O(J(X,{items:t,onSelect:o=>{e=o},onCancel:()=>{e=null}})),e},ze=async t=>{let e=null;return await O(J(z,{items:t,onSelect:o=>{e=o},onCancel:()=>{e=null}})),e},Qe=async(t,e)=>{let o=null;return await O(J(_,{items:t,required:e?.required,allowSelectAll:e?.allowSelectAll,onSubmit:s=>{o=s},onCancel:()=>{o=null}})),o};export{Qe as runBranchMultiPicker,ze as runBranchPicker,_e as runCommandPalette};
2
- //# sourceMappingURL=boot-SG7525LE.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/tui/boot.tsx", "../src/tui/safe-stderr.ts", "../src/tui/screens/branch-multi-picker.tsx", "../src/tui/hooks/use-list-filter.ts", "../src/tui/screens/branch-picker.tsx", "../src/tui/screens/command-palette.tsx", "../src/tui/screens/palette-window.ts"],
4
- "sourcesContent": ["import { render } from 'ink'\nimport process from 'node:process'\nimport type { ReactElement } from 'react'\n\nimport type { BranchPickerItem } from 'src/lib/prompts/types'\n\nimport { safeStderr } from './safe-stderr'\nimport { BranchMultiPicker } from './screens/branch-multi-picker'\nimport { BranchPicker } from './screens/branch-picker'\nimport { CommandPalette } from './screens/command-palette'\nimport type { PaletteItem } from './types'\n\n/**\n * Entry point for the Ink TUI. This module (and everything under `src/tui/`) is\n * the ONLY place allowed to import `ink`/`react`; it is reached exclusively via a\n * dynamic `await import('src/tui/boot')` from the TTY branch of entry/cli.ts, so\n * React never loads on the MCP / `--json` / non-TTY paths. Build splitting keeps\n * it in a separate lazy chunk (see scripts/build.js).\n */\n\n/**\n * Render an Ink element to stderr and resolve once it tears down. Frames are\n * written to stderr so stdout stays clean for the command that runs afterwards\n * (mirrors the previous Inquirer menu).\n *\n * Ink `unref()`s process.stdin when it tears down raw mode on exit. That ref is\n * process-wide and never restored, so the next consumer of stdin \u2014 the Inquirer\n * prompt the selected command opens (e.g. the worktrees-remove checkbox), or a\n * SECOND Ink render in the same process (the bare-menu palette \u2192 picker hop) \u2014\n * reads from an unref'd handle that no longer keeps the event loop alive. Once\n * the command's git/gh subprocesses settle, the loop drains mid-prompt, Node\n * flags entry/cli.ts's top-level await as unsettled, and the process exits with\n * code 13 (the prompt dies on arrival). Re-ref stdin on BOTH the resolve and the\n * reject path so it holds the loop while the next prompt is reading; an idle\n * ref'd stdin does not block exit, so non-interactive follow-up commands still\n * terminate normally. Every render must go through here so the re-ref is never\n * skipped.\n */\nconst renderToStderr = async (element: ReactElement): Promise<void> => {\n const { waitUntilExit } = render(element, {\n stdout: safeStderr(),\n })\n\n try {\n await waitUntilExit()\n } catch {\n // Swallow a teardown rejection: the caller reports cancellation via its own\n // captured sentinel (which stays at its initial value), not via a throw.\n } finally {\n process.stdin.ref()\n }\n}\n\n/**\n * Render the command palette and resolve to the chosen command name, or `null`\n * if the user cancels.\n */\nexport const runCommandPalette = async (items: PaletteItem[]): Promise<string | null> => {\n let selected: string | null = null\n\n await renderToStderr(\n <CommandPalette\n items={items}\n onSelect={(name) => {\n selected = name\n }}\n onCancel={() => {\n selected = null\n }}\n />,\n )\n\n return selected\n}\n\n/**\n * Render the searchable single-select branch picker and resolve to the chosen\n * value, or `null` if the user cancels.\n */\nexport const runBranchPicker = async (items: BranchPickerItem[]): Promise<string | null> => {\n let selected: string | null = null\n\n await renderToStderr(\n <BranchPicker\n items={items}\n onSelect={(value) => {\n selected = value\n }}\n onCancel={() => {\n selected = null\n }}\n />,\n )\n\n return selected\n}\n\n/**\n * Render the searchable multi-select branch picker and resolve to the chosen\n * values, or `null` if the user cancels. The captured variable starts as `null`\n * and is only assigned an array in `onSubmit`, so a cancel is distinguishable\n * from an empty submit.\n */\nexport const runBranchMultiPicker = async (\n items: BranchPickerItem[],\n opts?: { required?: boolean; allowSelectAll?: boolean },\n): Promise<string[] | null> => {\n let selected: string[] | null = null\n\n await renderToStderr(\n <BranchMultiPicker\n items={items}\n required={opts?.required}\n allowSelectAll={opts?.allowSelectAll}\n onSubmit={(values) => {\n selected = values\n }}\n onCancel={() => {\n selected = null\n }}\n />,\n )\n\n return selected\n}\n", "import process from 'node:process'\n\n/**\n * The stream Ink renders into \u2014 stderr, with the one escape that can destroy the user's terminal\n * history filtered out.\n *\n * Ink erases the whole terminal whenever a frame overflows the viewport: `renderInteractiveFrame`\n * writes `ansiEscapes.clearTerminal`, which on POSIX is `ESC[2J ESC[3J ESC[H`. The middle one is\n * DECSED 3 \u2014 \"erase saved lines\" \u2014 and it wipes the SCROLLBACK BUFFER, not just the visible region.\n * The session shell puts every command's real output in that scrollback, so a palette frame taller\n * than the window (25 commands vs an 80x24 terminal or a tmux split) would erase the very transcript\n * the shell exists to produce, plus whatever the user had on screen before launching `infra-kit`.\n *\n * Bounding the frame height is NOT a sufficient guard: Ink re-renders on SIGWINCH using the PREVIOUS\n * frame's height against the NEW viewport, so shrinking the window trips the overflow check\n * retroactively, and no window fits a terminal of a handful of rows. So the guard is applied to the\n * bytes instead of the geometry \u2014 `ESC[3J` never reaches the terminal, whatever Ink decides to draw.\n * What survives (`ESC[2J ESC[H`: erase viewport, home cursor) is exactly Ink's intent.\n */\n\n/** DECSED 3 \u2014 \"erase saved lines\". The only escape that can destroy scrollback. */\nconst ERASE_SCROLLBACK = '\\u001B[3J'\n\n/** Drop every scrollback-erasing escape from a frame (split/join: a regex here holds a control char). */\nconst scrub = (chunk: unknown): unknown => {\n return typeof chunk === 'string' ? chunk.split(ERASE_SCROLLBACK).join('') : chunk\n}\n\n/**\n * Wrap a stream so it can never erase scrollback. Everything except `write` is forwarded to the real\n * stream \u2014 Ink reads `columns`/`rows`/`isTTY` afresh on every frame and subscribes to `'resize'`, so\n * this must be a live view of the stream, not a snapshot of it.\n *\n * @example\n * const written: string[] = []\n * const safe = createSafeStream({ write: (s) => written.push(s) } as unknown as NodeJS.WriteStream)\n * safe.write('\\u001B[2J\\u001B[3J\\u001B[H')\n * written[0] // => '\\u001B[2J\\u001B[H' (the scrollback-erasing escape is gone)\n */\nexport const createSafeStream = (stream: NodeJS.WriteStream): NodeJS.WriteStream => {\n return new Proxy(stream, {\n get(target, property, receiver) {\n if (property === 'write') {\n return (chunk: unknown, ...rest: unknown[]): boolean => {\n return (target.write as (...args: unknown[]) => boolean)(scrub(chunk), ...rest)\n }\n }\n\n const value = Reflect.get(target, property, receiver) as unknown\n\n // Methods must keep their original `this` (an EventEmitter bound to the proxy would register\n // listeners on the wrong object), so bind them back to the real stream.\n return typeof value === 'function' ? value.bind(target) : value\n },\n })\n}\n\n/** Ink's render target for the whole CLI: stderr, minus the scrollback-erasing escape. */\nexport const safeStderr = (): NodeJS.WriteStream => {\n return createSafeStream(process.stderr as unknown as NodeJS.WriteStream)\n}\n", "import { Box, Text, useApp, useInput } from 'ink'\nimport { useEffect, useState } from 'react'\n\nimport type { BranchPickerItem } from '../../lib/prompts/types'\nimport { useListFilter } from '../hooks/use-list-filter'\n\ninterface BranchMultiPickerProps {\n items: BranchPickerItem[]\n /** Called with the selected item values; the app then exits. */\n onSubmit: (values: string[]) => void\n /** Called when the user cancels (Esc / Ctrl-C); the app then exits. */\n onCancel: () => void\n /** When true (default), Enter with an empty selection is ignored. */\n required?: boolean\n /** When true (default), Ctrl-A toggles select-all across all items. */\n allowSelectAll?: boolean\n}\n\n/**\n * Searchable multi-select branch picker rendered with Ink. Same filter/move/cancel\n * behaviour as `BranchPicker`, plus a selection `Set` keyed by item *value* so the\n * selection survives filter changes. Space toggles the active row; Ctrl-A toggles\n * select-all across all items; Enter submits the selected values.\n */\nexport const BranchMultiPicker = (props: BranchMultiPickerProps) => {\n const { items, onSubmit, onCancel, required = true, allowSelectAll = true } = props\n\n const T = {\n hint: 'Select branches \u2014 type to filter, \u2191\u2193 to move, Space to toggle, Ctrl-A all, Enter to submit, Esc to cancel',\n prompt: '\u276F ',\n empty: 'No matching branches',\n requiredHint: 'select at least one',\n }\n const labelWidth = items.reduce((max, item) => {\n return Math.max(max, item.label.length)\n }, 0)\n\n const { exit } = useApp()\n const { filtered, activeIndex, query, handleNavigation } = useListFilter(items, filterText)\n // Selection keyed by value so it persists across filter changes.\n const [selected, setSelected] = useState<Set<string>>(() => {\n return new Set()\n })\n const [showRequiredHint, setShowRequiredHint] = useState(false)\n // When set, the component renders `null` for one frame so Ink erases the list\n // before unmount commits its final frame.\n const [submitted, setSubmitted] = useState(false)\n\n // Exit only after the empty frame has committed, so the list is wiped first.\n useEffect(() => {\n if (submitted) {\n exit()\n }\n }, [submitted, exit])\n\n const toggleValue = (value: string) => {\n setSelected((prev) => {\n const next = new Set(prev)\n\n if (next.has(value)) {\n next.delete(value)\n } else {\n next.add(value)\n }\n\n return next\n })\n }\n\n const toggleSelectAll = () => {\n setSelected((prev) => {\n // Clear only when every item (all, not just filtered) is already selected.\n const allSelected = items.every((item) => {\n return prev.has(item.value)\n })\n\n if (allSelected) {\n return new Set()\n }\n\n return new Set(\n items.map((item) => {\n return item.value\n }),\n )\n })\n }\n\n const submit = () => {\n if (required && selected.size === 0) {\n setShowRequiredHint(true)\n\n return\n }\n\n onSubmit([...selected])\n setSubmitted(true)\n }\n\n useInput((input, key) => {\n if (submitted) {\n return\n }\n\n if (key.escape || (key.ctrl && input === 'c')) {\n onCancel()\n setSubmitted(true)\n\n return\n }\n\n if (key.ctrl && input === 'a') {\n if (allowSelectAll) {\n toggleSelectAll()\n }\n\n return\n }\n\n if (input === ' ') {\n const active = filtered[activeIndex]\n\n if (active) {\n toggleValue(active.value)\n }\n\n return\n }\n\n if (key.return) {\n submit()\n\n return\n }\n\n handleNavigation(input, key)\n })\n\n // Final frame: render nothing so Ink erases the list on its way out.\n if (submitted) {\n return null\n }\n\n return (\n <Box flexDirection=\"column\">\n <Text dimColor>{T.hint}</Text>\n <Box>\n <Text color=\"cyan\">{T.prompt}</Text>\n <Text>{query}</Text>\n </Box>\n <Box flexDirection=\"column\" marginTop={1}>\n {filtered.length === 0 ? (\n <Text dimColor>{T.empty}</Text>\n ) : (\n filtered.map((item, position) => {\n const isActive = position === activeIndex\n const isChecked = selected.has(item.value)\n\n return (\n <Text color={isActive ? 'green' : undefined} key={item.value}>\n {isActive ? '\u203A ' : ' '}\n {isChecked ? '[x] ' : '[ ] '}\n {item.label.padEnd(labelWidth)} <Text dimColor>{item.description ?? ''}</Text>\n </Text>\n )\n })\n )}\n </Box>\n {showRequiredHint ? <Text dimColor>{T.requiredHint}</Text> : null}\n </Box>\n )\n}\n\nfunction filterText(item: BranchPickerItem): string {\n return `${item.label} ${item.description ?? ''}`\n}\n", "import type { Key } from 'ink'\nimport { useMemo, useState } from 'react'\n\ninterface ListFilter<T> {\n /** Items matching the current query (all items when the query is empty). */\n filtered: T[]\n /** Active row index, always clamped into `filtered`'s bounds. */\n activeIndex: number\n /** Current filter query. */\n query: string\n /**\n * Handle the shared navigation/filter keys (\u2191\u2193 wrap-around, backspace/delete,\n * printable chars). Returns true when the key was consumed so the caller can\n * `return` early; false leaves the key for the component's own handlers.\n */\n handleNavigation: (input: string, key: Key) => boolean\n}\n\n/**\n * Shared list-filter state for the branch pickers: substring filtering,\n * wrap-around \u2191\u2193 movement, and query editing. Extracted so each `useInput`\n * handler stays under the sonarjs cognitive-complexity \u226415 gate.\n */\nexport const useListFilter = <T>(items: T[], toText: (item: T) => string): ListFilter<T> => {\n const [query, setQuery] = useState('')\n const [index, setIndex] = useState(0)\n\n const filtered = useMemo(() => {\n const q = query.trim().toLowerCase()\n\n if (!q) {\n return items\n }\n\n return items.filter((item) => {\n return toText(item).toLowerCase().includes(q)\n })\n }, [items, query, toText])\n\n const activeIndex = Math.min(index, Math.max(0, filtered.length - 1))\n\n const handleNavigation = (input: string, key: Key): boolean => {\n const n = filtered.length\n\n if (key.upArrow) {\n // `+ n` sidesteps JS negative modulo (e.g. -1 % 5 === -1, not 4).\n if (n > 0) {\n setIndex((activeIndex - 1 + n) % n)\n }\n\n return true\n }\n\n if (key.downArrow) {\n if (n > 0) {\n setIndex((activeIndex + 1) % n)\n }\n\n return true\n }\n\n if (key.backspace || key.delete) {\n setQuery(query.slice(0, -1))\n setIndex(0)\n\n return true\n }\n\n // Printable character: append to the filter and reset to the first row.\n if (input && !key.ctrl && !key.meta) {\n setQuery(query + input)\n setIndex(0)\n\n return true\n }\n\n return false\n }\n\n return { filtered, activeIndex, query, handleNavigation }\n}\n", "import { Box, Text, useApp, useInput } from 'ink'\nimport { useEffect, useState } from 'react'\n\nimport type { BranchPickerItem } from '../../lib/prompts/types'\nimport { useListFilter } from '../hooks/use-list-filter'\n\ninterface BranchPickerProps {\n items: BranchPickerItem[]\n /** Called with the chosen item's value; the app then exits. */\n onSelect: (value: string) => void\n /** Called when the user cancels (Esc / Ctrl-C); the app then exits. */\n onCancel: () => void\n}\n\n/**\n * Searchable single-select branch picker rendered with Ink. Mirrors\n * `CommandPalette` minus the group headers: type-to-filter over label +\n * description, \u2191\u2193 wrap-around, Enter selects the active item's *value*.\n */\nexport const BranchPicker = (props: BranchPickerProps) => {\n const { items, onSelect, onCancel } = props\n\n const T = {\n hint: 'Select a branch \u2014 type to filter, \u2191\u2193 to move, Enter to select, Esc to cancel',\n prompt: '\u276F ',\n empty: 'No matching branches',\n }\n const labelWidth = items.reduce((max, item) => {\n return Math.max(max, item.label.length)\n }, 0)\n\n const { exit } = useApp()\n const { filtered, activeIndex, query, handleNavigation } = useListFilter(items, filterText)\n // When set, the component renders `null` for one frame so Ink erases the list\n // before unmount commits its final frame.\n const [submitted, setSubmitted] = useState(false)\n\n // Exit only after the empty frame has committed, so the list is wiped first.\n useEffect(() => {\n if (submitted) {\n exit()\n }\n }, [submitted, exit])\n\n useInput((input, key) => {\n if (submitted) {\n return\n }\n\n if (key.escape || (key.ctrl && input === 'c')) {\n onCancel()\n setSubmitted(true)\n\n return\n }\n\n if (key.return) {\n const selected = filtered[activeIndex]\n\n if (selected) {\n onSelect(selected.value)\n setSubmitted(true)\n }\n\n return\n }\n\n handleNavigation(input, key)\n })\n\n // Final frame: render nothing so Ink erases the list on its way out.\n if (submitted) {\n return null\n }\n\n return (\n <Box flexDirection=\"column\">\n <Text dimColor>{T.hint}</Text>\n <Box>\n <Text color=\"cyan\">{T.prompt}</Text>\n <Text>{query}</Text>\n </Box>\n <Box flexDirection=\"column\" marginTop={1}>\n {filtered.length === 0 ? (\n <Text dimColor>{T.empty}</Text>\n ) : (\n filtered.map((item, position) => {\n const isActive = position === activeIndex\n\n return (\n <Text color={labelColor(item.type, isActive)} key={item.value}>\n {isActive ? '\u203A ' : ' '}\n {item.label.padEnd(labelWidth)} <Text dimColor>{item.description ?? ''}</Text>\n </Text>\n )\n })\n )}\n </Box>\n </Box>\n )\n}\n\n/**\n * Colour for a row's label, derived from the release type. Kept a plain lookup so\n * an unknown/absent type simply falls through to the default (green when active).\n */\nfunction labelColor(type: string | undefined, isActive: boolean): string | undefined {\n if (type === 'hotfix') {\n return 'red'\n }\n\n return isActive ? 'green' : undefined\n}\n\nfunction filterText(item: BranchPickerItem): string {\n return `${item.label} ${item.description ?? ''}`\n}\n", "import { Box, Text, useApp, useInput, useWindowSize } from 'ink'\nimport { useEffect, useMemo, useState } from 'react'\n\nimport type { PaletteItem } from '../types'\nimport { paletteWindow } from './palette-window'\n\ninterface CommandPaletteProps {\n items: PaletteItem[]\n /** Called with the chosen command name; the app then exits. */\n onSelect: (name: string) => void\n /** Called when the user cancels (Esc / Ctrl-C); the app then exits. */\n onCancel: () => void\n}\n\n/**\n * Fuzzy-ish command picker rendered with Ink. Pure presentation: it receives a\n * flat list of commands (already grouped/ordered by the catalog) and returns the\n * selected name. It never executes anything \u2014 the caller runs the command via\n * the existing Commander path.\n */\nexport const CommandPalette = (props: CommandPaletteProps) => {\n const { items, onSelect, onCancel } = props\n\n const T = {\n hint: 'Select a command \u2014 type to filter, \u2191\u2193 to move, Enter to run, Esc to cancel',\n prompt: '\u276F ',\n empty: 'No matching commands',\n more: '\u2026',\n }\n const nameWidth = 24\n\n const { exit } = useApp()\n const { rows } = useWindowSize()\n const [query, setQuery] = useState('')\n const [index, setIndex] = useState(0)\n // First command in the visible window. Kept in state so the list scrolls by the minimum needed\n // instead of jumping every time the cursor moves.\n const [windowStart, setWindowStart] = useState(0)\n // When set, the component renders `null` for one frame so Ink erases the list\n // before unmount commits its final frame (Ink's `log.done()` would otherwise\n // freeze the last drawn frame \u2014 the command list \u2014 into the scrollback).\n const [submitted, setSubmitted] = useState(false)\n\n // Exit only after the empty frame has committed, so the list is wiped first.\n useEffect(() => {\n if (submitted) {\n exit()\n }\n }, [submitted, exit])\n\n const filtered = useMemo(() => {\n const q = query.trim().toLowerCase()\n\n if (!q) {\n return items\n }\n\n return items.filter((item) => {\n return `${item.name} ${item.description}`.toLowerCase().includes(q)\n })\n }, [items, query])\n\n const activeIndex = Math.min(index, Math.max(0, filtered.length - 1))\n\n // The slice that fits this terminal. A 25-command list is 33 rows: taller than an 80x24 window or\n // any split pane, which would make the frame unreadable (and make Ink clear the screen on every\n // keystroke). `safe-stderr` is what makes an overflowing frame harmless; this is what avoids one.\n const view = paletteWindow(filtered, windowStart, activeIndex, rows)\n\n // Commit the scroll position once the frame that used it has rendered.\n useEffect(() => {\n if (view.start !== windowStart) {\n setWindowStart(view.start)\n }\n }, [view.start, windowStart])\n\n // Back out with nothing picked (Ctrl-C, or Esc on an empty filter).\n const quit = () => {\n onCancel()\n setSubmitted(true)\n }\n\n // Move the active row by `delta`, wrapping. The `+ n` sidesteps JS negative modulo\n // (e.g. -1 % 5 === -1, not 4). No-op on an empty list.\n const step = (delta: number) => {\n const n = filtered.length\n\n if (n > 0) {\n setIndex((activeIndex + delta + n) % n)\n }\n }\n\n useInput((input, key) => {\n if (submitted) {\n return\n }\n\n // Ctrl-C always quits; Esc clears a non-empty filter first, and only quits on an empty filter \u2014\n // the standard REPL/palette split, so the session shell keeps running while the user narrows.\n if (key.ctrl && input === 'c') {\n quit()\n\n return\n }\n\n if (key.escape && query) {\n setQuery('')\n setIndex(0)\n\n return\n }\n\n if (key.escape) {\n quit()\n\n return\n }\n\n if (key.return) {\n const selected = filtered[activeIndex]\n\n if (selected) {\n onSelect(selected.name)\n setSubmitted(true)\n }\n\n return\n }\n\n if (key.upArrow) {\n step(-1)\n\n return\n }\n\n if (key.downArrow) {\n step(1)\n\n return\n }\n\n if (key.backspace || key.delete) {\n setQuery(query.slice(0, -1))\n setIndex(0)\n\n return\n }\n\n // Printable character: append to the filter.\n if (input && !key.ctrl && !key.meta) {\n setQuery(query + input)\n setIndex(0)\n }\n })\n\n // Final frame: render nothing so Ink erases the list on its way out.\n if (submitted) {\n return null\n }\n\n let lastGroup = ''\n\n return (\n <Box flexDirection=\"column\">\n <Text dimColor>{T.hint}</Text>\n <Box>\n <Text color=\"cyan\">{T.prompt}</Text>\n <Text>{query}</Text>\n </Box>\n <Box flexDirection=\"column\" marginTop={1}>\n {filtered.length === 0 ? <Text dimColor>{T.empty}</Text> : null}\n {view.hiddenBefore > 0 ? <Text dimColor>{`${T.more} ${view.hiddenBefore} above`}</Text> : null}\n {view.visible.map((item, position) => {\n const isActive = view.start + position === activeIndex\n const showGroup = item.group !== lastGroup\n\n lastGroup = item.group\n\n return (\n <Box flexDirection=\"column\" key={item.name}>\n {/* Blank line between groups (not before the first row of the window). */}\n {showGroup && position > 0 ? <Text> </Text> : null}\n {showGroup ? <Text color=\"yellow\">{`\u2014 ${item.group} \u2014`}</Text> : null}\n <Text color={isActive ? 'green' : undefined}>\n {isActive ? '\u203A ' : ' '}\n {item.name.padEnd(nameWidth)} <Text dimColor>{item.description}</Text>\n </Text>\n </Box>\n )\n })}\n {view.hiddenAfter > 0 ? <Text dimColor>{`${T.more} ${view.hiddenAfter} below`}</Text> : null}\n </Box>\n </Box>\n )\n}\n", "/**\n * Pure viewport arithmetic for the command palette: which slice of the (already filtered) command\n * list fits the terminal, given that a group header and a separator blank line each cost a row too.\n *\n * This is a READABILITY guard, not a safety one. A 25-command list is 33 rows and simply does not fit\n * an 80x24 terminal or a tmux split \u2014 and an Ink frame taller than the viewport also makes Ink erase\n * the screen on every keystroke. The guard that makes scrollback destruction *impossible* is the\n * `ESC[3J` filter in `src/tui/safe-stderr.ts`; windowing is what keeps the palette usable and the\n * frame stable. (It cannot be the safety guard: Ink re-renders on resize against a stale frame height,\n * and below a handful of rows no window is small enough.)\n */\nimport type { PaletteItem } from '../types'\n\n/** The slice to render, plus how many rows fell off each end (for the `\u2026 n more` affordances). */\nexport interface PaletteWindow {\n visible: PaletteItem[]\n /** Index the window starts at \u2014 the component stores it so the list does not jump between frames. */\n start: number\n hiddenBefore: number\n hiddenAfter: number\n}\n\n/**\n * Rows the palette spends on anything that is not a command row: the hint, the prompt, the list's\n * `marginTop`, and the two `\u2026 n more` affordances. One extra row of headroom keeps the frame strictly\n * SHORTER than the viewport \u2014 Ink treats \"exactly as tall as the terminal\" as fullscreen and clears.\n */\nconst CHROME_ROWS = 6\n\n/**\n * How many rows rendering `items[start..end)` costs: one per command, plus one for each group header\n * and one for each blank line between groups (the first rendered group gets a header but no blank \u2014\n * it mirrors the `showGroup && position > 0` rule in the component).\n */\nconst heightOf = (items: PaletteItem[], start: number, end: number): number => {\n let height = 0\n let previousGroup = ''\n\n for (let index = start; index < end; index += 1) {\n const item = items[index]\n\n if (!item) break\n\n const startsGroup = item.group !== previousGroup\n\n height += 1 + (startsGroup ? 1 : 0) + (startsGroup && index > start ? 1 : 0)\n previousGroup = item.group\n }\n\n return height\n}\n\n/** The last index (exclusive) that still fits `budget` rows when rendering from `start`. */\nconst endThatFits = (items: PaletteItem[], start: number, budget: number): number => {\n let end = start\n\n while (end < items.length && heightOf(items, start, end + 1) <= budget) {\n end += 1\n }\n\n // Always show at least the active row, even on a terminal too short to hold it comfortably.\n return Math.max(end, start + 1)\n}\n\n/**\n * The window of commands to render: the largest slice starting at or after `start` that fits the\n * terminal while still containing `activeIndex`. Scrolls by the minimum needed \u2014 the caller keeps\n * `start` in state, so the list only moves when the cursor would otherwise leave the window.\n *\n * @example\n * const window = paletteWindow(items, 0, 12, 24)\n * window.visible.length // => however many commands fit 18 rows\n * window.hiddenAfter // => the rest, surfaced as '\u2026 n more'\n */\nexport const paletteWindow = (\n items: PaletteItem[],\n start: number,\n activeIndex: number,\n rows: number,\n): PaletteWindow => {\n const budget = Math.max(1, rows - CHROME_ROWS)\n\n // Scroll up only if the cursor moved above the window; otherwise scroll down just far enough.\n let first = Math.min(start, activeIndex)\n\n while (activeIndex >= endThatFits(items, first, budget) && first < activeIndex) {\n first += 1\n }\n\n const end = endThatFits(items, first, budget)\n\n return {\n visible: items.slice(first, end),\n start: first,\n hiddenBefore: first,\n hiddenAfter: Math.max(0, items.length - end),\n }\n}\n"],
5
- "mappings": "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",
6
- "names": ["render", "process", "process", "ERASE_SCROLLBACK", "scrub", "chunk", "createSafeStream", "stream", "target", "property", "receiver", "rest", "value", "safeStderr", "Box", "Text", "useApp", "useInput", "useEffect", "useState", "useMemo", "useState", "useListFilter", "items", "toText", "query", "setQuery", "index", "setIndex", "filtered", "q", "item", "activeIndex", "input", "key", "n", "jsx", "jsxs", "BranchMultiPicker", "props", "items", "onSubmit", "onCancel", "required", "allowSelectAll", "T", "labelWidth", "max", "item", "exit", "useApp", "filtered", "activeIndex", "query", "handleNavigation", "useListFilter", "filterText", "selected", "setSelected", "useState", "showRequiredHint", "setShowRequiredHint", "submitted", "setSubmitted", "useEffect", "toggleValue", "value", "prev", "next", "toggleSelectAll", "submit", "useInput", "input", "key", "active", "Box", "Text", "position", "isActive", "isChecked", "Box", "Text", "useApp", "useInput", "useEffect", "useState", "jsx", "jsxs", "BranchPicker", "props", "items", "onSelect", "onCancel", "T", "labelWidth", "max", "item", "exit", "useApp", "filtered", "activeIndex", "query", "handleNavigation", "useListFilter", "filterText", "submitted", "setSubmitted", "useState", "useEffect", "useInput", "input", "key", "selected", "Box", "Text", "position", "isActive", "labelColor", "type", "Box", "Text", "useApp", "useInput", "useWindowSize", "useEffect", "useMemo", "useState", "heightOf", "items", "start", "end", "height", "previousGroup", "index", "item", "startsGroup", "endThatFits", "budget", "paletteWindow", "activeIndex", "rows", "first", "jsx", "jsxs", "CommandPalette", "props", "items", "onSelect", "onCancel", "T", "nameWidth", "exit", "useApp", "rows", "useWindowSize", "query", "setQuery", "useState", "index", "setIndex", "windowStart", "setWindowStart", "submitted", "setSubmitted", "useEffect", "filtered", "useMemo", "q", "item", "activeIndex", "view", "paletteWindow", "quit", "step", "delta", "useInput", "input", "key", "selected", "lastGroup", "Box", "Text", "position", "isActive", "showGroup", "jsx", "renderToStderr", "element", "waitUntilExit", "render", "safeStderr", "process", "runCommandPalette", "items", "selected", "CommandPalette", "name", "runBranchPicker", "BranchPicker", "value", "runBranchMultiPicker", "opts", "BranchMultiPicker", "values"]
7
- }
@@ -1,2 +0,0 @@
1
- import{a as v}from"./chunk-6FU2TRU5.js";import{m as f,q as P}from"./chunk-CHETBZ6M.js";import m from"node:path";var d="infra-kit",h=`${d}@latest`,S=(t,e,n)=>{let r=m.relative(n(m.resolve(t)),m.resolve(e));return r!==""&&!r.startsWith("..")&&!m.isAbsolute(r)},l=(t,e)=>m.resolve(t).split(m.sep).includes(e),g=(t,e,n,r)=>{let a=t[e];return a!=null&&a!==""&&S(a,n,r)},H=[{manager:"volta",test:({selfRealPath:t,env:e,realpath:n})=>g(e,"VOLTA_HOME",t,n)||l(t,".volta")||l(t,"volta"),updateCommand:["volta","install",d],canSelfSpawn:!0},{manager:"homebrew",test:({selfRealPath:t,env:e,realpath:n})=>l(t,"Cellar")||g(e,"HOMEBREW_PREFIX",t,n),updateCommand:["brew","upgrade",d],canSelfSpawn:!1},{manager:"pnpm",test:({selfRealPath:t,env:e,realpath:n})=>g(e,"PNPM_HOME",t,n)||l(t,"pnpm")&&l(t,"global"),updateCommand:["pnpm","add","-g",h],canSelfSpawn:!0},{manager:"bun",test:({selfRealPath:t,env:e,realpath:n})=>g(e,"BUN_INSTALL",t,n)||l(t,".bun"),updateCommand:["bun","add","-g",h],canSelfSpawn:!0},{manager:"yarn",test:({selfRealPath:t})=>l(t,".yarn")||l(t,"yarn")&&l(t,"global"),updateCommand:["yarn","global","add",h],canSelfSpawn:!0},{manager:"npm",test:({selfRealPath:t,env:e,realpath:n})=>g(e,"npm_config_prefix",t,n),updateCommand:["npm","install","-g",h],canSelfSpawn:!0}],x=["npm","install","-g",h],M=t=>{let{selfRealPath:e,env:n,realpath:r,lazyNpmRoot:a}=t,s=H.find(({test:i})=>i({selfRealPath:e,env:n,realpath:r}));if(s)return{manager:s.manager,updateCommand:s.updateCommand,canSelfSpawn:s.canSelfSpawn};let o=a?.();return o!=null&&o!==""&&S(o,e,r)?{manager:"npm",updateCommand:x,canSelfSpawn:!0}:{manager:"unknown",updateCommand:x,canSelfSpawn:!1}},J=(t,e,n)=>l(t,"node_modules")&&S(e,t,n);import{realpathSync as G}from"node:fs";import K from"node:path";var b=t=>{try{return G(t)}catch{return K.resolve(t)}};import{execFileSync as W}from"node:child_process";import $ from"node:process";var B=3e3,k=()=>{try{let e=W("npm",["root","-g"],{encoding:"utf-8",timeout:B,stdio:["ignore","pipe","ignore"],shell:$.platform==="win32"}).trim();return e===""?void 0:e}catch{return}};import{spawnSync as pe}from"node:child_process";import{homedir as me}from"node:os";import E from"node:process";import p from"node:fs";import I from"node:path";import X from"node:process";var Y="update-check.lock",Q=600*1e3,Z=()=>I.join(f(),Y),ee=(t,e)=>{try{e-p.statSync(t).mtimeMs>Q&&p.rmSync(t,{force:!0})}catch{}},N=(t={})=>{let e=t.nowMs??Date.now(),n=t.lockPath??Z();try{p.mkdirSync(I.dirname(n),{recursive:!0})}catch{return null}let r=()=>{try{return p.openSync(n,"wx",384)}catch{return null}},a=r();if(a===null&&(ee(n,e),a=r()),a===null)return null;try{p.writeFileSync(a,String(X.pid))}catch{}return()=>{try{p.closeSync(a)}catch{}try{p.rmSync(n,{force:!0})}catch{}}};var te="https://registry.npmjs.org",ne=2500,re=t=>{let e=t.npm_config_registry,n=e!=null&&e!==""?e:te;for(;n.endsWith("/");)n=n.slice(0,-1);return n},A=async(t,e=fetch)=>{let n=new AbortController,r=setTimeout(()=>{n.abort()},ne);try{let a=await e(`${re(t)}/${d}/latest`,{signal:n.signal,headers:{accept:"application/json"}});if(!a.ok)return null;let o=(await a.json())?.version;return typeof o=="string"&&o!==""?o:null}catch{return null}finally{clearTimeout(r)}};var ae=/^(\d+)\.(\d+)\.(\d+)$/,_=t=>{let[e=""]=t.trim().split("+"),[n="",...r]=e.split("-"),a=ae.exec(n);if(!a)return null;let[s,o,i]=[a[1],a[2],a[3]].map(Number);return{release:[s,o,i],prerelease:r.length>0?r.join("-").split("."):[]}},R=/^\d+$/,oe=(t,e)=>{let n=R.test(t),r=R.test(e);return n&&r?Number(t)-Number(e):n!==r?n?-1:1:t<e?-1:1},se=(t,e)=>{for(let n=0;n<Math.max(t.length,e.length);n+=1){let r=t[n],a=e[n];if(r===void 0)return-1;if(a===void 0)return 1;if(r!==a)return oe(r,a)}return 0},L=(t,e)=>{let n=_(t),r=_(e);if(!n||!r)return!1;for(let o=0;o<3;o+=1){let i=n.release[o],y=r.release[o];if(i!==y)return i>y}let a=n.prerelease.length===0,s=r.prerelease.length===0;return a!==s?a:se(n.prerelease,r.prerelease)>0};import U from"node:fs";import T from"node:path";var F="update-check.json",le=1440*60*1e3,ie=()=>T.join(f(),F),ce=t=>Array.isArray(t)&&t.every(e=>typeof e=="string"),ue=t=>{if(typeof t!="object"||t===null)return!1;let{lastCheckMs:e,latestVersion:n,updateCommand:r}=t;return typeof e=="number"&&Number.isFinite(e)&&(n===null||typeof n=="string")&&(r===null||ce(r))},He=(t=e=>U.readFileSync(e,"utf8"))=>{try{let e=JSON.parse(t(ie()));return ue(e)?e:null}catch{return null}},O=t=>{let e=f();U.mkdirSync(e,{recursive:!0}),P(T.join(e,F),JSON.stringify(t),384)},Je=(t,e)=>{if(!t)return!0;let n=e-t.lastCheckMs;return n>=le||n<0};var de=200,fe=300*1e3,ge=t=>{try{return E.kill(t,0),!0}catch{return!1}},he=t=>new Promise(e=>{setTimeout(e,t)}),ye=async(t,e)=>{let n=e.clock()+fe;for(;e.clock()<n;){if(!e.isProcessAlive(t))return!0;await e.sleep(de)}return!e.isProcessAlive(t)},tt=async(t,e)=>{let r=(e.acquireLock??N)();if(!r)return"already-running";try{return await Ce(t,e)}finally{r()}},Ce=async(t,e)=>{let n=e.env??E.env,r=e.nowMs??Date.now(),a=e.fetchLatest??A,s=e.writeCache??O,o=e.isProcessAlive??ge,i=e.sleep??he,y=e.clock??Date.now,D=e.lazyNpmRoot??k,V=e.spawnSync??pe,c=await a(n),u=(q,z)=>(s({lastCheckMs:r,...z}),q);if(c===null)return u("fetch-failed",{latestVersion:null,updateCommand:null});if(!L(c,t))return u("up-to-date",{latestVersion:c,updateCommand:null});let{canSelfSpawn:j,updateCommand:C}=M({selfRealPath:e.selfRealPath,env:n,realpath:b,lazyNpmRoot:D});if(!j)return u("cannot-self-spawn",{latestVersion:c,updateCommand:C});if(e.parentPid==null)return u("parent-unknown",{latestVersion:c,updateCommand:null});if(!await ye(e.parentPid,{isProcessAlive:o,sleep:i,clock:y}))return u("parent-still-running",{latestVersion:c,updateCommand:null});let w=V(C[0],C.slice(1),{stdio:"ignore",shell:E.platform==="win32",cwd:me(),env:v(n),windowsHide:!0});return w.error||w.signal||w.status!==0?u("install-failed",{latestVersion:c,updateCommand:C}):u("installed",{latestVersion:null,updateCommand:null})};export{M as a,J as b,k as c,b as d,L as e,He as f,Je as g,tt as h};
2
- //# sourceMappingURL=chunk-IGTHMZPR.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/lib/install-manager/install-manager.ts", "../src/lib/install-manager/safe-realpath.ts", "../src/lib/install-manager/npm-root.ts", "../src/lib/update-check/run-update-check.ts", "../src/lib/update-check/lock.ts", "../src/lib/update-check/registry.ts", "../src/lib/update-check/semver.ts", "../src/lib/update-check/update-cache.ts"],
4
- "sourcesContent": ["/**\n * Pure detection of *how* this CLI was installed, so a self-update advisory can name the one command\n * that will actually work. No fs, no spawn, no `process.env` read \u2014 every input is injected, which is\n * what lets the whole matrix be table-tested.\n *\n * Detection is env-first and LAZY: the only signal that costs a subprocess (`npm root -g`) is passed in\n * as `lazyNpmRoot` and consulted solely when every cheaper matcher has missed.\n */\nimport path from 'node:path'\n\n/** The published package this CLI updates itself to. Single source for every suggested argv. */\nexport const PACKAGE_NAME = 'infra-kit'\n\nconst LATEST = `${PACKAGE_NAME}@latest`\n\nexport type InstallManager = 'npm' | 'pnpm' | 'yarn' | 'bun' | 'volta' | 'homebrew' | 'unknown'\n\nexport interface InstallManagerInfo {\n manager: InstallManager\n /** The command to run. Only safe to spawn ourselves when `canSelfSpawn`; otherwise print it. */\n updateCommand: string[]\n /** False when we must not run the command for the user \u2014 either it needs sudo/a tap, or we guessed. */\n canSelfSpawn: boolean\n}\n\n/**\n * Canonicalises a candidate *root* directory. Required, not optional: `selfRealPath` has already had its\n * symlinks followed, so comparing it against a raw `PNPM_HOME`/`npm root -g`/`cwd` compares a resolved\n * path to an unresolved one and silently never matches. macOS is the everyday proof \u2014 `/var` is a symlink\n * to `/private/var`, so a global install under `/var/...` reports `unknown` unless both sides are resolved.\n * Callers inject the real thing; tests inject a stub.\n */\nexport type RealpathFn = (dir: string) => string\n\nexport interface DetectInstallManagerInput {\n /** Fully-resolved (symlinks followed) path to this CLI's entry file. */\n selfRealPath: string\n env: NodeJS.ProcessEnv\n /** Canonicalises each candidate root before comparison. See {@link RealpathFn}. */\n realpath: RealpathFn\n /** `npm root -g`, deferred: invoked at most once, and only when no cheaper matcher hit. */\n lazyNpmRoot?: () => string | undefined\n}\n\n/**\n * Is `child` inside the `parent` subtree? Boundary-aware: a naive `startsWith` would call\n * `/Users/x/pnpm-ish` a child of `/Users/x/pnpm`. `parent` is canonicalised first because it arrives raw\n * from the environment while `child` is already a realpath.\n */\nconst isWithin = (parent: string, child: string, realpath: RealpathFn): boolean => {\n const rel = path.relative(realpath(path.resolve(parent)), path.resolve(child))\n\n return rel !== '' && !rel.startsWith('..') && !path.isAbsolute(rel)\n}\n\n/** Does `p` contain `name` as a whole path SEGMENT? `/a/node_modules_backup/b` must not match `node_modules`. */\nconst hasSegment = (p: string, name: string): boolean => {\n return path.resolve(p).split(path.sep).includes(name)\n}\n\n/** `env[key]` names a directory that contains `selfRealPath`. Absent/empty env var \u2192 no match. */\nconst underEnvDir = (env: NodeJS.ProcessEnv, key: string, selfRealPath: string, realpath: RealpathFn): boolean => {\n const dir = env[key]\n\n return dir != null && dir !== '' && isWithin(dir, selfRealPath, realpath)\n}\n\ninterface Matcher extends Omit<InstallManagerInfo, 'manager'> {\n manager: InstallManager\n test: (input: Required<Pick<DetectInstallManagerInput, 'selfRealPath' | 'env' | 'realpath'>>) => boolean\n}\n\n/**\n * Ordered, first-hit-wins. Order matters: volta and homebrew wrap an npm-shaped layout, so they must be\n * consulted before the generic npm prefix matcher would claim them.\n */\nconst MATCHERS: Matcher[] = [\n {\n manager: 'volta',\n // volta owns the shim; `volta install` is the correct tool, not a workaround \u2014 so we may run it.\n test: ({ selfRealPath, env, realpath }) => {\n return (\n underEnvDir(env, 'VOLTA_HOME', selfRealPath, realpath) ||\n hasSegment(selfRealPath, '.volta') ||\n hasSegment(selfRealPath, 'volta')\n )\n },\n updateCommand: ['volta', 'install', PACKAGE_NAME],\n canSelfSpawn: true,\n },\n {\n manager: 'homebrew',\n // `brew upgrade` can touch the prefix, relink, and prompt \u2014 never ours to run unattended.\n test: ({ selfRealPath, env, realpath }) => {\n return hasSegment(selfRealPath, 'Cellar') || underEnvDir(env, 'HOMEBREW_PREFIX', selfRealPath, realpath)\n },\n updateCommand: ['brew', 'upgrade', PACKAGE_NAME],\n canSelfSpawn: false,\n },\n {\n manager: 'pnpm',\n test: ({ selfRealPath, env, realpath }) => {\n return (\n underEnvDir(env, 'PNPM_HOME', selfRealPath, realpath) ||\n (hasSegment(selfRealPath, 'pnpm') && hasSegment(selfRealPath, 'global'))\n )\n },\n updateCommand: ['pnpm', 'add', '-g', LATEST],\n canSelfSpawn: true,\n },\n {\n manager: 'bun',\n test: ({ selfRealPath, env, realpath }) => {\n return underEnvDir(env, 'BUN_INSTALL', selfRealPath, realpath) || hasSegment(selfRealPath, '.bun')\n },\n updateCommand: ['bun', 'add', '-g', LATEST],\n canSelfSpawn: true,\n },\n {\n manager: 'yarn',\n test: ({ selfRealPath }) => {\n return (\n hasSegment(selfRealPath, '.yarn') || (hasSegment(selfRealPath, 'yarn') && hasSegment(selfRealPath, 'global'))\n )\n },\n updateCommand: ['yarn', 'global', 'add', LATEST],\n canSelfSpawn: true,\n },\n {\n manager: 'npm',\n test: ({ selfRealPath, env, realpath }) => {\n return underEnvDir(env, 'npm_config_prefix', selfRealPath, realpath)\n },\n updateCommand: ['npm', 'install', '-g', LATEST],\n canSelfSpawn: true,\n },\n]\n\nconst NPM_UPDATE_COMMAND = ['npm', 'install', '-g', LATEST]\n\n/**\n * Identify the package manager that owns `selfRealPath`.\n *\n * `lazyNpmRoot` is the fallback probe and is invoked at most once, only after every env/path matcher has\n * missed \u2014 a global `npm root -g` subprocess is never paid for a pnpm or volta install. When nothing\n * matches we report `unknown` with an npm command that is a *suggestion only* (`canSelfSpawn: false`):\n * running a guessed global install is worse than printing one.\n *\n * @example\n * detectInstallManager({ selfRealPath: '/Users/x/Library/pnpm/global/5/node_modules/infra-kit/dist/cli.js', env: {} })\n * // => { manager: 'pnpm', updateCommand: ['pnpm', 'add', '-g', 'infra-kit@latest'], canSelfSpawn: true }\n */\nexport const detectInstallManager = (input: DetectInstallManagerInput): InstallManagerInfo => {\n const { selfRealPath, env, realpath, lazyNpmRoot } = input\n const hit = MATCHERS.find(({ test }) => {\n return test({ selfRealPath, env, realpath })\n })\n\n if (hit) return { manager: hit.manager, updateCommand: hit.updateCommand, canSelfSpawn: hit.canSelfSpawn }\n\n const npmRoot = lazyNpmRoot?.()\n\n if (npmRoot != null && npmRoot !== '' && isWithin(npmRoot, selfRealPath, realpath)) {\n return { manager: 'npm', updateCommand: NPM_UPDATE_COMMAND, canSelfSpawn: true }\n }\n\n return { manager: 'unknown', updateCommand: NPM_UPDATE_COMMAND, canSelfSpawn: false }\n}\n\n/**\n * Is this CLI running from a *project-local* `node_modules` (as opposed to a global root)?\n *\n * The cwd clause is what does the distinguishing \u2014 BOTH a project install and a global root contain a\n * `/node_modules/` segment (`pnpm root -g` is `~/Library/pnpm/global/5/node_modules`). Without it we\n * would nag every global pnpm user to stop using a local install they do not have.\n *\n * Accepted false-negative: invoked from a subdirectory (cwd `project/apps/x`, deps at\n * `project/node_modules`) this returns false and the advisory stays silent. Under-warning is the safe\n * direction for a best-effort, never-throwing advisory \u2014 do not \"fix\" it by dropping the cwd clause.\n *\n * `cwd` is canonicalised before comparison for the same reason `detectInstallManager` canonicalises its\n * roots: `selfRealPath` is a realpath, and comparing it to an unresolved cwd never matches.\n *\n * @example\n * isLocalNodeModulesInstall('/repo/node_modules/infra-kit/dist/cli.js', '/repo', (p) => p) // => true\n */\nexport const isLocalNodeModulesInstall = (selfRealPath: string, cwd: string, realpath: RealpathFn): boolean => {\n return hasSegment(selfRealPath, 'node_modules') && isWithin(cwd, selfRealPath, realpath)\n}\n", "import { realpathSync } from 'node:fs'\nimport path from 'node:path'\n\n/**\n * The impure companion to the pure detection module: canonicalise a directory that may not exist.\n *\n * `realpathSync` throws on a missing path, and candidate roots routinely miss \u2014 `PNPM_HOME` can point at a\n * directory the user never created, `npm root -g` can name a prefix that was removed. A throw there would\n * turn \"we could not identify your install\" into a crash, so an unresolvable path falls back to\n * `path.resolve`, which still normalises `.`/`..` and yields a usable, non-matching absolute path.\n */\nexport const safeRealpath = (dir: string): string => {\n try {\n return realpathSync(dir)\n } catch {\n return path.resolve(dir)\n }\n}\n", "/* eslint-disable sonarjs/no-os-command-from-path */\n/**\n * `npm root -g`, the LAZY fallback for {@link detectInstallManager}.\n *\n * Why it is indispensable: the `npm` matcher keys off `npm_config_prefix`, which npm sets only while npm\n * itself is running. A user who ran `npm i -g infra-kit` months ago and now types `ik` has no such var,\n * so every cheap matcher misses and detection reports `unknown` / `canSelfSpawn: false`. Without this\n * probe the single most common install method can never update itself.\n *\n * Shared by `self-update` (interactive) and the background update worker so the two can never disagree\n * about who owns the install.\n */\nimport { execFileSync } from 'node:child_process'\nimport process from 'node:process'\n\nconst NPM_ROOT_TIMEOUT_MS = 3_000\n\n/**\n * The global npm root, or undefined when npm is absent, slow, or errors. Costs a subprocess, so\n * `detectInstallManager` invokes it at most once and only after every env/path matcher has missed \u2014 a\n * pnpm or volta install never pays for it.\n *\n * Resolving the user's `npm` from PATH is inherent here: we must ask the very package manager that owns\n * this install where its global root is. Pinning an absolute path would defeat the detection.\n *\n * @example\n * defaultLazyNpmRoot() // => '/usr/local/lib/node_modules' | undefined\n */\nexport const defaultLazyNpmRoot = (): string | undefined => {\n try {\n const out = execFileSync('npm', ['root', '-g'], {\n encoding: 'utf-8',\n timeout: NPM_ROOT_TIMEOUT_MS,\n stdio: ['ignore', 'pipe', 'ignore'],\n shell: process.platform === 'win32',\n })\n const trimmed = out.trim()\n\n return trimmed === '' ? undefined : trimmed\n } catch {\n return undefined\n }\n}\n", "/**\n * The CHILD half of the auto-update, spawned detached by `maybeAutoUpdate`. It outlives the CLI\n * invocation that started it, so it may take its time and it may replace the binary.\n *\n * WHY IT WAITS FOR THE PARENT TO EXIT \u2014 the load-bearing invariant of this whole feature:\n * `scripts/build.js` sets esbuild `splitting: true`, so `dist/cli.js` lazily imports sibling\n * `chunk-*.js` files at runtime (that is how the Ink TUI stays off the fast path). A package manager\n * installing over `dist/` mid-command deletes chunks the parent has not imported yet, and the parent\n * dies on a dynamic import of a file that no longer exists. Waiting for the parent to exit is what\n * makes a silent background install safe rather than a random crash under `infra-kit dev`.\n */\nimport { spawnSync } from 'node:child_process'\nimport { homedir } from 'node:os'\nimport process from 'node:process'\n\nimport { defaultLazyNpmRoot, detectInstallManager, safeRealpath } from 'src/lib/install-manager'\nimport { withoutPackageManagerEnv } from 'src/lib/pm-env'\n\nimport { acquireUpdateLock } from './lock'\nimport { fetchLatestVersion } from './registry'\nimport { isNewerVersion } from './semver'\nimport { writeUpdateCache } from './update-cache'\nimport type { UpdateCache } from './update-cache'\n\n/** How often to re-check whether the parent is gone. */\nexport const PARENT_POLL_INTERVAL_MS = 200\n\n/**\n * Give up waiting after this long. A long-lived parent (`infra-kit dev` runs for hours) must not leave\n * an immortal child pinned to a stale version: we simply skip this cycle. `lastCheckMs` is already\n * persisted by then, so the next short-lived command re-checks after the normal 24h window.\n */\nexport const PARENT_WAIT_TIMEOUT_MS = 5 * 60 * 1000\n\nexport interface RunUpdateCheckDeps {\n env?: NodeJS.ProcessEnv\n nowMs?: number\n fetchLatest?: (env: NodeJS.ProcessEnv) => Promise<string | null>\n writeCache?: typeof writeUpdateCache\n isProcessAlive?: (pid: number) => boolean\n sleep?: (ms: number) => Promise<void>\n /** Monotonic-ish source for the parent-wait deadline. Injected so the timeout is testable without real time. */\n clock?: () => number\n /** `npm root -g` probe. Defaults to the real subprocess; this child is detached, so it can afford one. */\n lazyNpmRoot?: () => string | undefined\n /** Single-flight guard. Returns a release fn, or null when another worker already holds the lock. */\n acquireLock?: () => (() => void) | null\n spawnSync?: typeof spawnSync\n /** Realpath of the installed `dist/cli.js`, used to identify the owning package manager. */\n selfRealPath: string\n parentPid?: number\n}\n\n/** Signal 0 performs the permission/existence check without delivering anything. */\nconst defaultIsProcessAlive = (pid: number): boolean => {\n try {\n process.kill(pid, 0)\n\n return true\n } catch {\n return false\n }\n}\n\nconst defaultSleep = (ms: number): Promise<void> => {\n return new Promise((resolve) => {\n setTimeout(resolve, ms)\n })\n}\n\n/**\n * Block until `parentPid` exits or {@link PARENT_WAIT_TIMEOUT_MS} elapses. Returns whether the parent\n * is actually gone \u2014 the caller must not install on a timeout.\n */\nconst waitForParentExit = async (\n parentPid: number,\n deps: Required<Pick<RunUpdateCheckDeps, 'isProcessAlive' | 'sleep' | 'clock'>>,\n): Promise<boolean> => {\n const deadline = deps.clock() + PARENT_WAIT_TIMEOUT_MS\n\n while (deps.clock() < deadline) {\n if (!deps.isProcessAlive(parentPid)) return true\n\n await deps.sleep(PARENT_POLL_INTERVAL_MS)\n }\n\n return !deps.isProcessAlive(parentPid)\n}\n\nexport type UpdateCheckOutcome =\n | 'installed'\n | 'install-failed'\n | 'up-to-date'\n | 'fetch-failed'\n | 'cannot-self-spawn'\n | 'parent-still-running'\n | 'parent-unknown'\n | 'already-running'\n\n/**\n * Fetch, decide, and (when safe) install \u2014 returning WHY it did what it did so tests can distinguish\n * \"no update\" from \"could not install\". Never throws.\n *\n * `lastCheckMs` is written from the ATTEMPT, before any early return. If it were written only on\n * success, an offline user would re-spawn a doomed child on every single command.\n *\n * @example\n * await runUpdateCheck('0.1.130', { selfRealPath: '/usr/local/lib/node_modules/infra-kit/dist/cli.js' })\n * // => 'installed'\n */\nexport const runUpdateCheck = async (currentVersion: string, deps: RunUpdateCheckDeps): Promise<UpdateCheckOutcome> => {\n const acquireLock = deps.acquireLock ?? acquireUpdateLock\n\n // Single-flight. N shells launched at once all read the same stale cache and each spawns a worker;\n // the cache throttle cannot stop them because it is only written after the fetch returns. Without\n // this, a pending update means N concurrent `npm install -g` over one global directory.\n const release = acquireLock()\n\n if (!release) return 'already-running'\n\n try {\n return await runUpdateCheckLocked(currentVersion, deps)\n } finally {\n release()\n }\n}\n\nconst runUpdateCheckLocked = async (currentVersion: string, deps: RunUpdateCheckDeps): Promise<UpdateCheckOutcome> => {\n const env = deps.env ?? process.env\n const nowMs = deps.nowMs ?? Date.now()\n const fetchLatest = deps.fetchLatest ?? fetchLatestVersion\n const writeCache = deps.writeCache ?? writeUpdateCache\n const isProcessAlive = deps.isProcessAlive ?? defaultIsProcessAlive\n const sleep = deps.sleep ?? defaultSleep\n const clock = deps.clock ?? Date.now\n const lazyNpmRoot = deps.lazyNpmRoot ?? defaultLazyNpmRoot\n const spawn = deps.spawnSync ?? spawnSync\n\n const latestVersion = await fetchLatest(env)\n\n // Every path below writes the cache EXACTLY once, and always with `lastCheckMs: nowMs` \u2014 the throttle\n // burns on the ATTEMPT, never on success. If it burned only on success, an offline user would respawn\n // a doomed child on every command.\n const finish = (outcome: UpdateCheckOutcome, cache: Omit<UpdateCache, 'lastCheckMs'>): UpdateCheckOutcome => {\n writeCache({ lastCheckMs: nowMs, ...cache })\n\n return outcome\n }\n\n if (latestVersion === null) return finish('fetch-failed', { latestVersion: null, updateCommand: null })\n if (!isNewerVersion(latestVersion, currentVersion))\n return finish('up-to-date', { latestVersion, updateCommand: null })\n\n // `lazyNpmRoot` is what makes the COMMON case work: a plain `npm i -g infra-kit` leaves no\n // `npm_config_prefix` in the user's shell, so every cheap matcher misses and detection would report\n // `unknown` / `canSelfSpawn: false`. Without this probe the auto-update would silently degrade to a\n // notice for the majority of installs. The subprocess is affordable here and nowhere else.\n const { canSelfSpawn, updateCommand } = detectInstallManager({\n selfRealPath: deps.selfRealPath,\n env,\n realpath: safeRealpath,\n lazyNpmRoot,\n })\n\n // Homebrew relinks its prefix and may prompt; an unknown location means the command is a guess.\n // Both stay the user's call, so record the command for `maybeAutoUpdate` to print next invocation.\n if (!canSelfSpawn) return finish('cannot-self-spawn', { latestVersion, updateCommand })\n\n // Fail SAFE. Without a parent to outlive we cannot know whether a live CLI is still lazily importing\n // `chunk-*.js` out of the `dist/` we are about to replace, so we skip the cycle rather than install\n // blind. The real spawn always passes `--parent-pid`; only a hand-run worker lands here.\n if (deps.parentPid == null) return finish('parent-unknown', { latestVersion, updateCommand: null })\n\n const parentGone = await waitForParentExit(deps.parentPid, { isProcessAlive, sleep, clock })\n\n // A long-lived parent (`infra-kit dev`) outlasted the wait. Stay silent and retry next window.\n if (!parentGone) return finish('parent-still-running', { latestVersion, updateCommand: null })\n\n // `shell` on win32 so the `.cmd` shims npm/pnpm/yarn ship as global bins resolve.\n // `withoutPackageManagerEnv` strips inherited `npm_*` vars, which otherwise make pnpm/portless-style\n // tools believe they were invoked via `npx`/`dlx` and refuse to run.\n //\n // `cwd` is pinned to the home directory, and this is a SECURITY control, not tidiness: npm resolves\n // `registry=` from an `.npmrc` on disk relative to the cwd. Inheriting the caller's cwd would let any\n // repo containing a hostile `.npmrc` silently redirect this unattended `install -g` to an attacker's\n // registry, executing its lifecycle scripts. Stripping `npm_config_registry` from the env does NOT\n // close that hole, because the redirect lives in a file, not the environment.\n const result = spawn(updateCommand[0] as string, updateCommand.slice(1), {\n stdio: 'ignore',\n shell: process.platform === 'win32',\n cwd: homedir(),\n env: withoutPackageManagerEnv(env),\n windowsHide: true,\n })\n\n // Record the manual command so a persistently failing silent install (EACCES on a root-owned global\n // dir, say) still surfaces ONE actionable line, instead of failing invisibly forever.\n if (result.error || result.signal || result.status !== 0) {\n return finish('install-failed', { latestVersion, updateCommand })\n }\n\n // The installed version is now `latestVersion`; clear it so the next run does not re-notify.\n return finish('installed', { latestVersion: null, updateCommand: null })\n}\n", "/**\n * Single-flight lock for the background update worker.\n *\n * Without it, N shells starting at once all read the same stale cache and each spawns a worker \u2014 and\n * once a newer version exists, each of those runs `npm install -g` concurrently, over the same global\n * directory. (Measured: five concurrent `ik` invocations spawn five workers.) The throttle in the cache\n * cannot prevent this: it is only written AFTER the fetch returns, long after the other workers launched.\n *\n * `openSync(path, 'wx')` is the primitive \u2014 an atomic create-if-absent, so exactly one worker wins even\n * if all of them call it in the same millisecond.\n */\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\n\nimport { getCacheRoot } from 'src/lib/constants'\n\nexport const LOCK_FILE_NAME = 'update-check.lock'\n\n/**\n * A lock older than this is assumed to belong to a worker that was killed before it could clean up.\n * Must exceed the worker's own worst case (a 5-minute parent wait plus a 2.5s fetch), or a long-lived\n * `infra-kit dev` session would let a healthy lock be stolen out from under its worker.\n */\nexport const LOCK_STALE_MS = 10 * 60 * 1000\n\nexport const lockFilePath = (): string => {\n return path.join(getCacheRoot(), LOCK_FILE_NAME)\n}\n\nexport interface LockDeps {\n nowMs?: number\n lockPath?: string\n}\n\n/** Delete a lock whose mtime predates the staleness window. Best-effort: losing the race is harmless. */\nconst reapIfStale = (lockPath: string, nowMs: number): void => {\n try {\n if (nowMs - fs.statSync(lockPath).mtimeMs > LOCK_STALE_MS) fs.rmSync(lockPath, { force: true })\n } catch {\n // Vanished under us \u2014 another worker reaped it. Nothing to do.\n }\n}\n\n/**\n * Take the lock, or return null when another worker already holds it.\n *\n * Returns a release function rather than a boolean so the caller cannot forget which path releases:\n * there is exactly one handle, and it is the only thing that can unlink the file.\n *\n * @example\n * const release = acquireUpdateLock()\n * if (!release) return 'already-running'\n * try { ... } finally { release() }\n */\nexport const acquireUpdateLock = (deps: LockDeps = {}): (() => void) | null => {\n const nowMs = deps.nowMs ?? Date.now()\n const lockPath = deps.lockPath ?? lockFilePath()\n\n try {\n fs.mkdirSync(path.dirname(lockPath), { recursive: true })\n } catch {\n return null\n }\n\n const open = (): number | null => {\n try {\n // 'wx' fails with EEXIST if the file is already there: an atomic test-and-set.\n return fs.openSync(lockPath, 'wx', 0o600)\n } catch {\n return null\n }\n }\n\n let fd = open()\n\n if (fd === null) {\n reapIfStale(lockPath, nowMs)\n // One retry only. If a live worker holds the lock, we are the loser and must simply stand down.\n fd = open()\n }\n\n if (fd === null) return null\n\n try {\n fs.writeFileSync(fd, String(process.pid))\n } catch {\n // The pid is a debugging aid, not part of the protocol; the lock is held either way.\n }\n\n return () => {\n try {\n fs.closeSync(fd)\n } catch {\n // Already closed.\n }\n\n try {\n fs.rmSync(lockPath, { force: true })\n } catch {\n // Already gone; a later worker will reap it via LOCK_STALE_MS regardless.\n }\n }\n}\n", "/**\n * Ask the npm registry for the `latest` dist-tag. Node >= 24 (see `engines`), so global `fetch` is\n * available and this needs no dependency.\n */\nimport { PACKAGE_NAME } from 'src/lib/install-manager'\n\nexport const DEFAULT_REGISTRY = 'https://registry.npmjs.org'\n\n/** Bounded so a hanging registry can never keep a detached child alive indefinitely. */\nexport const FETCH_TIMEOUT_MS = 2_500\n\n/**\n * The registry this install actually talks to. A user behind a corporate mirror has `npm_config_registry`\n * set; hitting registry.npmjs.org directly would either fail closed (firewall) or, worse, report a public\n * version they cannot install.\n *\n * @example\n * registryUrl({ npm_config_registry: 'https://nexus.corp/repo/npm/' }) // => 'https://nexus.corp/repo/npm'\n */\nexport const registryUrl = (env: NodeJS.ProcessEnv): string => {\n const configured = env.npm_config_registry\n\n let base = configured != null && configured !== '' ? configured : DEFAULT_REGISTRY\n\n // A `/\\/+$/` regex would do this in one line, but its backtracking is super-linear on a hostile input.\n while (base.endsWith('/')) {\n base = base.slice(0, -1)\n }\n\n return base\n}\n\n/**\n * The published `latest` version, or null on ANY failure (offline, timeout, non-200, malformed body).\n * Never throws: a failed check must be indistinguishable from \"no update available\" to every caller.\n *\n * @example\n * await fetchLatestVersion(process.env) // => '0.1.131' | null\n */\nexport const fetchLatestVersion = async (\n env: NodeJS.ProcessEnv,\n fetchFn: typeof fetch = fetch,\n): Promise<string | null> => {\n const controller = new AbortController()\n const timer = setTimeout(() => {\n controller.abort()\n }, FETCH_TIMEOUT_MS)\n\n try {\n const response = await fetchFn(`${registryUrl(env)}/${PACKAGE_NAME}/latest`, {\n signal: controller.signal,\n headers: { accept: 'application/json' },\n })\n\n if (!response.ok) return null\n\n const body: unknown = await response.json()\n const version = (body as { version?: unknown } | null)?.version\n\n return typeof version === 'string' && version !== '' ? version : null\n } catch {\n return null\n } finally {\n clearTimeout(timer)\n }\n}\n", "/**\n * Registry-safe version comparison for the auto-update check.\n *\n * Deliberately NOT `src/lib/version-utils`: `parseVersion` there does `versionStr.slice(1)` because it\n * only ever sees `v`-prefixed release tags, so it turns a bare registry version (`0.1.130`) into\n * `[NaN, 1, 130]`. Feeding npm's `dist-tags.latest` through it silently compares NaNs and never fires.\n * These inputs are bare semver from `registry.npmjs.org` and from our own `package.json`.\n */\n\n/** `1.2.3-beta.4+build` \u2192 the `[1, 2, 3]` release triple and the `beta.4` prerelease, or null if unparsable. */\ninterface ParsedVersion {\n release: [number, number, number]\n /** Dot-separated prerelease identifiers, empty when this is a final release. */\n prerelease: string[]\n}\n\nconst RELEASE_PATTERN = /^(\\d+)\\.(\\d+)\\.(\\d+)$/\n\n/**\n * Parse bare semver. Returns null (never throws, never NaNs) for anything that is not\n * `major.minor.patch` with optional `-prerelease` and `+build` \u2014 a garbled registry body must read as\n * \"no update\", not as \"update to NaN\".\n *\n * @example\n * parseSemver('0.1.130') // => { release: [0, 1, 130], prerelease: [] }\n * parseSemver('1.0.0-rc.1+abc') // => { release: [1, 0, 0], prerelease: ['rc', '1'] }\n * parseSemver('v1.0.0') // => null\n */\nexport const parseSemver = (version: string): ParsedVersion | null => {\n // Build metadata is ignored entirely by semver precedence rules.\n const [withoutBuild = ''] = version.trim().split('+')\n const [core = '', ...prereleaseParts] = withoutBuild.split('-')\n const match = RELEASE_PATTERN.exec(core)\n\n if (!match) return null\n\n const [major, minor, patch] = [match[1], match[2], match[3]].map(Number) as [number, number, number]\n\n return {\n release: [major, minor, patch],\n // Re-join on '-' so `1.0.0-rc-1` keeps its hyphen inside a single identifier.\n prerelease: prereleaseParts.length > 0 ? prereleaseParts.join('-').split('.') : [],\n }\n}\n\nconst NUMERIC_IDENTIFIER = /^\\d+$/\n\n/** Compare two unequal prerelease identifiers per semver \u00A711. Numeric ones rank below alphanumeric ones. */\nconst compareIdentifier = (left: string, right: string): number => {\n const leftNumeric = NUMERIC_IDENTIFIER.test(left)\n const rightNumeric = NUMERIC_IDENTIFIER.test(right)\n\n if (leftNumeric && rightNumeric) return Number(left) - Number(right)\n if (leftNumeric !== rightNumeric) return leftNumeric ? -1 : 1\n\n return left < right ? -1 : 1\n}\n\n/** Compare prerelease identifier lists. A shorter list has lower precedence: `1.0.0-rc` < `1.0.0-rc.1`. */\nconst comparePrerelease = (a: string[], b: string[]): number => {\n for (let index = 0; index < Math.max(a.length, b.length); index += 1) {\n const left = a[index]\n const right = b[index]\n\n if (left === undefined) return -1\n if (right === undefined) return 1\n if (left !== right) return compareIdentifier(left, right)\n }\n\n return 0\n}\n\n/**\n * Is `latest` strictly newer than `current`? False when either is unparsable, so a bad registry\n * response can never trigger an install.\n *\n * The load-bearing case is NUMERIC, not lexical, comparison: `0.1.9` must be older than `0.1.130`.\n * A string compare would order them the other way and pin every user to the older build forever.\n *\n * A final release outranks its own prereleases (`1.0.0` > `1.0.0-rc.1`), so a user on `rc` is offered\n * the release, and a user on the release is never \"downgraded\" to an rc.\n *\n * @example\n * isNewerVersion('0.1.130', '0.1.9') // => true (numeric, not lexical)\n * isNewerVersion('1.0.0', '1.0.0-rc.1') // => true\n * isNewerVersion('0.1.130', '0.1.130') // => false\n * isNewerVersion('garbage', '0.1.0') // => false\n */\nexport const isNewerVersion = (latest: string, current: string): boolean => {\n const parsedLatest = parseSemver(latest)\n const parsedCurrent = parseSemver(current)\n\n if (!parsedLatest || !parsedCurrent) return false\n\n for (let index = 0; index < 3; index += 1) {\n const left = parsedLatest.release[index] as number\n const right = parsedCurrent.release[index] as number\n\n if (left !== right) return left > right\n }\n\n const latestIsFinal = parsedLatest.prerelease.length === 0\n const currentIsFinal = parsedCurrent.prerelease.length === 0\n\n if (latestIsFinal !== currentIsFinal) return latestIsFinal\n\n return comparePrerelease(parsedLatest.prerelease, parsedCurrent.prerelease) > 0\n}\n", "/**\n * The throttle + notice state for the auto-update check, at `$cacheRoot/update-check.json`.\n *\n * `getCacheRoot()` \u2014 NOT `getSessionCacheDir()`, which throws unless `INFRA_KIT_SESSION` is set. A user\n * who installed this CLI globally and never ran `infra-kit init` has no session, and the update check\n * must still work for exactly that person.\n */\nimport fs from 'node:fs'\nimport path from 'node:path'\n\nimport { atomicWriteFileSync, getCacheRoot } from 'src/lib/constants'\n\nexport const CACHE_FILE_NAME = 'update-check.json'\n\n/** Refresh at most once a day. A background check is cheap, but not once per shell command. */\nexport const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000\n\nexport interface UpdateCache {\n /**\n * When the last check was ATTEMPTED \u2014 never \"when it last succeeded\". An offline user whose fetch\n * throws must still burn their 24h window, or every single command spawns another doomed child.\n */\n lastCheckMs: number\n /** Latest version seen on the registry, or null when the last attempt failed. */\n latestVersion: string | null\n /**\n * The command the USER must run, set only when the worker was not allowed to install for them\n * (Homebrew, or an install location it could not identify). Null means \"handled, say nothing\".\n *\n * The worker decides this, not the reader: identifying the owning package manager can cost an\n * `npm root -g` subprocess, and the CLI startup path must never pay for one. The background child\n * already pays it, so it writes the verdict down.\n */\n updateCommand: string[] | null\n}\n\nexport const cacheFilePath = (): string => {\n return path.join(getCacheRoot(), CACHE_FILE_NAME)\n}\n\nconst isStringArray = (value: unknown): value is string[] => {\n return (\n Array.isArray(value) &&\n value.every((entry) => {\n return typeof entry === 'string'\n })\n )\n}\n\nconst isUpdateCache = (value: unknown): value is UpdateCache => {\n if (typeof value !== 'object' || value === null) return false\n\n const { lastCheckMs, latestVersion, updateCommand } = value as Partial<UpdateCache>\n\n return (\n typeof lastCheckMs === 'number' &&\n Number.isFinite(lastCheckMs) &&\n (latestVersion === null || typeof latestVersion === 'string') &&\n (updateCommand === null || isStringArray(updateCommand))\n )\n}\n\n/**\n * Read the cache, or null when it is missing/unreadable/corrupt. A null read means \"stale\" to every\n * caller, so a first run and a hand-mangled file behave identically: check again.\n *\n * @example\n * readUpdateCache() // => { lastCheckMs: 1770000000000, latestVersion: '0.1.131' } | null\n */\nexport const readUpdateCache = (\n readFile: (p: string) => string = (p) => {\n return fs.readFileSync(p, 'utf8')\n },\n): UpdateCache | null => {\n try {\n const parsed: unknown = JSON.parse(readFile(cacheFilePath()))\n\n return isUpdateCache(parsed) ? parsed : null\n } catch {\n return null\n }\n}\n\n/**\n * Persist the check result. Creates `$cacheRoot` first: `atomicWriteFileSync` writes a temp file\n * beside the target and renames, so it throws ENOENT on a machine that has never had a `~/.cache/infra-kit`.\n * Without this mkdir the write fails, `lastCheckMs` never lands, and the throttle degrades into a\n * detached-child spawn on every invocation.\n *\n * @example\n * writeUpdateCache({ lastCheckMs: Date.now(), latestVersion: '0.1.131' })\n */\nexport const writeUpdateCache = (cache: UpdateCache): void => {\n const root = getCacheRoot()\n\n fs.mkdirSync(root, { recursive: true })\n atomicWriteFileSync(path.join(root, CACHE_FILE_NAME), JSON.stringify(cache), 0o600)\n}\n\n/**\n * Has the throttle window elapsed? A missing cache is stale by definition (first run).\n *\n * A `lastCheckMs` in the future (clock skew, or a restored backup) also reads as stale rather than\n * locking the user out of updates until their clock catches up.\n *\n * @example\n * isStale(null, 1_000) // => true\n * isStale({ lastCheckMs: 0, latestVersion: null }, CHECK_INTERVAL_MS + 1) // => true\n */\nexport const isStale = (cache: UpdateCache | null, nowMs: number): boolean => {\n if (!cache) return true\n\n const elapsed = nowMs - cache.lastCheckMs\n\n return elapsed >= CHECK_INTERVAL_MS || elapsed < 0\n}\n"],
5
- "mappings": "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",
6
- "names": ["path", "PACKAGE_NAME", "LATEST", "isWithin", "parent", "child", "realpath", "rel", "hasSegment", "p", "name", "underEnvDir", "env", "key", "selfRealPath", "dir", "MATCHERS", "NPM_UPDATE_COMMAND", "detectInstallManager", "input", "lazyNpmRoot", "hit", "test", "npmRoot", "isLocalNodeModulesInstall", "cwd", "realpathSync", "path", "safeRealpath", "dir", "execFileSync", "process", "NPM_ROOT_TIMEOUT_MS", "defaultLazyNpmRoot", "trimmed", "spawnSync", "homedir", "process", "fs", "path", "process", "LOCK_FILE_NAME", "LOCK_STALE_MS", "lockFilePath", "path", "getCacheRoot", "reapIfStale", "lockPath", "nowMs", "fs", "acquireUpdateLock", "deps", "open", "fd", "process", "DEFAULT_REGISTRY", "FETCH_TIMEOUT_MS", "registryUrl", "env", "configured", "base", "fetchLatestVersion", "fetchFn", "controller", "timer", "response", "PACKAGE_NAME", "version", "RELEASE_PATTERN", "parseSemver", "version", "withoutBuild", "core", "prereleaseParts", "match", "major", "minor", "patch", "NUMERIC_IDENTIFIER", "compareIdentifier", "left", "right", "leftNumeric", "rightNumeric", "comparePrerelease", "a", "b", "index", "isNewerVersion", "latest", "current", "parsedLatest", "parsedCurrent", "latestIsFinal", "currentIsFinal", "fs", "path", "CACHE_FILE_NAME", "CHECK_INTERVAL_MS", "cacheFilePath", "path", "getCacheRoot", "isStringArray", "value", "entry", "isUpdateCache", "lastCheckMs", "latestVersion", "updateCommand", "readUpdateCache", "readFile", "p", "fs", "parsed", "writeUpdateCache", "cache", "root", "atomicWriteFileSync", "isStale", "nowMs", "elapsed", "PARENT_POLL_INTERVAL_MS", "PARENT_WAIT_TIMEOUT_MS", "defaultIsProcessAlive", "pid", "process", "defaultSleep", "ms", "resolve", "waitForParentExit", "parentPid", "deps", "deadline", "runUpdateCheck", "currentVersion", "release", "acquireUpdateLock", "runUpdateCheckLocked", "env", "nowMs", "fetchLatest", "fetchLatestVersion", "writeCache", "writeUpdateCache", "isProcessAlive", "sleep", "clock", "lazyNpmRoot", "defaultLazyNpmRoot", "spawn", "spawnSync", "latestVersion", "finish", "outcome", "cache", "isNewerVersion", "canSelfSpawn", "updateCommand", "detectInstallManager", "safeRealpath", "result", "homedir", "withoutPackageManagerEnv"]
7
- }
@@ -1,8 +0,0 @@
1
- import{a as h,c as f}from"./chunk-JD5T4VCB.js";import{render as H}from"ink";import W from"node:process";import{Box as c,Static as D,Text as l}from"ink";import{useEffect as z,useState as F}from"react";import{jsx as d,jsxs as L}from"react/jsx-runtime";var R=r=>{let{phase:e,narration:t,readyLines:i}=r,[s,a]=F(0);return z(()=>{if(i)return;let n=setInterval(()=>{a(o=>o+1)},O);return()=>{clearInterval(n)}},[i]),i?d(D,{items:i,children:(n,o)=>d(l,{children:n},o)}):L(c,{flexDirection:"column",children:[L(c,{children:[d(l,{color:"cyan",children:m[s%m.length]}),d(l,{dimColor:!0,children:` ${e}`})]}),t?d(l,{dimColor:!0,children:t}):null]})},m=["\u280B","\u2819","\u2839","\u2838","\u283C","\u2834","\u2826","\u2827","\u2807","\u280F"],O=80;import{Box as T,Static as C,Text as y}from"ink";import{Fragment as P,jsx as u,jsxs as U}from"react/jsx-runtime";var x=r=>{let{headerLines:e,logLines:t,footerLines:i}=r,s=[...e,...t],a=E(i,r.maxFooterLines);return U(P,{children:[u(C,{items:s,children:(n,o)=>u(y,{children:n},o)}),u(T,{flexDirection:"column",children:a.map((n,o)=>u(y,{children:n},o))})]})},E=(r,e)=>{if(e==null||e<1||r.length<=e)return r;let t=r.slice(0,e-1);return[...t,` \u2026 +${r.length-t.length} more`]};import k from"node:process";var _="\x1B[2K";var S="\x1B[?25h",N=(r,e)=>`\x1B[${r};${e}r`,b=()=>"\x1B[r",v=(r,e)=>`\x1B[${r};${e}H`,I=r=>r<=0?"":`${`
2
- `.repeat(r)}\x1B[${r}A`,g=(r,e)=>{if(r.length===0)return"";let t=e-r.length+1;return`\x1B7${r.map((s,a)=>`${v(t+a,1)}${_}${s}`).join("")}\x1B8`},w=(r,e)=>e-r<1?"":`${I(r)}${N(1,e-r)}`;var p=class{renderer;stdout;verbose;footerLines=[];installed=!1;resizeBound=!1;constructor(e){this.verbose=e.verbose,this.stdout=e.stdout??k.stdout,this.renderer=new f({appendLog:e.appendLog,verbose:this.verbose,isTTY:!!this.stdout.isTTY,write:t=>{this.stdout.write(t)}})}logFn=(e,t="info")=>{this.log(e,t)};rows(){let e=this.stdout.rows;return e!=null&&e>0?e:24}writeLines(e){for(let t of e)this.stdout.write(`${t}
3
- `)}narrate(e){this.renderer.teeOnly(e,"info"),this.verbose&&this.stdout.write(`${e}
4
- `)}log(e,t="info"){(t!=="debug"||this.verbose)&&this.stdout.write(`${e}
5
- `),this.renderer.teeOnly(e,t)}bootStep(e){this.narrate(e)}stopSpinner(){}applyRegion(){let e=this.rows(),t=w(this.footerLines.length,e);return t===""?!1:(this.stdout.write(`${t}${g(this.footerLines,e)}`),!0)}bindResize(){this.resizeBound||typeof this.stdout.on!="function"||(this.stdout.on("resize",this.onResize),this.resizeBound=!0)}ready(e){this.footerLines=this.renderer.formatReadyLines(e);for(let t of this.footerLines)this.renderer.teeOnly(t,"info");if(!this.stdout.isTTY){this.writeLines(this.footerLines);return}if(!this.applyRegion()){this.writeLines(this.footerLines);return}this.installed=!0,this.bindResize()}event(e){let t=` ${h(new Date)} ${e.tag} ${e.text}`;this.stdout.write(`${t}
6
- `),this.renderer.teeOnly(`${e.tag} ${e.text}`,"info")}refresh(e){this.footerLines=this.renderer.formatReadyLines(e),this.installed&&this.stdout.write(g(this.footerLines,this.rows()))}onResize=()=>{this.installed&&(this.applyRegion()||this.dispose())};dispose(){if(this.resizeBound&&typeof this.stdout.off=="function"&&(this.stdout.off("resize",this.onResize),this.resizeBound=!1),this.installed){let e=this.rows();this.stdout.write(`${b()}${v(e,1)}
7
- ${S}`),this.installed=!1}}};import{jsx as B}from"react/jsx-runtime";var $=class{renderer;renderFn;stdout;verbose;now;appendLog;instance=null;finalized=!1;handoff=null;persistent=!1;phase="";narration="";headerLines=[];logLines=[];footerLines=[];constructor(e){this.verbose=e.verbose??!1,this.renderFn=e.render??H,this.stdout=e.stdout??W.stdout,this.appendLog=e.appendLog,this.now=e.now??(()=>new Date),this.renderer=new f({appendLog:e.appendLog,verbose:this.verbose,isTTY:!0,write:t=>{this.stdout.write(t)},now:this.now})}logFn=(e,t="info")=>{this.log(e,t)};renderBoot(e){let t=B(R,{phase:this.phase,narration:this.narration,readyLines:e});if(this.instance){this.instance.rerender(t);return}this.instance=this.renderFn(t,{stdout:this.stdout,exitOnCtrlC:!1,patchConsole:!1})}unmount(){this.instance&&(this.instance.unmount(),this.instance=null)}narrate(e){if(this.handoff){this.handoff.narrate(e);return}if(this.renderer.teeOnly(e,"info"),this.persistent){this.verbose&&(this.logLines.push(e),this.rerenderPersistent());return}this.finalized||(this.narration=e,this.instance&&this.renderBoot())}log(e,t="info"){if(this.handoff){this.handoff.log(e,t);return}let i=t!=="debug"||this.verbose;if(this.persistent){this.renderer.teeOnly(e,t),i&&(this.logLines.push(e),this.rerenderPersistent());return}i&&this.unmount(),this.renderer.log(e,t)}bootStep(e){this.renderer.teeOnly(e,"info"),!this.finalized&&(this.phase=e,this.renderBoot())}stopSpinner(){this.persistent||this.unmount()}ready(e){if(e.hasUiChild){this.readyHandoff(e);return}this.readyPersistent(e)}readyHandoff(e){this.finalized=!0,this.unmount(),this.handoff=new p({appendLog:this.appendLog,verbose:this.verbose,stdout:this.stdout}),this.handoff.ready(e)}readyPersistent(e){this.headerLines=this.renderer.formatHeaderLines(e),this.footerLines=this.renderer.formatFooterLines(e),this.logLines=[];for(let t of this.headerLines)this.renderer.teeOnly(t,"info");this.finalized=!0,this.persistent=!0,typeof this.stdout.on=="function"&&this.stdout.on("resize",this.onResize),this.rerenderPersistent()}maxFooterLines(){return Math.max(3,(this.stdout.rows??24)-6)}rerenderPersistent(){let e=B(x,{headerLines:this.headerLines,logLines:this.logLines,footerLines:this.footerLines,maxFooterLines:this.maxFooterLines()});if(this.instance){this.instance.rerender(e);return}this.instance=this.renderFn(e,{stdout:this.stdout,exitOnCtrlC:!1,patchConsole:!1})}onResize=()=>{this.persistent&&this.rerenderPersistent()};event(e){if(this.handoff){this.handoff.event(e);return}if(this.persistent){let t=` ${h(this.now())} ${e.tag} ${e.text}`;this.renderer.teeOnly(`${e.tag} ${e.text}`,"info"),this.logLines.push(t),this.rerenderPersistent();return}this.unmount(),this.renderer.event(e)}refresh(e){if(this.handoff){this.handoff.refresh(e);return}this.persistent&&(this.footerLines=this.renderer.formatFooterLines(e),this.rerenderPersistent())}dispose(){if(this.handoff){this.handoff.dispose(),this.handoff=null;return}this.persistent&&(this.rerenderPersistent(),typeof this.stdout.off=="function"&&this.stdout.off("resize",this.onResize)),this.finalized=!0,this.persistent=!1,this.unmount()}};export{$ as PersistentInkDevUi};
8
- //# sourceMappingURL=persistent-ink-dev-ui-LRTJBSCH.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../src/tui/dev-ui/persistent-ink-dev-ui.tsx", "../src/tui/dev-ui/boot-region.tsx", "../src/tui/dev-ui/live-region.tsx", "../src/tui/dev-ui/scroll-region-dev-ui.ts", "../src/dev/scroll-region.ts"],
4
- "sourcesContent": ["import { render as inkRender } from 'ink'\nimport process from 'node:process'\nimport type { ReactElement } from 'react'\n\nimport type { DevUi, LogLevel, ReadySummary } from 'src/dev/dev-ui'\nimport { DevRenderer, formatClock } from 'src/dev/render'\n\nimport { BootRegion } from './boot-region'\nimport { LiveRegion } from './live-region'\nimport { ScrollRegionDevUi } from './scroll-region-dev-ui'\n\n/**\n * Ink-backed {@link DevUi} for `infra-kit dev` that keeps a PERSISTENT live footer after `ready` on a\n * backend-only session. It is the default UI on any interactive TTY: the boot phase is identical to the\n * plain path, but at {@link ready} it branches on `summary.hasUiChild`.\n *\n * - UI session (`hasUiChild`): unmount Ink and HAND OFF to a {@link ScrollRegionDevUi}, which pins the\n * ready header as a DECSTBM scroll-region footer. The `turbo run dev` child's piped output makes the\n * tail unbounded, and Ink repaints its live region on every appended line; the scroll region appends\n * with a single write instead. The scroll-region UI owns `ready`/`event`/`log`/`refresh`/`dispose`\n * for the rest of the session.\n * - Backend-only (`!hasUiChild`): mount {@link LiveRegion} and STAY mounted \u2014 the header is committed\n * once via `<Static>`, logs accrete above the footer, and `refresh` repaints the footer in place as\n * health flips.\n *\n * The embedded {@link DevRenderer} owns the file-tee format (`teeOnly`) exactly as in `InkDevUi`; in\n * persistent mode it is used ONLY for teeing (never its direct stdout write, which would collide with\n * the live region). Every component here is OUTPUT-ONLY (no `useInput`) \u2014 see live-region.tsx.\n */\n\n/** The subset of an Ink render handle this UI drives \u2014 shared by `ink` and `ink-testing-library`. */\ninterface RenderHandle {\n rerender: (tree: ReactElement) => void\n unmount: () => void\n}\n\n/** Render seam: `ink`'s `render` by default; `ink-testing-library`'s `render` is injected in tests. */\ntype RenderFn = (tree: ReactElement, options?: unknown) => RenderHandle\n\nexport interface PersistentInkDevUiDeps {\n /** File tee \u2014 the same appender the runner hands the plain renderer. */\n appendLog: (text: string) => void\n /** Mirrors the renderer's verbose gate: only in verbose does a `debug` log reach the terminal. */\n verbose?: boolean\n /** Clock seam (for deterministic tee + event timestamps in tests). */\n now?: () => Date\n /** Render seam (default `ink`'s `render`); tests inject `ink-testing-library`'s `render`. */\n render?: RenderFn\n /** Target stream (default `process.stdout`). */\n stdout?: NodeJS.WriteStream\n}\n\nexport class PersistentInkDevUi implements DevUi {\n private readonly renderer: DevRenderer\n private readonly renderFn: RenderFn\n private readonly stdout: NodeJS.WriteStream\n private readonly verbose: boolean\n private readonly now: () => Date\n /** The file tee appender \u2014 kept so a UI-session handoff can hand the same sink to {@link ScrollRegionDevUi}. */\n private readonly appendLog: (text: string) => void\n /** Live Ink handle, or `null` when not mounted. */\n private instance: RenderHandle | null = null\n /** True once boot is done: never mount the boot region again. */\n private finalized = false\n /**\n * The scroll-region UI a UI session hands off to at {@link ready}, or `null` for a backend-only\n * session. Once set, every post-ready seam (`log`/`event`/`refresh`/`dispose`) delegates to it.\n */\n private handoff: ScrollRegionDevUi | null = null\n /** True while the persistent live region owns the terminal (backend-only session after ready). */\n private persistent = false\n private phase = ''\n private narration = ''\n private headerLines: string[] = []\n private logLines: string[] = []\n private footerLines: string[] = []\n\n constructor(deps: PersistentInkDevUiDeps) {\n this.verbose = deps.verbose ?? false\n this.renderFn = deps.render ?? (inkRender as RenderFn)\n this.stdout = deps.stdout ?? process.stdout\n this.appendLog = deps.appendLog\n this.now =\n deps.now ??\n ((): Date => {\n return new Date()\n })\n this.renderer = new DevRenderer({\n appendLog: deps.appendLog,\n verbose: this.verbose,\n isTTY: true,\n write: (text) => {\n this.stdout.write(text)\n },\n now: this.now,\n })\n }\n\n /** `LogFn`-shaped adapter for the build runner seam. */\n readonly logFn = (message: string, level: LogLevel = 'info'): void => {\n this.log(message, level)\n }\n\n // ---- boot (identical to InkDevUi) --------------------------------------\n\n /** Mount the Ink boot region (or rerender it in place) with the current phase + narration. */\n private renderBoot(readyLines?: string[]): void {\n const tree = <BootRegion phase={this.phase} narration={this.narration} readyLines={readyLines} />\n\n if (this.instance) {\n this.instance.rerender(tree)\n\n return\n }\n this.instance = this.renderFn(tree, { stdout: this.stdout, exitOnCtrlC: false, patchConsole: false })\n }\n\n /** Unmount Ink if mounted (idempotent). */\n private unmount(): void {\n if (this.instance) {\n this.instance.unmount()\n this.instance = null\n }\n }\n\n narrate(message: string): void {\n if (this.handoff) {\n this.handoff.narrate(message)\n\n return\n }\n this.renderer.teeOnly(message, 'info')\n if (this.persistent) {\n // Visible only in verbose (matches the plain renderer); tee already happened above.\n if (this.verbose) {\n this.logLines.push(message)\n this.rerenderPersistent()\n }\n\n return\n }\n if (this.finalized) {\n return\n }\n this.narration = message\n if (this.instance) {\n this.renderBoot()\n }\n }\n\n log(message: string, level: LogLevel = 'info'): void {\n if (this.handoff) {\n this.handoff.log(message, level)\n\n return\n }\n const willWrite = level !== 'debug' || this.verbose\n\n if (this.persistent) {\n // Never the embedded renderer's direct stdout write here \u2014 append to the live region instead.\n this.renderer.teeOnly(message, level)\n if (willWrite) {\n this.logLines.push(message)\n this.rerenderPersistent()\n }\n\n return\n }\n if (willWrite) {\n this.unmount()\n }\n this.renderer.log(message, level)\n }\n\n bootStep(phase: string): void {\n this.renderer.teeOnly(phase, 'info')\n if (this.finalized) {\n return\n }\n this.phase = phase\n this.renderBoot()\n }\n\n stopSpinner(): void {\n if (!this.persistent) {\n this.unmount()\n }\n }\n\n // ---- ready + persistence -----------------------------------------------\n\n ready(summary: ReadySummary): void {\n if (summary.hasUiChild) {\n this.readyHandoff(summary)\n\n return\n }\n this.readyPersistent(summary)\n }\n\n /**\n * UI session: unmount Ink and hand off to a {@link ScrollRegionDevUi}, which pins the ready header as\n * a scroll-region footer above the `turbo run dev` child's high-volume piped tail. From here on every\n * post-ready seam delegates to the handoff (see {@link log}/{@link event}/{@link refresh}/{@link dispose}).\n */\n private readyHandoff(summary: ReadySummary): void {\n this.finalized = true\n this.unmount()\n this.handoff = new ScrollRegionDevUi({ appendLog: this.appendLog, verbose: this.verbose, stdout: this.stdout })\n this.handoff.ready(summary)\n }\n\n /** Backend-only session: commit the header, tee it, mount the live footer, and STAY mounted. */\n private readyPersistent(summary: ReadySummary): void {\n this.headerLines = this.renderer.formatHeaderLines(summary)\n this.footerLines = this.renderer.formatFooterLines(summary)\n this.logLines = []\n for (const line of this.headerLines) {\n this.renderer.teeOnly(line, 'info')\n }\n this.finalized = true\n this.persistent = true\n // Re-clamp the footer when the terminal resizes. Guarded: an injected (test/piped) stream may not\n // be an EventEmitter TTY.\n if (typeof this.stdout.on === 'function') {\n this.stdout.on('resize', this.onResize)\n }\n this.rerenderPersistent()\n }\n\n /**\n * Cap for the pinned footer: it is a live region and cannot scroll, so it must never exceed the\n * viewport. Reserve rows for the scrolling log tail above it; fall back to an 80\u00D724 assumption when\n * the stream reports no row count (piped / test stdout).\n */\n private maxFooterLines(): number {\n return Math.max(3, (this.stdout.rows ?? 24) - 6)\n }\n\n /** Mount or repaint the live region from the current header / logs / footer snapshot. */\n private rerenderPersistent(): void {\n const tree = (\n <LiveRegion\n headerLines={this.headerLines}\n logLines={this.logLines}\n footerLines={this.footerLines}\n maxFooterLines={this.maxFooterLines()}\n />\n )\n\n if (this.instance) {\n this.instance.rerender(tree)\n\n return\n }\n this.instance = this.renderFn(tree, { stdout: this.stdout, exitOnCtrlC: false, patchConsole: false })\n }\n\n /** Re-clamp + repaint the footer on terminal resize (SIGWINCH), so a shrink never overflows the viewport. */\n private readonly onResize = (): void => {\n if (this.persistent) {\n this.rerenderPersistent()\n }\n }\n\n event(input: { tag: string; text: string }): void {\n if (this.handoff) {\n this.handoff.event(input)\n\n return\n }\n if (this.persistent) {\n const line = ` ${formatClock(this.now())} ${input.tag} ${input.text}`\n\n this.renderer.teeOnly(`${input.tag} ${input.text}`, 'info')\n this.logLines.push(line)\n this.rerenderPersistent()\n\n return\n }\n this.unmount()\n this.renderer.event(input)\n }\n\n /** Repaint the live footer in place from a fresh summary (header + logs untouched). No-op if not persistent. */\n refresh(summary: ReadySummary): void {\n if (this.handoff) {\n this.handoff.refresh(summary)\n\n return\n }\n // A session still in boot has no live footer to refresh.\n if (!this.persistent) {\n return\n }\n this.footerLines = this.renderer.formatFooterLines(summary)\n this.rerenderPersistent()\n }\n\n dispose(): void {\n if (this.handoff) {\n this.handoff.dispose()\n this.handoff = null\n\n return\n }\n if (this.persistent) {\n // Commit the last footer state, then release the terminal.\n this.rerenderPersistent()\n if (typeof this.stdout.off === 'function') {\n this.stdout.off('resize', this.onResize)\n }\n }\n this.finalized = true\n this.persistent = false\n this.unmount()\n }\n}\n", "import { Box, Static, Text } from 'ink'\nimport { useEffect, useState } from 'react'\n\n/**\n * The Ink boot region for `infra-kit dev`: an animated spinner + phase line while the server boots,\n * then the ready header committed to scrollback via `<Static>`.\n *\n * HARD INVARIANT \u2014 this component (and every boot component under `src/tui/dev-ui/`) is OUTPUT-ONLY.\n * It MUST NEVER call `useInput`. `useInput` arms Ink's raw mode + Ctrl-C interception, which would\n * steal SIGINT from the process signal handler that owns dev-server shutdown. The spinner animates via\n * a self-contained interval only; there is no keyboard input during boot. Do not add `useInput` here.\n */\n\nexport interface BootRegionProps {\n /** Current boot phase, shown beside the spinner. */\n phase: string\n /** Latest narration detail (dim subtitle under the spinner); empty hides the line. */\n narration: string\n /**\n * When set, boot is done: the pre-formatted ready-header lines are committed to scrollback via\n * `<Static>` and the transient spinner region is gone. The renderer unmounts right after.\n */\n readyLines?: string[]\n}\n\n/** Animated boot spinner + phase/narration, or the committed ready header once `readyLines` is set. */\nexport const BootRegion = (props: BootRegionProps) => {\n const { phase, narration, readyLines } = props\n\n const [frame, setFrame] = useState(0)\n\n useEffect(() => {\n // No spinner once the ready header is committed.\n if (readyLines) {\n return\n }\n\n const timer = setInterval(() => {\n setFrame((f) => {\n return f + 1\n })\n }, SPINNER_INTERVAL_MS)\n\n return () => {\n clearInterval(timer)\n }\n }, [readyLines])\n\n if (readyLines) {\n // `<Static>` writes each line once, above the (now empty) live region, so the header persists in\n // scrollback after unmount. Lines are pre-formatted by DevRenderer.formatReadyLines.\n return (\n <Static items={readyLines}>\n {(line, index) => {\n return <Text key={index}>{line}</Text>\n }}\n </Static>\n )\n }\n\n return (\n <Box flexDirection=\"column\">\n <Box>\n <Text color=\"cyan\">{SPINNER_FRAMES[frame % SPINNER_FRAMES.length]}</Text>\n <Text dimColor>{` ${phase}`}</Text>\n </Box>\n {narration ? <Text dimColor>{narration}</Text> : null}\n </Box>\n )\n}\n\nconst SPINNER_FRAMES = ['\u280B', '\u2819', '\u2839', '\u2838', '\u283C', '\u2834', '\u2826', '\u2827', '\u2807', '\u280F']\nconst SPINNER_INTERVAL_MS = 80\n", "import { Box, Static, Text } from 'ink'\n\n/**\n * The persistent Ink live region for `infra-kit dev` (backend-only sessions): the committed header +\n * growing log scrollback above, and a live health footer re-rendered in place below.\n *\n * HARD INVARIANT \u2014 this component (and every component under `src/tui/dev-ui/`) is OUTPUT-ONLY. It\n * MUST NEVER call `useInput`. `useInput` arms Ink's raw mode + Ctrl-C interception, which would steal\n * SIGINT from the process signal handler that owns dev-server shutdown. There is no keyboard input in\n * the live region \u2014 it is props only, with no state, no effects, no input. Do not add `useInput` here.\n */\n\nexport interface LiveRegionProps {\n /** Reference header, committed once to scrollback via `<Static>` (from `formatHeaderLines`). */\n headerLines: string[]\n /** Log lines committed above the footer as they arrive (also via the SAME `<Static>`). */\n logLines: string[]\n /** Live footer, re-rendered in place on every health change (from `formatFooterLines`). */\n footerLines: string[]\n /**\n * Cap on rendered footer rows so a many-backend session never eats a short viewport (the footer is a\n * live region \u2014 it cannot scroll). Overflow collapses into a trailing dim `\u2026 +N more` line. Omit for\n * no cap. The pinned footer must stay short; the full health picture is always in the log file.\n */\n maxFooterLines?: number\n}\n\n/**\n * A SINGLE `<Static>` streams the header then the growing logs (Ink writes only newly-appended items\n * each commit, so the header lands first and logs accrete beneath it \u2014 one block, never two). The\n * footer is an ordinary `<Box>`, so it repaints in place on each rerender without touching scrollback.\n */\nexport const LiveRegion = (props: LiveRegionProps) => {\n const { headerLines, logLines, footerLines } = props\n\n const staticItems = [...headerLines, ...logLines]\n const footer = clampFooter(footerLines, props.maxFooterLines)\n\n return (\n <>\n <Static items={staticItems}>\n {(line, index) => {\n return <Text key={index}>{line}</Text>\n }}\n </Static>\n <Box flexDirection=\"column\">\n {footer.map((line, index) => {\n return <Text key={index}>{line}</Text>\n })}\n </Box>\n </>\n )\n}\n\n/** Cap the footer to `max` rows, collapsing the overflow into one trailing ` \u2026 +N more` line. */\nexport const clampFooter = (lines: string[], max?: number): string[] => {\n if (max == null || max < 1 || lines.length <= max) {\n return lines\n }\n\n const shown = lines.slice(0, max - 1)\n\n return [...shown, ` \u2026 +${lines.length - shown.length} more`]\n}\n", "import process from 'node:process'\n\nimport type { DevUi, LogLevel, ReadySummary } from 'src/dev/dev-ui'\nimport { DevRenderer, formatClock } from 'src/dev/render'\nimport { SHOW_CURSOR, installRegion, moveTo, paintFooter, resetScrollRegion } from 'src/dev/scroll-region'\n\n/**\n * A {@link DevUi} that pins the `infra-kit dev` ready header to the bottom of the terminal as a STICKY\n * FOOTER using a DECSTBM scroll region, for sessions that run a `turbo run dev` UI child\n * (`summary.hasUiChild`). {@link PersistentInkDevUi} hands off to it at `ready`.\n *\n * Deliberately NO React / NO Ink. A UI session's tail is unbounded \u2014 every vite/HMR line the piped\n * child emits arrives as an `event` \u2014 and Ink's reconciler repaints its live region on each one. The\n * scroll region instead confines ALL scrolling (`log` + `event` lines) to the rows above the footer\n * using terminal escapes alone, so an append costs one `write` and the footer never repaints. See\n * {@link file://../../dev/scroll-region.ts}.\n *\n * The embedded {@link DevRenderer} is the single source of the file-tee format (`teeOnly`) and the\n * ready-header layout (`formatReadyLines`) \u2014 reused, never duplicated \u2014 exactly like the Ink UIs. Every\n * terminal write goes through the injected `stdout` so tests drive a fake stream.\n */\n\nexport interface ScrollRegionDevUiDeps {\n /** File tee \u2014 the same appender the runner hands every renderer. */\n appendLog: (text: string) => void\n /** Only in verbose does boot narration reach the terminal (it is always tee'd to the file). */\n verbose: boolean\n /** Target stream (default `process.stdout`). Its `isTTY`/`rows` gate the whole feature. */\n stdout?: NodeJS.WriteStream\n}\n\nexport class ScrollRegionDevUi implements DevUi {\n /** Embedded plain renderer: owns the file-tee format + the ready-header layout. Never drives a spinner. */\n private readonly renderer: DevRenderer\n private readonly stdout: NodeJS.WriteStream\n private readonly verbose: boolean\n /** The current footer block (the full ready header), recomputed on `ready`/`refresh`. */\n private footerLines: string[] = []\n /** True while a scroll region is installed and the footer is live (false when disabled / disposed). */\n private installed = false\n /** True once the resize listener is attached, so it is added at most once and removed exactly once. */\n private resizeBound = false\n\n constructor(deps: ScrollRegionDevUiDeps) {\n this.verbose = deps.verbose\n this.stdout = deps.stdout ?? process.stdout\n this.renderer = new DevRenderer({\n appendLog: deps.appendLog,\n verbose: this.verbose,\n // Colors follow the real stream: a TTY footer is colored; a piped / captured stream stays plain.\n isTTY: Boolean(this.stdout.isTTY),\n write: (text) => {\n this.stdout.write(text)\n },\n })\n }\n\n /** `LogFn`-shaped adapter for the build runner seam. */\n readonly logFn = (message: string, level: LogLevel = 'info'): void => {\n this.log(message, level)\n }\n\n /**\n * Row count, falling back to 24 (the classic terminal default) for any non-positive or absent value.\n *\n * `rows` is NOT merely `undefined` on a row-less stream: a pty allocated without a window size \u2014 what\n * `script(1)` gives us, and what some CI runners and terminal emulators report before their first\n * `SIGWINCH` \u2014 sets `isTTY === true` alongside `rows === 0`. A `??` fallback passes that `0` straight\n * through, `installRegion` then reads the terminal as \"too short\", and the footer silently never\n * installs on a terminal that is in fact perfectly tall enough.\n */\n private rows(): number {\n const reported = this.stdout.rows\n\n return reported != null && reported > 0 ? reported : 24\n }\n\n /** Write each line as its own terminal row (used for the non-TTY / too-short plain fallback). */\n private writeLines(lines: readonly string[]): void {\n for (const line of lines) {\n this.stdout.write(`${line}\\n`)\n }\n }\n\n narrate(message: string): void {\n this.renderer.teeOnly(message, 'info')\n if (this.verbose) {\n this.stdout.write(`${message}\\n`)\n }\n }\n\n /** A general message: lands inside the scroll region and scrolls normally, plus a file tee. */\n log(message: string, level: LogLevel = 'info'): void {\n if (level !== 'debug' || this.verbose) {\n this.stdout.write(`${message}\\n`)\n }\n this.renderer.teeOnly(message, level)\n }\n\n /** Boot narration only \u2014 no spinner (a spinner would fight the child for the cursor). */\n bootStep(phase: string): void {\n this.narrate(phase)\n }\n\n /** No-op: this UI never runs a spinner. */\n stopSpinner(): void {\n // Nothing to stop.\n }\n\n /**\n * Install (or re-install) the scroll region and paint the footer for the CURRENT terminal size.\n * Returns `false` when the terminal is too short to host both a region and the footer \u2014 the caller\n * then falls back to a one-shot plain print (or disposes, on a shrink).\n */\n private applyRegion(): boolean {\n const rows = this.rows()\n const region = installRegion(this.footerLines.length, rows)\n\n if (region === '') return false\n this.stdout.write(`${region}${paintFooter(this.footerLines, rows)}`)\n\n return true\n }\n\n /** Attach the resize listener once (guarded \u2014 an injected stream may not be an EventEmitter TTY). */\n private bindResize(): void {\n if (this.resizeBound || typeof this.stdout.on !== 'function') return\n this.stdout.on('resize', this.onResize)\n this.resizeBound = true\n }\n\n ready(summary: ReadySummary): void {\n this.footerLines = this.renderer.formatReadyLines(summary)\n for (const line of this.footerLines) {\n this.renderer.teeOnly(line, 'info')\n }\n\n // Not a real terminal (piped / test capture): just print the header once, no region.\n if (!this.stdout.isTTY) {\n this.writeLines(this.footerLines)\n\n return\n }\n\n // Too short for a region \u2192 print plainly and leave the feature disabled (installed stays false).\n if (!this.applyRegion()) {\n this.writeLines(this.footerLines)\n\n return\n }\n this.installed = true\n this.bindResize()\n }\n\n /** One tagged, timestamped tail line \u2014 scrolls inside the region like any other `log`. */\n event(input: { tag: string; text: string }): void {\n const line = ` ${formatClock(new Date())} ${input.tag} ${input.text}`\n\n this.stdout.write(`${line}\\n`)\n this.renderer.teeOnly(`${input.tag} ${input.text}`, 'info')\n }\n\n /** Recompute the footer and repaint it in place \u2014 nothing else is reprinted. No-op when not installed. */\n refresh(summary: ReadySummary): void {\n this.footerLines = this.renderer.formatReadyLines(summary)\n if (!this.installed) return\n this.stdout.write(paintFooter(this.footerLines, this.rows()))\n }\n\n /**\n * Re-clamp on terminal resize (SIGWINCH, surfaced as the stream's `resize` event): re-run the region\n * install + repaint for the new size. If the new size is too short to host the footer, dispose\n * gracefully rather than paint a broken region.\n */\n private readonly onResize = (): void => {\n if (!this.installed) return\n if (!this.applyRegion()) {\n this.dispose()\n }\n }\n\n /**\n * Release the terminal: drop the resize listener, reset the scroll region, drop the cursor onto the\n * last row with a fresh newline, and restore the cursor. Idempotent and safe if never installed \u2014 the\n * region escapes are emitted only while a region is actually live, so a second call (or a disposal of\n * a never-installed instance) writes nothing and leaves the terminal usable.\n */\n dispose(): void {\n if (this.resizeBound && typeof this.stdout.off === 'function') {\n this.stdout.off('resize', this.onResize)\n this.resizeBound = false\n }\n if (this.installed) {\n const rows = this.rows()\n\n this.stdout.write(`${resetScrollRegion()}${moveTo(rows, 1)}\\n${SHOW_CURSOR}`)\n this.installed = false\n }\n }\n}\n", "/**\n * Pure, side-effect-free ANSI builders for a terminal SCROLL REGION (DECSTBM) sticky footer.\n *\n * `infra-kit dev` uses these to pin the ready-header block to the bottom of the terminal while a\n * `turbo run dev` UI child owns the same TTY via inherited stdio. DECSTBM (`ESC [ top ; bottom r`)\n * confines normal scrolling \u2014 from both the runner's own writes AND the inherited child's raw bytes \u2014\n * to the region ABOVE the footer, so the footer rows are never scrolled away. That is why this works\n * where a piping approach could not: the child keeps inherited stdio and needs no per-framework flags.\n *\n * Every function here is a pure string builder: no I/O, no `process` access, no terminal queries. The\n * caller ({@link file://../tui/dev-ui/scroll-region-dev-ui.ts}) owns all writes, the row count, and the\n * TTY guard. Each builder is unit-tested with exact-string assertions.\n */\n\n/** Save the cursor position (DECSC). Paired with {@link RESTORE_CURSOR} around a footer repaint. */\nexport const SAVE_CURSOR = '\\x1B7'\n/** Restore the cursor position saved by {@link SAVE_CURSOR} (DECRC). */\nexport const RESTORE_CURSOR = '\\x1B8'\n/** Erase the entire current line without moving the cursor (EL 2), so a repaint leaves no stale glyphs. */\nexport const CLEAR_LINE = '\\x1B[2K'\n/** Hide the cursor (DECTCEM off) \u2014 avoids a cursor flicker while the footer repaints. */\nexport const HIDE_CURSOR = '\\x1B[?25l'\n/** Show the cursor (DECTCEM on) \u2014 restored on teardown so the terminal is left usable. */\nexport const SHOW_CURSOR = '\\x1B[?25h'\n\n/**\n * Set the vertical scroll region (DECSTBM) to lines `top..bottom` inclusive, 1-indexed. Scrolling is\n * thereafter confined to that band; the rows below `bottom` become a static footer area.\n */\nexport const setScrollRegion = (top: number, bottom: number): string => {\n return `\\x1B[${top};${bottom}r`\n}\n\n/** Reset the scroll region to the full screen (DECSTBM with no params) \u2014 the teardown counterpart. */\nexport const resetScrollRegion = (): string => {\n return '\\x1B[r'\n}\n\n/** Absolute cursor move (CUP) to `row`,`col`, both 1-indexed. */\nexport const moveTo = (row: number, col: number): string => {\n return `\\x1B[${row};${col}H`\n}\n\n/**\n * Scroll existing content up by `n` blank lines, then return the cursor to its starting row, so the\n * bottom `n` rows are cleared for a footer WITHOUT overwriting whatever was already on screen. Emits\n * `n` newlines (which scroll the viewport) followed by cursor-up-`n` (CUU). Returns `''` for `n <= 0`.\n */\nexport const reserveRows = (n: number): string => {\n if (n <= 0) return ''\n\n return `${'\\n'.repeat(n)}\\x1B[${n}A`\n}\n\n/**\n * Paint `lines` into the bottom `lines.length` rows of a `rows`-tall terminal, cursor-safe: save the\n * cursor, absolutely position + clear + write each line (top-most footer row first), then restore the\n * cursor so surrounding scroll output is undisturbed. Returns `''` when there are no lines to paint.\n */\nexport const paintFooter = (lines: readonly string[], rows: number): string => {\n if (lines.length === 0) return ''\n\n const base = rows - lines.length + 1\n const painted = lines\n .map((line, i) => {\n return `${moveTo(base + i, 1)}${CLEAR_LINE}${line}`\n })\n .join('')\n\n return `${SAVE_CURSOR}${painted}${RESTORE_CURSOR}`\n}\n\n/**\n * Reserve `footerHeight` rows at the bottom and confine scrolling to the rows above them: emit\n * {@link reserveRows} then a {@link setScrollRegion} of `1..rows - footerHeight`. Returns `''` when the\n * terminal is too short to host both a scroll region and the footer (`rows - footerHeight < 1`); the\n * caller MUST treat `''` as \"skip the whole feature\" and fall back to a one-shot plain print.\n */\nexport const installRegion = (footerHeight: number, rows: number): string => {\n if (rows - footerHeight < 1) return ''\n\n return `${reserveRows(footerHeight)}${setScrollRegion(1, rows - footerHeight)}`\n}\n"],
5
- "mappings": "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",
6
- "names": ["inkRender", "process", "Box", "Static", "Text", "useEffect", "useState", "jsx", "jsxs", "BootRegion", "props", "phase", "narration", "readyLines", "frame", "setFrame", "timer", "f", "SPINNER_INTERVAL_MS", "line", "index", "SPINNER_FRAMES", "Box", "Static", "Text", "Fragment", "jsx", "jsxs", "LiveRegion", "props", "headerLines", "logLines", "footerLines", "staticItems", "footer", "clampFooter", "line", "index", "lines", "max", "shown", "process", "CLEAR_LINE", "SHOW_CURSOR", "setScrollRegion", "top", "bottom", "resetScrollRegion", "moveTo", "row", "col", "reserveRows", "n", "paintFooter", "lines", "rows", "base", "line", "i", "CLEAR_LINE", "installRegion", "footerHeight", "ScrollRegionDevUi", "deps", "process", "DevRenderer", "text", "message", "level", "reported", "lines", "line", "phase", "rows", "region", "installRegion", "paintFooter", "summary", "input", "formatClock", "resetScrollRegion", "moveTo", "SHOW_CURSOR", "jsx", "PersistentInkDevUi", "deps", "inkRender", "process", "DevRenderer", "text", "message", "level", "readyLines", "tree", "BootRegion", "willWrite", "phase", "summary", "ScrollRegionDevUi", "line", "LiveRegion", "input", "formatClock"]
7
- }