infra-kit 0.1.130 → 0.1.132
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/boot-SG7525LE.js +2 -0
- package/dist/boot-SG7525LE.js.map +7 -0
- package/dist/chunk-6EPRH34O.js +2 -0
- package/dist/chunk-6EPRH34O.js.map +7 -0
- package/dist/chunk-6FU2TRU5.js +2 -0
- package/dist/chunk-6FU2TRU5.js.map +7 -0
- package/dist/chunk-CHETBZ6M.js +3 -0
- package/dist/{chunk-V375VXOL.js.map → chunk-CHETBZ6M.js.map} +4 -4
- package/dist/chunk-GQHUWUJL.js +220 -0
- package/dist/chunk-GQHUWUJL.js.map +7 -0
- package/dist/chunk-IGTHMZPR.js +2 -0
- package/dist/chunk-IGTHMZPR.js.map +7 -0
- package/dist/chunk-JD5T4VCB.js +6 -0
- package/dist/chunk-JD5T4VCB.js.map +7 -0
- package/dist/chunk-LSEYIAWK.js +2 -0
- package/dist/chunk-LSEYIAWK.js.map +7 -0
- package/dist/chunk-LTKR3F4N.js +2 -0
- package/dist/chunk-LTKR3F4N.js.map +7 -0
- package/dist/chunk-RSF5XAQJ.js +4 -0
- package/dist/chunk-RSF5XAQJ.js.map +7 -0
- package/dist/chunk-RXEMP3VI.js +4 -0
- package/dist/chunk-RXEMP3VI.js.map +7 -0
- package/dist/chunk-V37MZDFR.js +3 -0
- package/dist/chunk-V37MZDFR.js.map +7 -0
- package/dist/cli.js +10 -19
- package/dist/cli.js.map +4 -4
- package/dist/dev-server.js +25 -11
- package/dist/dev-server.js.map +4 -4
- package/dist/dev-wizard-run-TNA5YI7P.js +2 -0
- package/dist/dev-wizard-run-TNA5YI7P.js.map +7 -0
- package/dist/lib/release-slug/release-slug.d.ts +26 -2
- package/dist/lib/vite/vite.d.ts +41 -9
- package/dist/mcp.js +1 -1
- package/dist/mcp.js.map +3 -3
- package/dist/persistent-ink-dev-ui-LRTJBSCH.js +8 -0
- package/dist/persistent-ink-dev-ui-LRTJBSCH.js.map +7 -0
- package/dist/update-check.js +2 -0
- package/dist/update-check.js.map +7 -0
- package/dist/vite.js +1 -1
- package/package.json +5 -2
- package/dist/boot-SGM5RVLJ.js +0 -2
- package/dist/boot-SGM5RVLJ.js.map +0 -7
- package/dist/chunk-2PZRQHWF.js +0 -2
- package/dist/chunk-2PZRQHWF.js.map +0 -7
- package/dist/chunk-4YU4TAGX.js +0 -3
- package/dist/chunk-4YU4TAGX.js.map +0 -7
- package/dist/chunk-AP63Y7KE.js +0 -159
- package/dist/chunk-AP63Y7KE.js.map +0 -7
- package/dist/chunk-Q36WX5RP.js +0 -4
- package/dist/chunk-Q36WX5RP.js.map +0 -7
- package/dist/chunk-V375VXOL.js +0 -5
- package/dist/chunk-YGCVOVNX.js +0 -2
- package/dist/chunk-YGCVOVNX.js.map +0 -7
- package/dist/dev-wizard-run-LYNLPQMV.js +0 -2
- package/dist/dev-wizard-run-LYNLPQMV.js.map +0 -7
|
@@ -0,0 +1,2 @@
|
|
|
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
|
|
@@ -0,0 +1,7 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{a as v,b as x,c as y}from"./chunk-LTKR3F4N.js";import{Buffer as K}from"node:buffer";import{execFileSync as L}from"node:child_process";import g from"node:fs";import O from"node:net";import u from"node:path";import p from"node:process";import{pathToFileURL as _}from"node:url";import{z as l}from"zod";import{z as i}from"zod";var P=i.strictObject({requiredScripts:i.array(i.string().min(1)).optional(),requiredFiles:i.array(i.string().min(1)).optional(),turbo:i.strictObject({requiredTasks:i.array(i.string().min(1)).optional()}).optional(),dev:i.strictObject({proxy:i.strictObject({templates:i.strictObject({local:i.string().min(1),cloud:i.string().min(1)}),routes:i.record(i.string().min(1),i.strictObject({packageName:i.string().min(1),from:i.array(i.enum(["local","cloud"])).min(1),default:i.enum(["local","cloud"]).optional()}).refine(e=>e.from.length<=1||e.default!==void 0,{message:"default is required when `from` has more than one source"}).refine(e=>e.default===void 0||e.from.includes(e.default),{message:"default must be listed in `from`"}))}).optional()}).optional()});var R="INFRA_KIT_ENV",k="infra-kit.config.ts",N=u.join(".infra-kit","dev-context"),T=u.join(".infra-kit","dev-context.json"),D=l.object({package:l.string(),port:l.number(),pid:l.number().optional(),writtenAt:l.number().optional(),release:l.string().optional(),alias:l.string().optional(),proxyPort:l.number().optional(),origin:l.string().optional()}),j=(e,t)=>{let r=t?.username??e.E2E__BASIC_AUTH_USERNAME,n=t?.password??e.E2E__BASIC_AUTH_PASSWORD;return!r||!n?void 0:`Basic ${K.from(`${r}:${n}`).toString("base64")}`};var b=(e,t)=>e.replaceAll("<release>",t.release).replaceAll("<packageName>",t.packageName).replaceAll("<env>",t.env),C=80,F="127.0.0.1",S=(e,t)=>{if(t==null||t===C)return e;let r;try{r=new URL(e)}catch{throw new Error(`infra-kit/vite: dev.proxy templates.local resolved to "${e}", which is not a valid URL, so the proxy port ${t} cannot be applied.`)}if(r.port!=="")return e;r.port=String(t);let n=r.toString();return!e.endsWith("/")&&n.endsWith("/")?n.slice(0,-1):n},U=1024,V=(e,t)=>t!=null&&t>=U?S(e.replace(/^https:\/\//iu,"http://"),t):S(e,t),M=e=>{let{route:t,templates:r,env:n,getRelease:a,info:o}=e;if(o?.origin)return o.origin;let s=b(r.local,{release:o?.release??a(),packageName:v(t.packageName),env:n??""});return V(s,o?.proxyPort)},H=(e,t)=>{let r=e.default??e.from[0];return e.from.includes("local")&&t.has(e.packageName)?"local":r},$=new Set(["localhost","127.0.0.1","::1"]),B=e=>{let t;try{t=new URL(e).hostname.toLowerCase()}catch{return!1}let r=t.startsWith("[")&&t.endsWith("]")?t.slice(1,-1):t;return r.endsWith(".localhost")||$.has(r)},W=({routePath:e,route:t,templates:r,localSet:n,env:a,getRelease:o,localInfo:s})=>{if(H(t,n)==="local"){let f=M({route:t,templates:r,env:a,getRelease:o,info:s?.get(t.packageName)});return B(f)?{target:f,changeOrigin:!0,secure:!1}:{target:f,changeOrigin:!0}}if(!a)throw new Error(`infra-kit/vite: proxy route "${e}" resolves to a cloud backend but ${R} is not set. Source an environment from Doppler first (e.g. \`infra-kit env-load -c dev\`).`);return{target:b(r.cloud,{release:"",packageName:t.packageName,env:a}),changeOrigin:!0,secure:!1,cookieDomainRewrite:"localhost"}},J=({proxy:e,localSet:t,env:r,getRelease:n,authHeader:a,localInfo:o})=>{let s={},c=a?{Authorization:a}:void 0;for(let[d,f]of Object.entries(e.routes)){let m=W({routePath:d,route:f,templates:e.templates,localSet:t,env:r,getRelease:n,localInfo:o});s[d]=c?{...m,headers:c}:m}return s},G=e=>{let t;return()=>(t??={value:e()},t.value)},q=async e=>{let t=u.join(e,k);if(!g.existsSync(t))return;let r=g.statSync(t),o=(await import(`${_(t).href}?mtime=${Number(r.mtimeMs)}`)).default;if(o===void 0)return;let s=typeof o=="function"?await o():o,c=P.safeParse(s);if(!c.success)throw new Error(`infra-kit/vite: invalid ${k} at ${t}: ${l.prettifyError(c.error)}`);return c.data.dev},z=e=>{if(Array.isArray(e))return e.filter(t=>typeof t=="string");if(e!==null&&typeof e=="object"){let t=e.packages??e.localPackages;if(Array.isArray(t))return t.filter(r=>typeof r=="string")}return[]},I=(e,t)=>{let r=u.resolve(e);for(;;){let n=u.join(r,t);if(g.existsSync(n))return n;let a=u.dirname(r);if(a===r)return;r=a}},h=()=>({packages:new Set,info:new Map}),X=e=>{if(e==null)return!0;try{return p.kill(e,0),!0}catch(t){return t.code==="EPERM"}},Y=e=>{let t;try{t=g.readdirSync(e)}catch{return h()}let r=new Set,n=new Map;for(let a of t)if(a.endsWith(".json"))try{let o=D.safeParse(JSON.parse(g.readFileSync(u.join(e,a),"utf-8")));if(!o.success||!X(o.data.pid))continue;r.add(o.data.package),n.set(o.data.package,{port:o.data.port,origin:o.data.origin,release:o.data.release,alias:o.data.alias,proxyPort:o.data.proxyPort})}catch{continue}return{packages:r,info:n}},Q=e=>{let t=I(e,N);if(t)return Y(t);let r=I(e,T);if(!r)return h();try{return{packages:new Set(z(JSON.parse(g.readFileSync(r,"utf-8")))),info:new Map}}catch{return h()}};var Z=e=>L("git",["rev-parse","--abbrev-ref","HEAD"],{cwd:e,encoding:"utf-8"}).trim(),ee=()=>new Promise((e,t)=>{let r=O.createServer();r.unref(),r.on("error",t),r.listen(0,"127.0.0.1",()=>{let n=r.address(),a=typeof n=="object"&&n!==null?n.port:0;r.close(()=>e(a))})}),te="INFRA_KIT_UI_PORTS",re=e=>{try{let t=JSON.parse(g.readFileSync(u.join(e,"package.json"),"utf-8"));return typeof t.name=="string"?t.name:null}catch{return null}},ne=e=>{let t=p.env[te];if(t==null||t==="")return null;let r=re(e);if(r==null)return null;try{let n=JSON.parse(t)[r];if(typeof n=="number")return{port:n};if(n===null||typeof n!="object")return null;let{port:a,alias:o}=n;return typeof a!="number"?null:{port:a,alias:typeof o=="string"?o:void 0}}catch{return null}},oe=443,ae=async(e={})=>{let t=e.cwd??p.cwd();if(e.command==="build")return{proxy:{}};let r=e.port==null?ne(t):null,n=e.port??r?.port??await ee(),a=r!=null,o=e.host??F,s=r?.alias==null?void 0:{protocol:"wss",host:r.alias,clientPort:oe},c={port:n,host:o,...a?{strictPort:a}:{},...s?{hmr:s}:{}},d=await q(t);if(!d?.proxy)return{...c,proxy:{}};let{packages:f,info:m}=Q(t),w=p.env[R],A=j(p.env,e.basicAuth),E=G(()=>{try{return x(Z(t))||y}catch{return y}});return{...c,proxy:J({proxy:d.proxy,localSet:f,env:w,getRelease:E,authHeader:A,localInfo:m})}},ve=async(e={})=>(await ae(e)).proxy;export{P as a,J as b,q as c,ae as d,ve as e};
|
|
2
|
+
//# sourceMappingURL=chunk-6EPRH34O.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/lib/vite/vite.ts", "../src/lib/package-config/package-config-schema.ts"],
|
|
4
|
+
"sourcesContent": ["import { Buffer } from 'node:buffer'\nimport { execFileSync } from 'node:child_process'\nimport fs from 'node:fs'\nimport net from 'node:net'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { pathToFileURL } from 'node:url'\nimport { z } from 'zod'\n\nimport type {\n InfraKitDev,\n InfraKitDevProxy,\n InfraKitDevProxyRoute,\n InfraKitDevProxySource,\n} from '../package-config/package-config'\nimport { packageConfigSchema } from '../package-config/package-config-schema'\nimport { DEFAULT_RELEASE_SLUG, slugifyHostLabel, slugifyRelease } from '../release-slug/release-slug'\n\n/**\n * Env-name handle read at vite-config time to fill the `<env>` placeholder in a\n * cloud proxy target. Inlined (not imported from `src/lib/constants`) to keep the\n * `infra-kit/vite` bundle's import graph tiny \u2014 see the module header. Kept\n * byte-identical to `INFRA_KIT_ENV_VAR` in `src/lib/constants/constants.ts`.\n */\nconst INFRA_KIT_ENV = 'INFRA_KIT_ENV'\n\n/** Per-package config filename, mirrored from the CLI's `PACKAGE_CONFIG_FILE`. */\nconst PACKAGE_CONFIG_FILE = 'infra-kit.config.ts'\n\n/**\n * Repo-relative dev-context fragment DIRECTORY, searched upward from cwd. Each\n * runner (single-process or cmux pane) writes its OWN `<app>.json` fragment here\n * recording its real bound port + release; the helper merges them (see\n * {@link readLocalContext}). This is the current source of truth.\n */\nconst DEV_CONTEXT_DIR = path.join('.infra-kit', 'dev-context')\n\n/**\n * Legacy single-file dev-context manifest, searched upward from cwd. Read only as\n * a transitional back-compat path when {@link DEV_CONTEXT_DIR} is absent (the\n * directory wins per-package when both exist).\n */\nconst DEV_CONTEXT_FILE = path.join('.infra-kit', 'dev-context.json')\n\n/**\n * One `.infra-kit/dev-context/<app>.json` fragment. `package` is the load-bearing\n * field the helper reads (\u2192 localSet); `origin` is **the authoritative local target** \u2014\n * the exact origin the runner registered, obeyed verbatim by {@link resolveLocalTarget}.\n * The runner is the only party that knows the scheme and port its alias is really served\n * on, so the helper never re-derives them when `origin` is present.\n *\n * `release`, `alias` and `proxyPort` are the LEGACY inputs, read only when `origin` is\n * absent \u2014 which can only happen when an OLD CLI wrote the fragment (a CLI-only rollback).\n * `port` is no longer a proxy target (the direct `127.0.0.1:<port>` branch is gone) \u2014 it\n * survives as diagnostic provenance. `pid` is the staleness guard: {@link readFragmentDir}\n * drops a fragment whose writer is no longer alive, so a crashed runner cannot keep a\n * package pinned `local` at a dead alias. `writtenAt` is provenance only.\n *\n * Kept lenient (not `.strict()`) so extra writer fields never reject a valid fragment, and\n * deliberately un-`refine`d: a rejected fragment would drop a started package to `cloud`\n * instead of falling into legacy mode, which is what makes the rollback path work.\n */\nconst devContextFragmentSchema = z.object({\n package: z.string(),\n port: z.number(),\n pid: z.number().optional(),\n writtenAt: z.number().optional(),\n release: z.string().optional(),\n alias: z.string().optional(),\n proxyPort: z.number().optional(),\n origin: z.string().optional(),\n})\n\n/**\n * A single Vite `server.proxy` entry. `changeOrigin` is always set. `cookieDomainRewrite` is cloud-only\n * (it keeps an HTTPS cloud BE's cookies usable from a local FE). `secure: false` appears on BOTH sources\n * but for different reasons: a cloud target may serve a cert the local store rejects, and a local target\n * is served by portless from its own private CA \u2014 set there only for a loopback host (see\n * {@link isLoopbackTarget}). `headers` is present only when HTTP Basic Auth is injected (see\n * {@link buildBasicAuthHeader}) \u2014 it carries the `Authorization` header applied\n * uniformly to every route so upstream environments behind auth (e2e/staging)\n * stay reachable.\n */\nexport interface InfraKitViteProxyEntry {\n target: string\n changeOrigin: true\n secure?: false\n cookieDomainRewrite?: 'localhost'\n headers?: Record<string, string>\n}\n\n/** A Vite `server.proxy`-shaped map: path-prefix \u2192 proxy entry. */\nexport type InfraKitViteProxy = Record<string, InfraKitViteProxyEntry>\n\n/** Explicit HTTP Basic Auth credentials, overriding the `E2E__BASIC_AUTH_*` env vars. */\nexport interface InfraKitBasicAuth {\n username: string\n password: string\n}\n\nexport interface InfraKitDevOptions {\n /** Package dir whose `infra-kit.config.ts` is loaded. Defaults to `process.cwd()`. */\n cwd?: string\n /**\n * Explicit HTTP Basic Auth credentials injected into every proxy route as an\n * `Authorization` header. Takes precedence over the `E2E__BASIC_AUTH_USERNAME`\n * / `E2E__BASIC_AUTH_PASSWORD` env vars. Omit to use the env-based default.\n */\n basicAuth?: InfraKitBasicAuth\n /**\n * Vite's `command`. Pass it (`defineConfig(async ({ command }) => ({ server: await infraKitDev({ command }) }))`)\n * so `build` is a no-op: `server` is irrelevant to a build and proxy resolution would otherwise\n * fail-fast on a cloud route with no sourced env. Omit (or `'serve'`) for the dev-server config.\n */\n command?: 'build' | 'serve'\n /**\n * Explicit dev-server port. Omit for a **per-worktree dynamic** free port (a fresh OS-assigned\n * port) so N simultaneous git worktrees never collide on Vite's default `5173`; Vite prints the\n * chosen URL. Pass a fixed number only when an external contract pins the port.\n */\n port?: number\n /**\n * Interface the dev server binds. Defaults to {@link LOOPBACK_V4} so a portless alias can reach it\n * (Vite's own `localhost` default binds `[::1]` only, which the proxy cannot dial). Override when\n * the dev server must be reachable off the loopback \u2014 `'0.0.0.0'` or `true` inside a container, on\n * a LAN, or for an e2e runner on another host.\n */\n host?: string | boolean\n}\n\n/**\n * Build the `Authorization: Basic <base64(user:pass)>` header value. Credentials\n * come from `override` when given, else from `E2E__BASIC_AUTH_USERNAME` /\n * `E2E__BASIC_AUTH_PASSWORD` (NOTE: double underscore). Returns `undefined` when\n * either half is missing, so callers add no `headers` key at all.\n */\nconst buildBasicAuthHeader = (env: NodeJS.ProcessEnv, override?: InfraKitBasicAuth): string | undefined => {\n const username = override?.username ?? env.E2E__BASIC_AUTH_USERNAME\n const password = override?.password ?? env.E2E__BASIC_AUTH_PASSWORD\n\n if (!username || !password) return undefined\n\n const encoded = Buffer.from(`${username}:${password}`).toString('base64')\n\n return `Basic ${encoded}`\n}\n\n/**\n * Re-exported from the shared, dependency-light `lib/release-slug` module so the\n * published `infra-kit/vite` surface (`entry/vite.ts:7`) is unchanged while the\n * dev-server's fragment writer derives `<release>` from the SAME implementation\n * (no slug drift). See {@link slugifyRelease}.\n */\nexport { slugifyRelease }\n\n/** Fill the `<release>`/`<packageName>`/`<env>` placeholders in a URL template. */\nconst interpolate = (template: string, values: { release: string; packageName: string; env: string }): string => {\n return template\n .replaceAll('<release>', values.release)\n .replaceAll('<packageName>', values.packageName)\n .replaceAll('<env>', values.env)\n}\n\n/** The port a bare `http://` target implies, and the proxy's default \u2014 never spelled out in a URL. */\nconst DEFAULT_HTTP_PORT = 80\n\n/**\n * The interface the vite dev server binds. Pinned to IPv4 loopback because portless dials its routes\n * on `127.0.0.1`, while vite's default `host: 'localhost'` combined with its\n * `dns.setDefaultResultOrder('verbatim')` binds `[::1]` ONLY \u2014 the alias then resolves, the proxy\n * connects, and the connection is refused, surfacing as a 502 on every UI request. Backends never hit\n * this because `ServerlessLocalRun` already binds `127.0.0.1`. Still loopback-only: this narrows the\n * address family, it does not expose the dev server to the network.\n */\nconst LOOPBACK_V4 = '127.0.0.1'\n\n/**\n * Graft the proxy's listen port onto an interpolated local target. The `dev.proxy` local template is\n * written port-free (`http://<release>.<packageName>.localhost`) because the proxy defaults to `:80`.\n * A consumer who trades the clean URL for a zero-sudo unprivileged port still needs the frontend to\n * reach it, so the runner records the port it actually bound and we append it here. A template that\n * already pins its own port keeps it.\n */\nconst withProxyPort = (target: string, proxyPort: number | undefined): string => {\n if (proxyPort == null || proxyPort === DEFAULT_HTTP_PORT) return target\n\n let url: URL\n\n try {\n url = new URL(target)\n } catch {\n throw new Error(\n `infra-kit/vite: dev.proxy templates.local resolved to \"${target}\", which is not a valid URL, so the proxy port ${proxyPort} cannot be applied.`,\n )\n }\n\n if (url.port !== '') return target\n url.port = String(proxyPort)\n\n // `URL.toString()` synthesizes a `/` pathname for an authority-only URL, so the common port-free\n // template would come back as `\u2026localhost:1355/`. Strip that ONLY when the template had no trailing\n // slash of its own \u2014 a template ending in `/` (e.g. `\u2026localhost/base/`) means it, and silently\n // trimming it would change the path the frontend proxies to.\n const grafted = url.toString()\n\n return !target.endsWith('/') && grafted.endsWith('/') ? grafted.slice(0, -1) : grafted\n}\n\n/**\n * The lowest port a daemon can bind without root. An origin-less fragment recording a port AT OR ABOVE\n * this was written by an old CLI whose `spawnDaemon` always passed `--no-tls`, so infra-kit itself could\n * only ever have put PLAIN HTTP there. Below it (80/443) the daemon was installed out-of-band, where\n * portless defaults to TLS ON \u2014 which is precisely why the legacy scheme is left alone down there.\n */\nconst LEGACY_SELF_SPAWNED_PORT_FLOOR = 1024\n\n/**\n * Legacy target resolution \u2014 reached ONLY for an origin-less fragment, i.e. one written by an OLD CLI\n * (a CLI-only rollback). It must reproduce the old helper rather than reject anything, because that is\n * what lets the CLI be reverted without touching a single consumer repo.\n *\n * `proxyPort >= 1024`: force the scheme to `http`. **Mitigation, not a proof** \u2014 infra-kit's own\n * `spawnDaemon` always passed `--no-tls`, so an unprivileged daemon *infra-kit created* was always plain\n * HTTP; a HAND-STARTED TLS daemon on such a port (portless's own default is TLS) would be wrongly\n * downgraded here. That corner is accepted: forcing `http` reproduces what the old CLI + old helper\n * already emitted, so it regresses nothing that worked.\n *\n * `proxyPort < 1024` (80/443) or absent: the daemon was installed out-of-band and may well be TLS, and we\n * cannot know. Reproduce the old helper verbatim and NEVER rewrite the scheme \u2014 forcing `http` on a `:443`\n * fragment would emit `http://<alias>:443`, plain HTTP into a TLS listener, the exact silent failure this\n * design exists to remove.\n */\nconst resolveLegacyTarget = (target: string, proxyPort: number | undefined): string => {\n if (proxyPort != null && proxyPort >= LEGACY_SELF_SPAWNED_PORT_FLOOR) {\n return withProxyPort(target.replace(/^https:\\/\\//iu, 'http://'), proxyPort)\n }\n\n return withProxyPort(target, proxyPort)\n}\n\n/**\n * The proxy target for a route resolved to `local`. The runner PUBLISHES the contract and the helper OBEYS\n * it: when the fragment carries an `origin`, that string is the target verbatim \u2014 the runner is the only\n * party that knows the scheme and port the alias it registered is actually served on, and this helper ships\n * on its own release cadence, so any re-derivation here is a guess that can silently disagree.\n *\n * `templates.local` is consulted ONLY in legacy mode (see {@link resolveLegacyTarget}).\n *\n * The target proxies to a stable HOSTNAME, and the runner re-points that alias at the freshly-bound port\n * on every `--watch` restart, so it self-heals without Vite reloading its config.\n */\nconst resolveLocalTarget = (args: {\n route: InfraKitDevProxyRoute\n templates: InfraKitDevProxy['templates']\n env: string | undefined\n getRelease: () => string\n info: LocalPackageInfo | undefined\n}): string => {\n const { route, templates, env, getRelease, info } = args\n\n if (info?.origin) return info.origin\n\n const target = interpolate(templates.local, {\n // Prefer THIS package's runner-recorded release slug (R4) over the single global git derivation,\n // so cross-branch FE/BE pairings don't drift the emitted `<release>` from the segment the runner\n // aliased. Fall back to the global git slug only when the fragment carried no release.\n release: info?.release ?? getRelease(),\n // The local template resolves to a `<release>.<packageName>.localhost` alias the dev-server\n // registered with portless \u2014 which slugifies the package name to a legal DNS label. Slugify\n // identically here or a scoped name (`@hulyo/client-ui`) emits a target that no alias backs.\n packageName: slugifyHostLabel(route.packageName),\n env: env ?? '',\n })\n\n return resolveLegacyTarget(target, info?.proxyPort)\n}\n\n/**\n * Pick the effective source for a route: `local` when the route lists `local` as\n * a capability AND its packageName is in the local dev set; otherwise the fallback\n * \u2014 the declared `default` for a multi-source route, or the sole `from` entry for\n * a single-source one. Always resolves (the schema guarantees a usable fallback).\n */\nconst pickSource = (route: InfraKitDevProxyRoute, localSet: ReadonlySet<string>): InfraKitDevProxySource => {\n // `from` is guaranteed non-empty by the schema, so `from[0]` is always present.\n const fallback = route.default ?? route.from[0]!\n\n return route.from.includes('local') && localSet.has(route.packageName) ? 'local' : fallback\n}\n\ninterface ResolveRouteArgs {\n routePath: string\n route: InfraKitDevProxyRoute\n templates: InfraKitDevProxy['templates']\n localSet: ReadonlySet<string>\n env: string | undefined\n getRelease: () => string\n /** Per-package runtime data (recorded release/port) from the dev-context merge. */\n localInfo?: ReadonlyMap<string, LocalPackageInfo>\n}\n\n/** Hostnames that can only be this machine \u2014 the only ones a private-CA cert is allowed to go unchecked on. */\nconst LOOPBACK_HOSTNAMES = new Set(['localhost', '127.0.0.1', '::1'])\n\n/**\n * Is `target` unambiguously on this machine? Only then may {@link resolveRoute} disable cert validation:\n * `templates.local` is an unconstrained string in a consumer-authored config, so an unconditional\n * `secure: false` in a PUBLISHED helper would silently switch off validation for whatever URL a consumer\n * wrote there. Scoping it to `.localhost`/loopback is what makes \"no MITM surface\" true by construction.\n * An unparseable target is NOT local (the caller then leaves `secure` unset \u2014 fail closed).\n */\nconst isLoopbackTarget = (target: string): boolean => {\n let hostname: string\n\n try {\n hostname = new URL(target).hostname.toLowerCase()\n } catch {\n return false\n }\n\n // `URL.hostname` brackets an IPv6 literal (`[::1]`); compare the bare address.\n const bare = hostname.startsWith('[') && hostname.endsWith(']') ? hostname.slice(1, -1) : hostname\n\n return bare.endsWith('.localhost') || LOOPBACK_HOSTNAMES.has(bare)\n}\n\n/** Resolve one route into a Vite proxy entry (or throw an actionable error). */\nconst resolveRoute = ({\n routePath,\n route,\n templates,\n localSet,\n env,\n getRelease,\n localInfo,\n}: ResolveRouteArgs): InfraKitViteProxyEntry => {\n const source = pickSource(route, localSet)\n\n if (source === 'local') {\n const target = resolveLocalTarget({ route, templates, env, getRelease, info: localInfo?.get(route.packageName) })\n\n // The local target is now HTTPS, served with a cert minted by portless's own private CA. Node's\n // http-proxy validates the upstream chain against the system store, which does not contain that CA,\n // so every proxied request fails with SELF_SIGNED_CERT_IN_CHAIN (confirmed empirically \u2014 see\n // `.omc/research/portless-https-spike.md`). Only a loopback target earns the exemption.\n return isLoopbackTarget(target) ? { target, changeOrigin: true, secure: false } : { target, changeOrigin: true }\n }\n\n if (!env) {\n throw new Error(\n `infra-kit/vite: proxy route \"${routePath}\" resolves to a cloud backend but ${INFRA_KIT_ENV} is not set. Source an environment from Doppler first (e.g. \\`infra-kit env-load -c dev\\`).`,\n )\n }\n\n const target = interpolate(templates.cloud, { release: '', packageName: route.packageName, env })\n\n return { target, changeOrigin: true, secure: false, cookieDomainRewrite: 'localhost' }\n}\n\ninterface ResolveProxyArgs {\n proxy: InfraKitDevProxy\n localSet: ReadonlySet<string>\n env: string | undefined\n getRelease: () => string\n /** Pre-computed `Authorization` header value applied uniformly to every route. */\n authHeader?: string\n /** Per-package runtime data (recorded release/port) from the dev-context merge. */\n localInfo?: ReadonlyMap<string, LocalPackageInfo>\n}\n\n/**\n * Pure resolver: turn a `dev.proxy` config + resolved inputs (local set, env,\n * lazy release) into a Vite `server.proxy` map. Side-effect-free so the\n * resolution shape is fully unit-testable. When `authHeader` is set, every route\n * entry gets a matching `headers.Authorization`; otherwise no `headers` key.\n */\nexport const resolveProxyConfig = ({\n proxy,\n localSet,\n env,\n getRelease,\n authHeader,\n localInfo,\n}: ResolveProxyArgs): InfraKitViteProxy => {\n const result: InfraKitViteProxy = {}\n const headers = authHeader ? { Authorization: authHeader } : undefined\n\n for (const [routePath, route] of Object.entries(proxy.routes)) {\n const entry = resolveRoute({ routePath, route, templates: proxy.templates, localSet, env, getRelease, localInfo })\n\n result[routePath] = headers ? { ...entry, headers } : entry\n }\n\n return result\n}\n\n/** Memoize a zero-arg thunk so `<release>` git resolution runs at most once. */\nconst once = <T>(fn: () => T): (() => T) => {\n let cached: { value: T } | undefined\n\n return () => {\n cached ??= { value: fn() }\n\n return cached.value\n }\n}\n\n/**\n * Load a package's `infra-kit.config.ts` and return its `dev` block, or\n * `undefined` when the config or the `dev` key is absent. The `.ts` config is\n * evaluated via Node's native type stripping (Node >= 24) \u2014 the same mechanism\n * the CLI's config loader uses. Cache-busted by mtime so repeated dev-server\n * reloads pick up edits.\n */\nexport const loadDev = async (cwd: string): Promise<InfraKitDev | undefined> => {\n const configPath = path.join(cwd, PACKAGE_CONFIG_FILE)\n\n if (!fs.existsSync(configPath)) return undefined\n\n const stat = fs.statSync(configPath)\n const moduleUrl = `${pathToFileURL(configPath).href}?mtime=${Number(stat.mtimeMs)}`\n\n const imported = (await import(moduleUrl)) as { default?: unknown }\n const rawExport = imported.default\n\n if (rawExport === undefined) return undefined\n\n const resolved = typeof rawExport === 'function' ? await (rawExport as () => unknown)() : rawExport\n\n const parsed = packageConfigSchema.safeParse(resolved)\n\n if (!parsed.success) {\n throw new Error(`infra-kit/vite: invalid ${PACKAGE_CONFIG_FILE} at ${configPath}: ${z.prettifyError(parsed.error)}`)\n }\n\n return parsed.data.dev\n}\n\n/** Coerce a parsed dev-context.json into the set of locally-running package names. */\nconst extractPackages = (parsed: unknown): string[] => {\n if (Array.isArray(parsed)) {\n return parsed.filter((v): v is string => {\n return typeof v === 'string'\n })\n }\n\n if (parsed !== null && typeof parsed === 'object') {\n const candidate = (parsed as Record<string, unknown>).packages ?? (parsed as Record<string, unknown>).localPackages\n\n if (Array.isArray(candidate)) {\n return candidate.filter((v): v is string => {\n return typeof v === 'string'\n })\n }\n }\n\n return []\n}\n\n/** Search upward from `start` for `relative`, returning the first hit or undefined. */\nconst findUp = (start: string, relative: string): string | undefined => {\n let dir = path.resolve(start)\n\n for (;;) {\n const candidate = path.join(dir, relative)\n\n if (fs.existsSync(candidate)) return candidate\n\n const parent = path.dirname(dir)\n\n if (parent === dir) return undefined\n\n dir = parent\n }\n}\n\n/** Per-package runtime data merged from the dev-context fragment directory. */\nexport interface LocalPackageInfo {\n /** The real bound port the runner recorded. Provenance only \u2014 the proxy target is the alias host. */\n port: number\n /**\n * The authoritative local target (`https://<release>.<packageName>.localhost`), published by the\n * runner. Present \u2192 {@link resolveLocalTarget} uses it verbatim and consults no template. Absent \u2192\n * the fragment came from an old CLI and the legacy template path runs.\n */\n origin?: string\n /** LEGACY (no `origin`): the runner-recorded release slug, preferred over the helper's git derivation. */\n release?: string\n /** LEGACY (no `origin`): `<release>.<packageName>.localhost`, present only when an alias was registered. */\n alias?: string\n /** LEGACY (no `origin`): the port {@link alias} resolves on. `80` (the default) is implicit in the template. */\n proxyPort?: number\n}\n\n/**\n * The locally-running package set plus a `package \u2192 { port, release }` map merged\n * from the dev-context fragments. `packages` feeds `pickSource`; `info` lets a\n * `local` route emit the per-package recorded release (see {@link resolveRoute}).\n */\nexport interface LocalContext {\n packages: ReadonlySet<string>\n info: ReadonlyMap<string, LocalPackageInfo>\n}\n\n/** An empty {@link LocalContext} (frontend-only / dev-context absent). */\nconst emptyLocalContext = (): LocalContext => {\n return { packages: new Set(), info: new Map() }\n}\n\n/**\n * Is the runner that wrote a fragment still alive? Signal `0` performs the permission and existence\n * checks without delivering anything. `ESRCH` is the one answer that means \"gone\"; `EPERM` means the\n * pid exists under another uid, which is still a live process. A fragment with no `pid` predates the\n * field and is trusted (back-compat) rather than dropped.\n */\nconst isFragmentWriterAlive = (pid: number | undefined): boolean => {\n if (pid == null) return true\n\n try {\n process.kill(pid, 0)\n\n return true\n } catch (error) {\n return (error as NodeJS.ErrnoException).code === 'EPERM'\n }\n}\n\n/**\n * Merge every `<app>.json` fragment in the dev-context directory into a\n * {@link LocalContext}. Two DISTINCT failure branches (do NOT collapse them into\n * one directory-wide catch):\n * - `readdir`-ENOENT (directory vanished mid-race) \u2192 empty context, matching the\n * dir-absent back-compat behaviour.\n * - a single corrupt/truncated/invalid `<app>.json` \u2192 that ONE fragment is\n * SKIPPED (per-fragment `safeParse` isolation); the rest still merge, so one bad\n * fragment never collapses the whole localSet to empty (which would silently\n * drop a started package to cloud).\n */\nconst readFragmentDir = (dir: string): LocalContext => {\n let entries: string[]\n\n try {\n entries = fs.readdirSync(dir)\n } catch {\n return emptyLocalContext()\n }\n\n const packages = new Set<string>()\n const info = new Map<string, LocalPackageInfo>()\n\n for (const name of entries) {\n if (!name.endsWith('.json')) continue\n\n try {\n const parsed = devContextFragmentSchema.safeParse(JSON.parse(fs.readFileSync(path.join(dir, name), 'utf-8')))\n\n if (!parsed.success) continue\n\n // A runner killed with SIGKILL (or crashed) leaves its fragment AND its portless alias behind.\n // Trusting it would route this package's `/api` at an alias nothing serves \u2014 a silent 502 with\n // no diagnostic. The writer already stamps its pid; honour it.\n if (!isFragmentWriterAlive(parsed.data.pid)) continue\n\n packages.add(parsed.data.package)\n info.set(parsed.data.package, {\n port: parsed.data.port,\n origin: parsed.data.origin,\n release: parsed.data.release,\n alias: parsed.data.alias,\n proxyPort: parsed.data.proxyPort,\n })\n } catch {\n // Corrupt/truncated fragment (JSON.parse / read failure): skip ONLY this one.\n continue\n }\n }\n\n return { packages, info }\n}\n\n/**\n * Read the locally-running package context, searched upward from `cwd`. Prefers\n * the `.infra-kit/dev-context/` fragment DIRECTORY (merged via\n * {@link readFragmentDir}); when it is absent, falls back to the legacy single\n * `.infra-kit/dev-context.json` file (read as before \u2014 no per-package release);\n * when neither exists \u2192 empty context (frontend-only, every route resolves cloud).\n */\nexport const readLocalContext = (cwd: string): LocalContext => {\n const dir = findUp(cwd, DEV_CONTEXT_DIR)\n\n if (dir) return readFragmentDir(dir)\n\n const legacy = findUp(cwd, DEV_CONTEXT_FILE)\n\n if (!legacy) return emptyLocalContext()\n\n try {\n return { packages: new Set(extractPackages(JSON.parse(fs.readFileSync(legacy, 'utf-8')))), info: new Map() }\n } catch {\n return emptyLocalContext()\n }\n}\n\n/**\n * Read the set of locally-running packages (searched upward from `cwd`). Thin\n * wrapper over {@link readLocalContext} preserving the historical `Set`-returning\n * contract. Absent dev-context \u2192 empty set (frontend-only cloud resolution).\n */\nexport const readLocalSet = (cwd: string): ReadonlySet<string> => {\n return readLocalContext(cwd).packages\n}\n\n/** Current git branch of `cwd` (raw, un-slugified). */\nconst readGitBranch = (cwd: string): string => {\n // eslint-disable-next-line sonarjs/no-os-command-from-path\n return execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd, encoding: 'utf-8' }).trim()\n}\n\n/**\n * An OS-assigned free TCP port on 127.0.0.1 \u2014 the per-worktree dev-server port (mirrors the\n * backend's `listen(0)`). This probes then releases the port, so there is a small TOCTOU window\n * before Vite binds it; Vite's default `strictPort: false` absorbs a lost race by stepping to the\n * next free port. Only a consumer that sets `strictPort: true` would hard-fail on the rare collision.\n */\nconst getFreePort = (): Promise<number> => {\n return new Promise((resolve, reject) => {\n const srv = net.createServer()\n\n srv.unref()\n srv.on('error', reject)\n srv.listen(0, '127.0.0.1', () => {\n const address = srv.address()\n const port = typeof address === 'object' && address !== null ? address.port : 0\n\n srv.close(() => {\n return resolve(port)\n })\n })\n })\n}\n\n/** Env var carrying the runner-assigned `{ \"<ui-package>\": <port> }` map (Layer B). */\nconst UI_PORTS_ENV = 'INFRA_KIT_UI_PORTS'\n\n/** This package's `package.json` `name`, or `null` (unreadable / nameless). */\nconst readPackageName = (cwd: string): string | null => {\n try {\n const parsed = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf-8')) as { name?: unknown }\n\n return typeof parsed.name === 'string' ? parsed.name : null\n } catch {\n return null\n }\n}\n\n/**\n * What the runner assigned THIS UI. `alias` is present only in the widened record form, i.e. only when the\n * runner both assigned the port AND registered the hostname \u2014 which is exactly the condition for pointing\n * HMR at that hostname. The legacy bare-number form yields no alias.\n */\ninterface ManagedUi {\n port: number\n alias?: string\n}\n\n/**\n * What `infra-kit dev` assigned THIS UI via `INFRA_KIT_UI_PORTS` (Layer B), or `null` when the map is\n * absent / this package isn't in it / it's malformed. Present \u2192 the helper binds the port with\n * `strictPort` so vite lands on exactly the port the runner already aliased. Absent (a standalone\n * `vite dev`) \u2192 the caller falls back to a free port, byte-identical to today.\n *\n * Accepts BOTH the widened `{ port, alias }` record and the legacy bare `<port>` number an older runner\n * writes \u2014 a new helper paired with an old CLI must still bind the assigned port.\n */\nconst resolveManagedUi = (cwd: string): ManagedUi | null => {\n const raw = process.env[UI_PORTS_ENV]\n\n if (raw == null || raw === '') return null\n\n const name = readPackageName(cwd)\n\n if (name == null) return null\n\n try {\n const entry = (JSON.parse(raw) as Record<string, unknown>)[name]\n\n if (typeof entry === 'number') return { port: entry }\n\n if (entry === null || typeof entry !== 'object') return null\n\n const { port, alias } = entry as { port?: unknown; alias?: unknown }\n\n if (typeof port !== 'number') return null\n\n return { port, alias: typeof alias === 'string' ? alias : undefined }\n } catch {\n return null\n }\n}\n\n/** The port an `https://` alias is served on \u2014 never spelled out in a URL, and what the HMR client dials. */\nconst HTTPS_PORT = 443\n\n/**\n * Vite's `server.hmr` override, pointing the HMR client at the alias instead of at the raw dev-server port.\n * The page is loaded over `https://<alias>`, so its websocket must be `wss://` on the same origin or the\n * browser blocks it as mixed content; `clientPort` is the proxy's implicit `:443`, not vite's bound port.\n */\nexport interface InfraKitViteHmr {\n protocol: 'wss'\n host: string\n clientPort: number\n}\n\n/**\n * Resolve a package's `dev` block into a ready-made Vite `server` config: a per-worktree dev-server\n * `port` plus the `proxy` map. Loads the package's `infra-kit.config.ts`, merges the local dev set\n * from the `.infra-kit/dev-context/` fragment directory, and interpolates the local/cloud templates.\n * `<env>` comes from `INFRA_KIT_ENV`; `<release>` from each package's runner-recorded fragment when\n * present, else the slugified git branch (computed lazily, only when a local route needs it).\n *\n * `port` defaults to a fresh OS-assigned free port so simultaneous git worktrees never collide on\n * Vite's `5173` (override via `options.port`). `host` defaults to {@link LOOPBACK_V4} so the portless\n * alias can actually reach it (override via `options.host` for containers/LAN). `hmr` is emitted only\n * when `infra-kit dev` registered an alias for this UI, so a bare `vite dev` keeps vite's own HMR\n * defaults. Pass `command` so `build` is a no-op (empty proxy, no port) \u2014 a build ignores `server` and\n * proxy resolution would otherwise fail-fast on a cloud route with no sourced env.\n *\n * @example\n * // vite.config.ts \u2014 the whole `server` field is the helper's output\n * import { infraKitDev } from 'infra-kit/vite'\n * export default defineConfig(async ({ command }) => ({ server: await infraKitDev({ command }) }))\n */\nexport const infraKitDev = async (\n options: InfraKitDevOptions = {},\n): Promise<{\n port?: number\n host?: string | boolean\n strictPort?: boolean\n hmr?: InfraKitViteHmr\n proxy: InfraKitViteProxy\n}> => {\n const cwd = options.cwd ?? process.cwd()\n\n // A build ignores `server`; skip the port + proxy work (proxy resolution would fail-fast on a\n // cloud route with no sourced env).\n if (options.command === 'build') return { proxy: {} }\n\n // Port precedence: explicit `options.port` > the runner-assigned Layer-B port (bound `strictPort` so\n // vite lands on exactly the aliased port) > a fresh free port (today's default; the portless-off path).\n const managed = options.port == null ? resolveManagedUi(cwd) : null\n const port = options.port ?? managed?.port ?? (await getFreePort())\n const strictPort = managed != null\n const host = options.host ?? LOOPBACK_V4\n\n // Override HMR ONLY when the runner handed us the alias it registered. A bare `vite dev` (no runner)\n // has no alias, and a computable hostname is not a registered one \u2014 pointing the HMR client at a\n // hostname nothing resolves would break hot reload on a path that works today.\n const hmr: InfraKitViteHmr | undefined =\n managed?.alias == null ? undefined : { protocol: 'wss', host: managed.alias, clientPort: HTTPS_PORT }\n const server = { port, host, ...(strictPort ? { strictPort } : {}), ...(hmr ? { hmr } : {}) }\n const dev = await loadDev(cwd)\n\n if (!dev?.proxy) return { ...server, proxy: {} }\n\n const { packages: localSet, info: localInfo } = readLocalContext(cwd)\n const env = process.env[INFRA_KIT_ENV]\n const authHeader = buildBasicAuthHeader(process.env, options.basicAuth)\n const getRelease = once(() => {\n // Mirrors the dev-server's `readAppRelease`: outside a git repo both sides must land on the SAME\n // label, or this target names a host the runner never aliased.\n try {\n return slugifyRelease(readGitBranch(cwd)) || DEFAULT_RELEASE_SLUG\n } catch {\n return DEFAULT_RELEASE_SLUG\n }\n })\n\n return {\n ...server,\n proxy: resolveProxyConfig({ proxy: dev.proxy, localSet, env, getRelease, authHeader, localInfo }),\n }\n}\n\n/**\n * Convenience wrapper returning just the proxy map (for spreading into\n * `server.proxy` directly). Equivalent to `(await infraKitDev(options)).proxy`.\n */\nexport const infraKitProxy = async (options: InfraKitDevOptions = {}): Promise<InfraKitViteProxy> => {\n return (await infraKitDev(options)).proxy\n}\n", "import { z } from 'zod'\n\n/**\n * Schema for the resolved (post-factory) package config object. `strictObject`\n * rejects unknown keys so typos in `infra-kit.config.ts` surface as validation\n * errors instead of being silently ignored.\n *\n * Kept in its own module \u2014 separate from the public `defineConfig`/types entry \u2014\n * so the published `infra-kit` type surface stays free of a `zod` import.\n */\nexport const packageConfigSchema = z.strictObject({\n requiredScripts: z.array(z.string().min(1)).optional(),\n requiredFiles: z.array(z.string().min(1)).optional(),\n turbo: z\n .strictObject({\n requiredTasks: z.array(z.string().min(1)).optional(),\n })\n .optional(),\n dev: z\n .strictObject({\n proxy: z\n .strictObject({\n templates: z.strictObject({\n local: z.string().min(1),\n cloud: z.string().min(1),\n }),\n routes: z.record(\n z.string().min(1),\n z\n .strictObject({\n packageName: z.string().min(1),\n from: z.array(z.enum(['local', 'cloud'])).min(1),\n default: z.enum(['local', 'cloud']).optional(),\n })\n .refine(\n (route) => {\n return route.from.length <= 1 || route.default !== undefined\n },\n {\n message: 'default is required when `from` has more than one source',\n },\n )\n .refine(\n (route) => {\n return route.default === undefined || route.from.includes(route.default)\n },\n {\n message: 'default must be listed in `from`',\n },\n ),\n ),\n })\n .optional(),\n })\n .optional(),\n})\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["Buffer", "execFileSync", "fs", "net", "path", "process", "pathToFileURL", "z", "z", "packageConfigSchema", "route", "INFRA_KIT_ENV", "PACKAGE_CONFIG_FILE", "DEV_CONTEXT_DIR", "path", "DEV_CONTEXT_FILE", "devContextFragmentSchema", "z", "buildBasicAuthHeader", "env", "override", "username", "password", "Buffer", "interpolate", "template", "values", "DEFAULT_HTTP_PORT", "LOOPBACK_V4", "withProxyPort", "target", "proxyPort", "url", "grafted", "LEGACY_SELF_SPAWNED_PORT_FLOOR", "resolveLegacyTarget", "resolveLocalTarget", "args", "route", "templates", "env", "getRelease", "info", "slugifyHostLabel", "pickSource", "localSet", "fallback", "LOOPBACK_HOSTNAMES", "isLoopbackTarget", "hostname", "bare", "resolveRoute", "routePath", "localInfo", "INFRA_KIT_ENV", "resolveProxyConfig", "proxy", "authHeader", "result", "headers", "entry", "once", "fn", "cached", "loadDev", "cwd", "configPath", "path", "PACKAGE_CONFIG_FILE", "fs", "stat", "rawExport", "pathToFileURL", "resolved", "parsed", "packageConfigSchema", "z", "extractPackages", "v", "candidate", "findUp", "start", "relative", "dir", "parent", "emptyLocalContext", "isFragmentWriterAlive", "pid", "process", "error", "readFragmentDir", "entries", "packages", "name", "devContextFragmentSchema", "readLocalContext", "DEV_CONTEXT_DIR", "legacy", "DEV_CONTEXT_FILE", "readGitBranch", "cwd", "execFileSync", "getFreePort", "resolve", "reject", "srv", "net", "address", "port", "UI_PORTS_ENV", "readPackageName", "parsed", "fs", "path", "resolveManagedUi", "raw", "process", "name", "entry", "alias", "HTTPS_PORT", "infraKitDev", "options", "managed", "strictPort", "host", "LOOPBACK_V4", "hmr", "server", "dev", "loadDev", "localSet", "localInfo", "readLocalContext", "env", "INFRA_KIT_ENV", "authHeader", "buildBasicAuthHeader", "getRelease", "once", "slugifyRelease", "DEFAULT_RELEASE_SLUG", "resolveProxyConfig", "infraKitProxy"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/lib/pm-env/pm-env.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * `env` minus every package-manager marker a spawned child could mistake for a throwaway install.\n *\n * Any child we launch through `node <bin>` (the portless driver, self-update, the mcp launcher) inherits\n * our env, and a tool that sniffs those markers will conclude it was run via `npx`/`pnpm dlx` and refuse\n * to work. portless is the motivating case; its guard (cli.js `main()`) is:\n *\n * isNpx = npm_command === 'exec' && !npm_lifecycle_event\n * isPnpmDlx = !!PNPM_SCRIPT_SRC_DIR && !npm_lifecycle_event\n * if ((isNpx || isPnpmDlx) && !isLocallyInstalled()) \u2192 abort\n *\n * `isLocallyInstalled()` walks up from the CHILD's cwd, so in a consumer repo (where portless is a\n * transitive dep of infra-kit, absent from the root `node_modules`) it is false and the guard is live.\n * `pnpm exec infra-kit dev` exports `npm_command=exec` with no lifecycle event \u2192 `isNpx` \u2192 every\n * portless call exits 1 \u2192 `isAvailable()` is false \u2192 `ensureProxy` aborts the whole dev boot with\n * \"portless is not installed\", on a machine where it plainly is.\n *\n * `PNPM_SCRIPT_SRC_DIR` must be dropped ALONGSIDE the `npm_*` block, not left behind: it is set by\n * `pnpm run <script>`, where `npm_lifecycle_event` is what suppresses `isPnpmDlx`. Stripping only\n * `npm_*` removes that suppressor and *creates* an abort for the script-style invocation that\n * previously worked. Both markers go, or neither is safe.\n *\n * The result makes the child look exactly like a direct `node <pkg>/dist/cli.js` run \u2014 which it is.\n */\nexport const withoutPackageManagerEnv = (env: NodeJS.ProcessEnv): NodeJS.ProcessEnv => {\n return Object.fromEntries(\n Object.entries(env).filter(([key]) => {\n return !key.startsWith('npm_') && key !== 'PNPM_SCRIPT_SRC_DIR'\n }),\n )\n}\n"],
|
|
5
|
+
"mappings": "AAwBO,IAAMA,EAA4BC,GAChC,OAAO,YACZ,OAAO,QAAQA,CAAG,EAAE,OAAO,CAAC,CAACC,CAAG,IACvB,CAACA,EAAI,WAAW,MAAM,GAAKA,IAAQ,qBAC3C,CACH",
|
|
6
|
+
"names": ["withoutPackageManagerEnv", "env", "key"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import T from"node:crypto";import r from"node:fs";import F from"node:os";import n from"node:path";import A from"node:process";var V="env-load.sh",a="env-clear.sh",p="projects",O=7200,E="INFRA_KIT_SESSION",m="INFRA_KIT_ENV",C="INFRA_KIT_ENV_CONFIG",D="INFRA_KIT_ENV_PROJECT",S="INFRA_KIT_ENV_PROJECT_ROOT",K="INFRA_KIT_ENV_LOADED_AT",g="INFRA_KIT_ENV_AUTOLOADED",x="INFRA_KIT_ENV_CLEARED",I=/^([A-Z_]\w*)=/i,L=(t,e)=>{let o=e;for(let _=0;_<t.length;_++){if(!o&&t[_]==="\\"){_++;continue}t[_]==="'"&&(o=!o)}return o},h=t=>{if(!r.existsSync(t))return[];let e=r.readFileSync(t,"utf-8"),o=[],_=!1;for(let s of e.split(`
|
|
2
|
+
`)){if(!_){let c=I.exec(s);c&&o.push(c[1])}_=L(s,_)}return o},R=()=>{let t=A.env.XDG_CACHE_HOME,e=t&&t.length>0?t:n.join(F.homedir(),".cache");return n.join(e,"infra-kit")},f=()=>{let t=A.env[E];if(!t)throw new Error(`${E} is not set. Run \`infra-kit init\` then \`source ~/.zshrc\`.`);return n.join(R(),t)},i=t=>T.createHash("sha256").update(t,"utf8").digest("hex"),N=()=>n.join(R(),p),u=t=>n.join(N(),i(t)),l=(t,e,o)=>{let _=`${t}.tmp.${A.pid}`;r.writeFileSync(_,e,{mode:o});try{r.renameSync(_,t)}catch(s){throw r.rmSync(_,{force:!0}),s}},y="-worktrees";export{V as a,a as b,O as c,E as d,m as e,C as f,D as g,S as h,K as i,g as j,x as k,h as l,R as m,f as n,N as o,u as p,l as q,y as r};
|
|
3
|
+
//# sourceMappingURL=chunk-CHETBZ6M.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/lib/constants/constants.ts"
|
|
4
|
-
"sourcesContent": ["import crypto from 'node:crypto'\nimport fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport process from 'node:process'\n\nexport const ENV_LOAD_FILE = 'env-load.sh'\nexport const ENV_CLEAR_FILE = 'env-clear.sh'\n\n/** Subdir of the cache root holding the project-scoped WARM caches (one dir per\n * project, keyed by {@link warmCacheKey}). Kept in sync with the zsh block in\n * init.ts, which reads `$cache_root/projects/$key/env-load.sh`. */\nexport const WARM_CACHE_SUBDIR = 'projects'\n\n/**\n * Default warm-cache TTL in seconds (2h). Governs BOTH the node-side eviction\n * sweep and the zsh-side source gate (`_INFRA_KIT_WARM_TTL` in the shell block);\n * the two MUST stay equal, so this constant is the single source of truth and the\n * shell default is emitted from it. Short by design: a warm file older than this\n * is never sourced (bounds a revoked/rotated secret served at prompt-0) and is\n * deleted on the next write. The background refresh lands in ~1-2s regardless.\n */\nexport const DEFAULT_WARM_TTL_SECONDS = 2 * 60 * 60\n\nexport const INFRA_KIT_SESSION_VAR = 'INFRA_KIT_SESSION'\n/**\n * The active environment/config NAME (e.g. `dev`, `arthur`), exported plainly so\n * non-shell consumers can read it from `process.env`. Notably `infra-kit/vite`'s\n * `infraKitDev()` reads this to interpolate the `<env>` placeholder in cloud proxy\n * targets. Mirrors {@link INFRA_KIT_ENV_CONFIG_VAR} (same value) but is the stable,\n * purpose-named handle for tooling rather than the shell session-metadata var.\n */\nexport const INFRA_KIT_ENV_VAR = 'INFRA_KIT_ENV'\nexport const INFRA_KIT_ENV_CONFIG_VAR = 'INFRA_KIT_ENV_CONFIG'\nexport const INFRA_KIT_ENV_PROJECT_VAR = 'INFRA_KIT_ENV_PROJECT'\n/**\n * Absolute project root (git top-level) the loaded env belongs to. Lets the shell\n * startup gate tell a same-project subshell (skip) from a NEW project (load), so a\n * different project's secrets are never silently kept after `cd`/new shell.\n */\nexport const INFRA_KIT_ENV_PROJECT_ROOT_VAR = 'INFRA_KIT_ENV_PROJECT_ROOT'\nexport const INFRA_KIT_ENV_LOADED_AT_VAR = 'INFRA_KIT_ENV_LOADED_AT'\n/**\n * Marker exported into env-load.sh ONLY when the load was triggered automatically\n * (see lib/env-autoload). Its presence is the sole signal distinguishing an\n * auto-loaded env from a deliberate manual `env-load`, so auto-load never\n * clobbers a manual choice. A manual load unsets it.\n */\nexport const INFRA_KIT_ENV_AUTOLOADED_VAR = 'INFRA_KIT_ENV_AUTOLOADED'\n/**\n * Suppression sentinel exported by env-clear. While set in a shell, cli-invocation\n * auto-load stays silent (a deliberate clear must not be immediately re-loaded).\n * Lifted by a manual `env-load` or a new shell.\n */\nexport const INFRA_KIT_ENV_CLEARED_VAR = 'INFRA_KIT_ENV_CLEARED'\n\n/**\n * Matches a line of the form `KEY=...` where KEY is an env-var identifier\n * (letter or underscore, then word chars). Capture group 1 is the name. Shared\n * between env-load (validation, var counting) and parseVarNamesFromEnvFile.\n */\nexport const ENV_VAR_LINE_PATTERN = /^([A-Z_]\\w*)=/i\n\n/**\n * Track whether a physical line leaves us inside an open single-quoted value,\n * mirroring how `shellSingleQuote` emits values (`'\u2026'`, with literal quotes as\n * `'\\''`). Outside a quote a backslash escapes the next char; inside a quote a\n * `'` closes it. Lets the parser skip the continuation lines of a multiline value.\n */\nconst advanceSingleQuoteState = (line: string, startInQuote: boolean): boolean => {\n let inQuote = startInQuote\n\n for (let i = 0; i < line.length; i++) {\n if (!inQuote && line[i] === '\\\\') {\n i++\n continue\n }\n\n if (line[i] === \"'\") {\n inQuote = !inQuote\n }\n }\n\n return inQuote\n}\n\nexport const parseVarNamesFromEnvFile = (filePath: string): string[] => {\n if (!fs.existsSync(filePath)) return []\n\n const content = fs.readFileSync(filePath, 'utf-8')\n const names: string[] = []\n let inQuote = false\n\n for (const line of content.split('\\n')) {\n // Only a line that starts OUTSIDE a quoted value can be a real assignment;\n // continuation lines of a multiline secret value are skipped.\n if (!inQuote) {\n const match = ENV_VAR_LINE_PATTERN.exec(line)\n\n if (match) {\n names.push(match[1]!)\n }\n }\n\n inQuote = advanceSingleQuoteState(line, inQuote)\n }\n\n return names\n}\n\n/**\n * Root cache dir for infra-kit across all sessions. Resolved from\n * $XDG_CACHE_HOME when set, falling back to ~/.cache/infra-kit. Keep in sync\n * with the shell block emitted by `infra-kit init` (src/commands/init/init.ts).\n */\nexport const getCacheRoot = (): string => {\n const xdg = process.env.XDG_CACHE_HOME\n const base = xdg && xdg.length > 0 ? xdg : path.join(os.homedir(), '.cache')\n\n return path.join(base, 'infra-kit')\n}\n\nexport const getSessionCacheDir = (): string => {\n const session = process.env[INFRA_KIT_SESSION_VAR]\n\n if (!session) {\n throw new Error(`${INFRA_KIT_SESSION_VAR} is not set. Run \\`infra-kit init\\` then \\`source ~/.zshrc\\`.`)\n }\n\n return path.join(getCacheRoot(), session)\n}\n\n/**\n * The warm-cache key for a project: the hex SHA-256 of its CANONICAL (realpath'd)\n * directory. This MUST be byte-identical to the zsh block's\n * `printf %s \"$canon\" | shasum -a 256 | cut -c1-64` \u2014 same input string (no\n * trailing newline), same digest, full 64-hex output \u2014 or a warm file written by\n * node is never found by the shell. The shell passes the already-canonicalized\n * dir via `--project-dir`, so node hashes that VERBATIM (it does not re-resolve).\n */\nexport const warmCacheKey = (canonicalProjectDir: string): string => {\n return crypto.createHash('sha256').update(canonicalProjectDir, 'utf8').digest('hex')\n}\n\n/** Root holding all project-scoped warm caches: `$cacheRoot/projects`. */\nexport const getWarmCacheRoot = (): string => {\n return path.join(getCacheRoot(), WARM_CACHE_SUBDIR)\n}\n\n/** This project's warm-cache dir: `$cacheRoot/projects/<warmCacheKey>`. */\nexport const getProjectWarmCacheDir = (canonicalProjectDir: string): string => {\n return path.join(getWarmCacheRoot(), warmCacheKey(canonicalProjectDir))\n}\n\n/**\n * Write content atomically: write to a pid-suffixed temp file in the same\n * directory, then rename. fs.renameSync is atomic on a single filesystem, so\n * concurrent writers can't produce a half-written secret file.\n */\nexport const atomicWriteFileSync = (filePath: string, content: string, mode: number): void => {\n const tmpPath = `${filePath}.tmp.${process.pid}`\n\n fs.writeFileSync(tmpPath, content, { mode })\n\n try {\n fs.renameSync(tmpPath, filePath)\n } catch (error) {\n fs.rmSync(tmpPath, { force: true })\n throw error\n }\n}\n\nexport const WORKTREES_DIR_SUFFIX = '-worktrees'\n// eslint-disable-next-line sonarjs/publicly-writable-directories\nexport const LOG_FILE_PATH = '/tmp/mcp-infra-kit.log'\n"
|
|
5
|
-
"mappings": "
|
|
6
|
-
"names": ["crypto", "fs", "os", "path", "process", "ENV_LOAD_FILE", "ENV_CLEAR_FILE", "WARM_CACHE_SUBDIR", "DEFAULT_WARM_TTL_SECONDS", "INFRA_KIT_SESSION_VAR", "INFRA_KIT_ENV_VAR", "INFRA_KIT_ENV_CONFIG_VAR", "INFRA_KIT_ENV_PROJECT_VAR", "INFRA_KIT_ENV_PROJECT_ROOT_VAR", "INFRA_KIT_ENV_LOADED_AT_VAR", "INFRA_KIT_ENV_AUTOLOADED_VAR", "INFRA_KIT_ENV_CLEARED_VAR", "ENV_VAR_LINE_PATTERN", "advanceSingleQuoteState", "line", "startInQuote", "inQuote", "i", "parseVarNamesFromEnvFile", "filePath", "content", "names", "match", "getCacheRoot", "xdg", "base", "getSessionCacheDir", "session", "warmCacheKey", "canonicalProjectDir", "getWarmCacheRoot", "getProjectWarmCacheDir", "atomicWriteFileSync", "mode", "tmpPath", "error", "WORKTREES_DIR_SUFFIX"
|
|
3
|
+
"sources": ["../src/lib/constants/constants.ts"],
|
|
4
|
+
"sourcesContent": ["import crypto from 'node:crypto'\nimport fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport process from 'node:process'\n\nexport const ENV_LOAD_FILE = 'env-load.sh'\nexport const ENV_CLEAR_FILE = 'env-clear.sh'\n\n/** Subdir of the cache root holding the project-scoped WARM caches (one dir per\n * project, keyed by {@link warmCacheKey}). Kept in sync with the zsh block in\n * init.ts, which reads `$cache_root/projects/$key/env-load.sh`. */\nexport const WARM_CACHE_SUBDIR = 'projects'\n\n/**\n * Default warm-cache TTL in seconds (2h). Governs BOTH the node-side eviction\n * sweep and the zsh-side source gate (`_INFRA_KIT_WARM_TTL` in the shell block);\n * the two MUST stay equal, so this constant is the single source of truth and the\n * shell default is emitted from it. Short by design: a warm file older than this\n * is never sourced (bounds a revoked/rotated secret served at prompt-0) and is\n * deleted on the next write. The background refresh lands in ~1-2s regardless.\n */\nexport const DEFAULT_WARM_TTL_SECONDS = 2 * 60 * 60\n\nexport const INFRA_KIT_SESSION_VAR = 'INFRA_KIT_SESSION'\n/**\n * The active environment/config NAME (e.g. `dev`, `arthur`), exported plainly so\n * non-shell consumers can read it from `process.env`. Notably `infra-kit/vite`'s\n * `infraKitDev()` reads this to interpolate the `<env>` placeholder in cloud proxy\n * targets. Mirrors {@link INFRA_KIT_ENV_CONFIG_VAR} (same value) but is the stable,\n * purpose-named handle for tooling rather than the shell session-metadata var.\n */\nexport const INFRA_KIT_ENV_VAR = 'INFRA_KIT_ENV'\nexport const INFRA_KIT_ENV_CONFIG_VAR = 'INFRA_KIT_ENV_CONFIG'\nexport const INFRA_KIT_ENV_PROJECT_VAR = 'INFRA_KIT_ENV_PROJECT'\n/**\n * Absolute project root (git top-level) the loaded env belongs to. Lets the shell\n * startup gate tell a same-project subshell (skip) from a NEW project (load), so a\n * different project's secrets are never silently kept after `cd`/new shell.\n */\nexport const INFRA_KIT_ENV_PROJECT_ROOT_VAR = 'INFRA_KIT_ENV_PROJECT_ROOT'\nexport const INFRA_KIT_ENV_LOADED_AT_VAR = 'INFRA_KIT_ENV_LOADED_AT'\n/**\n * Marker exported into env-load.sh ONLY when the load was triggered automatically\n * (see lib/env-autoload). Its presence is the sole signal distinguishing an\n * auto-loaded env from a deliberate manual `env-load`, so auto-load never\n * clobbers a manual choice. A manual load unsets it.\n */\nexport const INFRA_KIT_ENV_AUTOLOADED_VAR = 'INFRA_KIT_ENV_AUTOLOADED'\n/**\n * Suppression sentinel exported by env-clear. While set in a shell, cli-invocation\n * auto-load stays silent (a deliberate clear must not be immediately re-loaded).\n * Lifted by a manual `env-load` or a new shell.\n */\nexport const INFRA_KIT_ENV_CLEARED_VAR = 'INFRA_KIT_ENV_CLEARED'\n\n/**\n * Matches a line of the form `KEY=...` where KEY is an env-var identifier\n * (letter or underscore, then word chars). Capture group 1 is the name. Shared\n * between env-load (validation, var counting) and parseVarNamesFromEnvFile.\n */\nexport const ENV_VAR_LINE_PATTERN = /^([A-Z_]\\w*)=/i\n\n/**\n * Track whether a physical line leaves us inside an open single-quoted value,\n * mirroring how `shellSingleQuote` emits values (`'\u2026'`, with literal quotes as\n * `'\\''`). Outside a quote a backslash escapes the next char; inside a quote a\n * `'` closes it. Lets the parser skip the continuation lines of a multiline value.\n */\nconst advanceSingleQuoteState = (line: string, startInQuote: boolean): boolean => {\n let inQuote = startInQuote\n\n for (let i = 0; i < line.length; i++) {\n if (!inQuote && line[i] === '\\\\') {\n i++\n continue\n }\n\n if (line[i] === \"'\") {\n inQuote = !inQuote\n }\n }\n\n return inQuote\n}\n\nexport const parseVarNamesFromEnvFile = (filePath: string): string[] => {\n if (!fs.existsSync(filePath)) return []\n\n const content = fs.readFileSync(filePath, 'utf-8')\n const names: string[] = []\n let inQuote = false\n\n for (const line of content.split('\\n')) {\n // Only a line that starts OUTSIDE a quoted value can be a real assignment;\n // continuation lines of a multiline secret value are skipped.\n if (!inQuote) {\n const match = ENV_VAR_LINE_PATTERN.exec(line)\n\n if (match) {\n names.push(match[1]!)\n }\n }\n\n inQuote = advanceSingleQuoteState(line, inQuote)\n }\n\n return names\n}\n\n/**\n * Root cache dir for infra-kit across all sessions. Resolved from\n * $XDG_CACHE_HOME when set, falling back to ~/.cache/infra-kit. Keep in sync\n * with the shell block emitted by `infra-kit init` (src/commands/init/init.ts).\n */\nexport const getCacheRoot = (): string => {\n const xdg = process.env.XDG_CACHE_HOME\n const base = xdg && xdg.length > 0 ? xdg : path.join(os.homedir(), '.cache')\n\n return path.join(base, 'infra-kit')\n}\n\nexport const getSessionCacheDir = (): string => {\n const session = process.env[INFRA_KIT_SESSION_VAR]\n\n if (!session) {\n throw new Error(`${INFRA_KIT_SESSION_VAR} is not set. Run \\`infra-kit init\\` then \\`source ~/.zshrc\\`.`)\n }\n\n return path.join(getCacheRoot(), session)\n}\n\n/**\n * The warm-cache key for a project: the hex SHA-256 of its CANONICAL (realpath'd)\n * directory. This MUST be byte-identical to the zsh block's\n * `printf %s \"$canon\" | shasum -a 256 | cut -c1-64` \u2014 same input string (no\n * trailing newline), same digest, full 64-hex output \u2014 or a warm file written by\n * node is never found by the shell. The shell passes the already-canonicalized\n * dir via `--project-dir`, so node hashes that VERBATIM (it does not re-resolve).\n */\nexport const warmCacheKey = (canonicalProjectDir: string): string => {\n return crypto.createHash('sha256').update(canonicalProjectDir, 'utf8').digest('hex')\n}\n\n/** Root holding all project-scoped warm caches: `$cacheRoot/projects`. */\nexport const getWarmCacheRoot = (): string => {\n return path.join(getCacheRoot(), WARM_CACHE_SUBDIR)\n}\n\n/** This project's warm-cache dir: `$cacheRoot/projects/<warmCacheKey>`. */\nexport const getProjectWarmCacheDir = (canonicalProjectDir: string): string => {\n return path.join(getWarmCacheRoot(), warmCacheKey(canonicalProjectDir))\n}\n\n/**\n * Write content atomically: write to a pid-suffixed temp file in the same\n * directory, then rename. fs.renameSync is atomic on a single filesystem, so\n * concurrent writers can't produce a half-written secret file.\n */\nexport const atomicWriteFileSync = (filePath: string, content: string, mode: number): void => {\n const tmpPath = `${filePath}.tmp.${process.pid}`\n\n fs.writeFileSync(tmpPath, content, { mode })\n\n try {\n fs.renameSync(tmpPath, filePath)\n } catch (error) {\n fs.rmSync(tmpPath, { force: true })\n throw error\n }\n}\n\nexport const WORKTREES_DIR_SUFFIX = '-worktrees'\n// eslint-disable-next-line sonarjs/publicly-writable-directories\nexport const LOG_FILE_PATH = '/tmp/mcp-infra-kit.log'\n"],
|
|
5
|
+
"mappings": "AAAA,OAAOA,MAAY,cACnB,OAAOC,MAAQ,UACf,OAAOC,MAAQ,UACf,OAAOC,MAAU,YACjB,OAAOC,MAAa,eAEb,IAAMC,EAAgB,cAChBC,EAAiB,eAKjBC,EAAoB,WAUpBC,EAA2B,KAE3BC,EAAwB,oBAQxBC,EAAoB,gBACpBC,EAA2B,uBAC3BC,EAA4B,wBAM5BC,EAAiC,6BACjCC,EAA8B,0BAO9BC,EAA+B,2BAM/BC,EAA4B,wBAO5BC,EAAuB,iBAQ9BC,EAA0B,CAACC,EAAcC,IAAmC,CAChF,IAAIC,EAAUD,EAEd,QAASE,EAAI,EAAGA,EAAIH,EAAK,OAAQG,IAAK,CACpC,GAAI,CAACD,GAAWF,EAAKG,CAAC,IAAM,KAAM,CAChCA,IACA,QACF,CAEIH,EAAKG,CAAC,IAAM,MACdD,EAAU,CAACA,EAEf,CAEA,OAAOA,CACT,EAEaE,EAA4BC,GAA+B,CACtE,GAAI,CAACvB,EAAG,WAAWuB,CAAQ,EAAG,MAAO,CAAC,EAEtC,IAAMC,EAAUxB,EAAG,aAAauB,EAAU,OAAO,EAC3CE,EAAkB,CAAC,EACrBL,EAAU,GAEd,QAAWF,KAAQM,EAAQ,MAAM;AAAA,CAAI,EAAG,CAGtC,GAAI,CAACJ,EAAS,CACZ,IAAMM,EAAQV,EAAqB,KAAKE,CAAI,EAExCQ,GACFD,EAAM,KAAKC,EAAM,CAAC,CAAE,CAExB,CAEAN,EAAUH,EAAwBC,EAAME,CAAO,CACjD,CAEA,OAAOK,CACT,EAOaE,EAAe,IAAc,CACxC,IAAMC,EAAMzB,EAAQ,IAAI,eAClB0B,EAAOD,GAAOA,EAAI,OAAS,EAAIA,EAAM1B,EAAK,KAAKD,EAAG,QAAQ,EAAG,QAAQ,EAE3E,OAAOC,EAAK,KAAK2B,EAAM,WAAW,CACpC,EAEaC,EAAqB,IAAc,CAC9C,IAAMC,EAAU5B,EAAQ,IAAIK,CAAqB,EAEjD,GAAI,CAACuB,EACH,MAAM,IAAI,MAAM,GAAGvB,CAAqB,+DAA+D,EAGzG,OAAON,EAAK,KAAKyB,EAAa,EAAGI,CAAO,CAC1C,EAUaC,EAAgBC,GACpBlC,EAAO,WAAW,QAAQ,EAAE,OAAOkC,EAAqB,MAAM,EAAE,OAAO,KAAK,EAIxEC,EAAmB,IACvBhC,EAAK,KAAKyB,EAAa,EAAGrB,CAAiB,EAIvC6B,EAA0BF,GAC9B/B,EAAK,KAAKgC,EAAiB,EAAGF,EAAaC,CAAmB,CAAC,EAQ3DG,EAAsB,CAACb,EAAkBC,EAAiBa,IAAuB,CAC5F,IAAMC,EAAU,GAAGf,CAAQ,QAAQpB,EAAQ,GAAG,GAE9CH,EAAG,cAAcsC,EAASd,EAAS,CAAE,KAAAa,CAAK,CAAC,EAE3C,GAAI,CACFrC,EAAG,WAAWsC,EAASf,CAAQ,CACjC,OAASgB,EAAO,CACd,MAAAvC,EAAG,OAAOsC,EAAS,CAAE,MAAO,EAAK,CAAC,EAC5BC,CACR,CACF,EAEaC,EAAuB",
|
|
6
|
+
"names": ["crypto", "fs", "os", "path", "process", "ENV_LOAD_FILE", "ENV_CLEAR_FILE", "WARM_CACHE_SUBDIR", "DEFAULT_WARM_TTL_SECONDS", "INFRA_KIT_SESSION_VAR", "INFRA_KIT_ENV_VAR", "INFRA_KIT_ENV_CONFIG_VAR", "INFRA_KIT_ENV_PROJECT_VAR", "INFRA_KIT_ENV_PROJECT_ROOT_VAR", "INFRA_KIT_ENV_LOADED_AT_VAR", "INFRA_KIT_ENV_AUTOLOADED_VAR", "INFRA_KIT_ENV_CLEARED_VAR", "ENV_VAR_LINE_PATTERN", "advanceSingleQuoteState", "line", "startInQuote", "inQuote", "i", "parseVarNamesFromEnvFile", "filePath", "content", "names", "match", "getCacheRoot", "xdg", "base", "getSessionCacheDir", "session", "warmCacheKey", "canonicalProjectDir", "getWarmCacheRoot", "getProjectWarmCacheDir", "atomicWriteFileSync", "mode", "tmpPath", "error", "WORKTREES_DIR_SUFFIX"]
|
|
7
7
|
}
|