landfall 0.3.2 → 0.4.0

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.
@@ -0,0 +1,5 @@
1
+ import{A as e,C as t,D as n,E as r,F as i,I as a,L as o,M as s,N as c,O as l,P as u,R as d,S as f,T as ee,_ as p,a as te,b as m,c as ne,d as h,f as g,g as _,h as v,i as y,j as b,k as re,l as ie,m as ae,n as oe,o as se,p as ce,r as le,s as ue,t as x,u as de,v as fe,w as pe,x as S,y as me,z as C}from"./cli.mjs";import{spawn as w,spawnSync as T}from"node:child_process";import E from"node:fs";import{homedir as D}from"node:os";import{dirname as O,isAbsolute as k,join as A,resolve as j}from"node:path";import{randomBytes as M,timingSafeEqual as N}from"node:crypto";import{createServer as P}from"node:http";import{Readable as F}from"node:stream";const I=/^(github\.com|gitlab\.com|bitbucket\.org|codeberg\.org)$/;function L(){let e=u(),t=Object.keys(e.servers).map(t=>({name:t,registered:!0,isDefault:e.default===t})),n=f().filter(t=>!e.servers[t]&&!I.test(t)).map(e=>({name:e,registered:!1,isDefault:!1}));return[...t,...n]}function R(e){try{let t=S(a(e).server);return`${t.user}@${t.hostname}${t.port===22?``:`:${t.port}`}`}catch{return`cannot resolve host`}}function z(e){let[t,n]=e;return e.includes(`--check`)||e.includes(`--key-only`)?!1:t===`firewall`?[`enable`,`disable`,`allow`,`deny`,`update`,`rm`,`remove`].includes(n):t===`traefik`?n===`setup`||n===`use`:t===`env`?e.some(e=>e.includes(`=`)||e===`-u`||e===`--unset`):t===`domains`?e.some(e=>[`-a`,`--add`,`--rm`,`--set`].includes(e)):t===`auth`?e.some(e=>[`-u`,`--user`,`--rm-user`,`--off`].includes(e)):t===`update`||t===`upgrade`||[`setup`,`deploy`,`rollback`,`stop`,`start`,`restart`,`adopt`].includes(t)}function B(e){let t=j(!e||e===`~`?D():c(e)),n=E.readdirSync(t,{withFileTypes:!0}).filter(e=>!e.name.startsWith(`.`)&&(e.isDirectory()||e.isSymbolicLink()&&V(A(t,e.name)))).map(e=>e.name).sort((e,t)=>e.localeCompare(t));return{path:t,parent:O(t)===t?null:O(t),home:D(),dirs:n,detected:v(t)}}const V=e=>{try{return E.statSync(e).isDirectory()}catch{return!1}},H=A(s,`uploads`),U=e=>e.split(/[\\/]+/).filter(e=>e&&e!==`.`&&e!==`..`);async function W(e,t){E.mkdirSync(O(e),{recursive:!0}),E.writeFileSync(e,Buffer.from(await t.arrayBuffer()))}async function he(e){E.mkdirSync(H,{recursive:!0,mode:448});for(let e of E.readdirSync(H)){let t=A(H,e);Date.now()-E.statSync(t).mtimeMs>864e5&&E.rmSync(t,{recursive:!0,force:!0})}let t=E.mkdtempSync(A(H,`u-`)),n=e.getAll(`file`).filter(e=>e instanceof File);if(!n.length)throw Error(`no files in the upload`);let r=e.getAll(`path`).map(String);if(n.length===1&&/\.(zip|tar|tgz|tar\.gz)$/i.test(n[0].name)){let e=A(t,`.upload-`+U(n[0].name).pop());await W(e,n[0]);let r=A(t,`files`);E.mkdirSync(r);let i=/\.zip$/i.test(e),a=i?T(`unzip`,[`-q`,e,`-d`,r]):T(`tar`,[`-xf`,e,`-C`,r]);if(a.error||a.status!==0){let t=i?T(`tar`,[`-xf`,e,`-C`,r]):a;if(t.error||t.status!==0)throw Error(`could not unpack ${n[0].name}`)}return E.rmSync(e),{path:G(r),files:ge(r)}}let i=A(t,`files`);for(let[e,t]of n.entries()){let n=U(r[e]||t.name);n.length&&await W(A(i,...n),t)}return{path:G(i),files:n.length}}function G(e){for(;;){let t=E.readdirSync(e).filter(e=>e!==`__MACOSX`&&e!==`.DS_Store`);if(t.length!==1||!V(A(e,t[0])))return e;e=A(e,t[0])}}function ge(e){return E.readdirSync(e,{withFileTypes:!0,recursive:!0}).filter(e=>e.isFile()).length}function _e(e){if(e!=null&&e!==``){if(typeof e!=`string`||!k(e)||!V(e))throw Error(`cwd must be an existing folder`);return e}}const K=()=>[process.execPath,...process.execArgv,process.argv[1]],q=5e3;function ve(e,t,n={}){let r={...process.env,NO_COLOR:`1`,...n.env};n.password&&Object.assign(r,ne(n.password));let i=w(K()[0],[...K().slice(1),...e],{stdio:[`ignore`,`pipe`,`pipe`],env:r,cwd:n.cwd,detached:!0}),a={argv:e,lines:[],dropped:0,stop(){if(a.code===void 0){a.stopped=!0;try{process.kill(-i.pid,`SIGTERM`)}catch{}}}},o,s=()=>o??=setTimeout(()=>(o=void 0,t()),50),c=()=>{let e=``;return{push(t){let n=(e+t).split(`
2
+ `);e=n.pop();for(let e of n)a.lines.push(e.slice(e.lastIndexOf(`\r`,e.length-2)+1).replace(/\r$/,``));a.lines.length>q&&(a.dropped+=a.lines.splice(0,a.lines.length-q).length),s()},flush:()=>e&&a.lines.push(e)}},l=c(),u=c();return i.stdout.setEncoding(`utf8`).on(`data`,l.push),i.stderr.setEncoding(`utf8`).on(`data`,u.push),i.on(`error`,e=>{a.lines.push(`error: ${e.message}`),a.code=1,t()}),i.on(`close`,e=>{l.flush(),u.flush(),a.code=a.stopped?null:e??1,clearTimeout(o),t()}),a}const ye=E.readFileSync(new URL(`./index.html`,import.meta.url),`utf8`),J=/* @__PURE__ */ new Map,be=/* @__PURE__ */ new Set([`ui`,`web`]);function xe(){let t=A(O(e),`ui-token`);try{let e=E.readFileSync(t,`utf8`).trim();if(e.length>=32)return e}catch{}let n=M(24).toString(`hex`);return E.mkdirSync(O(t),{recursive:!0,mode:448}),E.writeFileSync(t,n+`
3
+ `,{mode:384}),n}const Y=(e,t=200)=>Response.json(e,{status:t,headers:{"cache-control":`no-store`}}),X=(e,t=500)=>Y({error:e?.message??String(e)},t);function Se(e,t){let n=`${Date.now().toString(36)}${Math.random().toString(36).slice(2,6)}`,r=/* @__PURE__ */ new Set,i=ve(e,()=>r.forEach(e=>e()),{password:t.password,cwd:t.cwd,env:{LANDFALL_RUN:n}}),a={id:n,argv:e,server:t.server,startedAt:(/* @__PURE__ */ new Date()).toISOString(),undoable:z(e),job:i,listeners:r};J.set(n,a);for(let[e,t]of J){if(J.size<=50)break;t.job.code!==void 0&&J.delete(e)}return a}const Z=e=>e.map((t,n)=>/^(-p|--([\w-]+-)?password)$/.test(e[n-1])?`***`:t.replace(/^(--([\w-]+-)?password=).*/,`$1***`)),Q=e=>({id:e.id,argv:Z(e.argv),server:e.server,startedAt:e.startedAt,undoable:e.undoable,code:e.job.code,stopped:!!e.job.stopped});function Ce(e,t,n){let r=new TextEncoder,i=t,a=()=>{},o=new ReadableStream({start(t){let o=e=>t.enqueue(r.encode(e)),s=()=>{let{lines:t,dropped:n,code:r}=e.job,s=Math.max(i,n);n+t.length>s&&(o(`data: ${JSON.stringify({from:s,lines:t.slice(s-n)})}\n\n`),i=n+t.length),r!==void 0&&(o(`event: done\ndata: ${JSON.stringify(Q(e))}\n\n`),a())},c=setInterval(()=>o(`: beat
4
+
5
+ `),15e3);a=()=>{clearInterval(c),e.listeners.delete(s);try{t.close()}catch{}},e.listeners.add(s),n.addEventListener(`abort`,a),s()},cancel:()=>a()});return new Response(o,{headers:{"content-type":`text/event-stream`,"cache-control":`no-store`}})}async function we(e,t){let[s,c,d]=t,f=e.method;if(s===`state`&&f===`GET`){let e=u();return Y({version:x,cwd:process.cwd(),project:i(process.cwd())??null,projects:_(),defaultImage:pe,paths:{root:n,sites:l,traefik:re,journal:ee,proxy:r},servers:L().map(t=>({...t,target:R(t.name),saved:e.servers[t.name]??null})),jobs:[...J.values()].reverse().map(Q)})}if(s===`servers`&&c){let{server:t}=a(c);if(f===`GET`&&!d)return Y(await ae(t));if(f===`GET`&&d===`firewall`){let{login:e,probe:n,...r}=await te(t);return Y({...r,asRoot:S(e).user===`root`})}if(f===`GET`&&d===`config`){let e=le(c),[n,r]=await Promise.all([Promise.all(e.domains.map(e=>oe(t,e))),y(t)]);return Y({...e,dns:n,suggest:r})}if(f===`GET`&&d===`history`){let e=(await ue(t)).reverse();return Y(e.map(e=>({...e,pending:se(e).length})))}if(f===`GET`&&d===`facts`){let e=await fe(t),n=await m(t,e);return Y({...e,ips:n,sslipIp:me(n)??null})}if(f===`GET`&&d===`updates`){let n=new URL(e.url).searchParams.get(`refresh`)===`1`,r=await ce(t,n),i=await h(t);return Y({packages:r,images:await g(t),engine:{...i,...de(i)}})}if(f===`GET`&&d===`dns`){let n=(new URL(e.url).searchParams.get(`domains`)??``).split(`,`).filter(e=>b.test(e)).slice(0,50),r=await m(t),[i,a]=await Promise.all([p(n,r),Promise.all(n.map(ie))]);return Y({ips:r,domains:i.map((e,t)=>({...e,https:a[t]}))})}if(f===`POST`&&d==="default"){let e=u();return e.servers[c]??={host:c},e.default=c,o(e),Y({ok:!0})}}if(s===`jobs`){if(f===`POST`&&!c){let t=await e.json(),n=t.argv;if(!Array.isArray(n)||!n.length||!n.every(e=>typeof e==`string`)||be.has(n[0]))return X(`argv must be a list of landfall arguments`,400);let r;try{r=_e(t.cwd)}catch(e){return X(e,400)}return Y(Q(Se(n,{server:t.server,password:t.password||void 0,cwd:r})))}let t=c?J.get(c):void 0;if(c&&!t)return X(`no job ${c}`,404);if(t&&f===`GET`&&d===`events`)return Ce(t,Number(new URL(e.url).searchParams.get(`from`))||0,e.signal);if(t&&f===`GET`&&!d)return Y({...Q(t),lines:t.job.lines,from:t.job.dropped});if(t&&f===`POST`&&d===`stop`)return t.job.stop(),Y(Q(t))}if(s===`fs`&&f===`GET`)return Y(B(new URL(e.url).searchParams.get(`path`)??void 0));if(s===`projects`&&f===`GET`)return Y(_());if(s===`upload`&&f===`POST`){let t=await he(await e.formData());return Y({...t,detected:B(t.path).detected})}if(s===`open`&&f===`POST`){let{url:t}=await e.json();return!t||!/^https?:\/\//.test(t)?X(`not a web address`,400):(C(t),Y({ok:!0}))}return X(`not found`,404)}const $=(e,t)=>e.length===t.length&&N(Buffer.from(e),Buffer.from(t)),Te=e=>(e.headers.get(`cookie`)??``).split(/;\s*/).find(e=>e.startsWith(`landfall=`))?.slice(9)??``;async function Ee(e){t(!0);let n=xe(),r=P(De(async e=>{let t=new URL(e.url),r=e.headers.get(`host`)??``;if(!/^(127\.0\.0\.1|localhost|\[::1\])(:\d+)?$/.test(r))return new Response(`forbidden host`,{status:403});let i=e.headers.get(`origin`);if(i&&new URL(i).host!==r)return new Response(`forbidden origin`,{status:403});if(t.pathname===`/login`)return $(t.searchParams.get(`t`)??``,n)?new Response(null,{status:303,headers:{location:`/`,"set-cookie":`landfall=${n}; HttpOnly; SameSite=Strict; Path=/; Max-Age=31536000`}}):new Response(`wrong or missing token, use the link lf ui printed`,{status:403});if(!$(Te(e),n))return new Response("Open the link `lf ui` printed (it logs this browser in).",{status:401,headers:{"content-type":`text/plain`}});if(t.pathname===`/`)return new Response(ye,{headers:{"content-type":`text/html; charset=utf-8`,"cache-control":`no-store`}});if(t.pathname.startsWith(`/api/`))try{return await we(e,t.pathname.slice(5).split(`/`).filter(Boolean).map(decodeURIComponent))}catch(e){return X(e)}return new Response(`not found`,{status:404})}));r.requestTimeout=0;let i=await new Promise(t=>{r.once(`error`,t),r.listen(e.port,`127.0.0.1`,()=>t(void 0))});if(i){if(i.code!==`EADDRINUSE`)throw i;let t=`http://127.0.0.1:${e.port}/login?t=${n}`;return await fetch(`http://127.0.0.1:${e.port}/api/state`,{headers:{cookie:`landfall=${n}`}}).then(e=>e.ok,()=>!1)?(console.log(`lf ui already runs: ${d.cyan(t)}`),e.open&&C(t),0):(console.error(`${d.red(`error:`)} port ${e.port} is in use; pick another with --port`),1)}let a=`http://127.0.0.1:${r.address().port}/login?t=${n}`;return console.log(`${d.bold(`lf ui`)} ${d.dim(x)} on ${d.cyan(a)}`),console.log(d.dim(`Only this machine can reach it, and only with that link. Ctrl-C stops it (and any running commands).`)),e.open&&C(a),new Promise(e=>{let t=()=>{for(let e of J.values())e.job.stop();r.closeAllConnections(),r.close(),e(0)};process.once(`SIGINT`,t),process.once(`SIGTERM`,t)})}function De(e){return async(t,n)=>{let r=new AbortController;n.on(`close`,()=>r.abort());let i;try{let n=new Headers;for(let[e,r]of Object.entries(t.headers))for(let t of[r??[]].flat())n.append(e,t);let a=t.method===`GET`||t.method===`HEAD`?void 0:F.toWeb(t);i=await e(new Request(`http://${t.headers.host??`127.0.0.1`}${t.url}`,{method:t.method,headers:n,body:a,signal:r.signal,duplex:`half`}))}catch{i=new Response(`bad request`,{status:400})}for(let[e,t]of i.headers)e!==`set-cookie`&&n.setHeader(e,t);let a=i.headers.getSetCookie();if(a.length&&n.setHeader(`set-cookie`,a),n.writeHead(i.status),!i.body)return n.end();let o=F.fromWeb(i.body);n.on(`close`,()=>o.destroy()),o.pipe(n)}}export{Ee as webUi};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "landfall",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "Set up servers and deploy sites behind Traefik with automatic HTTPS",
5
5
  "author": "ph1p <me@ph1p.dev>",
6
6
  "license": "MIT",
@@ -26,14 +26,11 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "bcryptjs": "^3.0.3",
29
- "ink": "^8.0.0",
30
- "react": "^19.3.1",
31
29
  "string-width": "^8.3.0",
32
30
  "yaml": "^2.9.1"
33
31
  },
34
32
  "devDependencies": {
35
33
  "@types/node": "^26.6.5",
36
- "@types/react": "^19.3.0",
37
34
  "tsdown": "^0.23.2",
38
35
  "tsx": "^4.23.15",
39
36
  "typescript": "^7.0.2",
@@ -1,2 +0,0 @@
1
- import{E as e,N as t,T as n,c as r,j as i}from"./cli.mjs";import{spawn as a}from"node:child_process";const o=/^(github\.com|gitlab\.com|bitbucket\.org|codeberg\.org)$/;function s(){let t=i(),n=Object.keys(t.servers).map(e=>({name:e,registered:!0,isDefault:t.default===e})),r=e().filter(e=>!t.servers[e]&&!o.test(e)).map(e=>({name:e,registered:!1,isDefault:!1}));return[...n,...r]}function c(e){try{let r=n(t(e).server);return`${r.user}@${r.hostname}${r.port===22?``:`:${r.port}`}`}catch{return`cannot resolve host`}}function l(e){let[t,n]=e;return e.includes(`--check`)||e.includes(`--key-only`)?!1:t===`firewall`?[`enable`,`disable`,`allow`,`deny`,`update`,`rm`,`remove`].includes(n):t===`traefik`?n===`setup`||n===`use`:t===`env`?e.some(e=>e.includes(`=`)||e===`-u`||e===`--unset`):t===`domains`?e.some(e=>[`-a`,`--add`,`--rm`,`--set`].includes(e)):t===`auth`?e.some(e=>[`-u`,`--user`,`--rm-user`,`--off`].includes(e)):t===`update`||t===`upgrade`||[`setup`,`deploy`,`rollback`,`stop`,`start`,`restart`,`adopt`].includes(t)}const u=()=>[process.execPath,...process.execArgv,process.argv[1]],d=5e3;function f(e,t,n={}){let i={...process.env,NO_COLOR:`1`,...n.env};n.password&&Object.assign(i,r(n.password));let o=a(u()[0],[...u().slice(1),...e],{stdio:[`ignore`,`pipe`,`pipe`],env:i,cwd:n.cwd,detached:!0}),s={argv:e,lines:[],dropped:0,stop(){if(s.code===void 0){s.stopped=!0;try{process.kill(-o.pid,`SIGTERM`)}catch{}}}},c,l=()=>c??=setTimeout(()=>(c=void 0,t()),50),f=()=>{let e=``;return{push(t){let n=(e+t).split(`
2
- `);e=n.pop();for(let e of n)s.lines.push(e.slice(e.lastIndexOf(`\r`,e.length-2)+1).replace(/\r$/,``));s.lines.length>d&&(s.dropped+=s.lines.splice(0,s.lines.length-d).length),l()},flush:()=>e&&s.lines.push(e)}},p=f(),m=f();return o.stdout.setEncoding(`utf8`).on(`data`,p.push),o.stderr.setEncoding(`utf8`).on(`data`,m.push),o.on(`error`,e=>{s.lines.push(`error: ${e.message}`),s.code=1,t()}),o.on(`close`,e=>{p.flush(),m.flush(),s.code=s.stopped?null:e??1,clearTimeout(c),t()}),s}export{c as i,l as n,s as r,f as t};
@@ -1,4 +0,0 @@
1
- import{A as e,B as t,C as n,D as r,F as i,I as a,L as o,M as s,N as c,O as l,P as ee,R as te,S as ne,T as u,U as d,V as f,W as p,_ as m,a as h,d as g,f as _,h as v,i as y,j as b,k as x,l as S,n as C,o as re,p as ie,r as ae,s as oe,t as w,u as se,v as T,w as E,x as ce,z as le}from"./cli.mjs";import{i as D,n as O,r as k,t as A}from"./job-DOi_DnYQ.mjs";import{spawnSync as j}from"node:child_process";import M from"node:fs";import{homedir as N}from"node:os";import{dirname as P,isAbsolute as F,join as I,resolve as L}from"node:path";import{randomBytes as R,timingSafeEqual as z}from"node:crypto";import{createServer as B}from"node:http";import{Readable as V}from"node:stream";function H(t){let n=L(!t||t===`~`?N():e(t)),r=M.readdirSync(n,{withFileTypes:!0}).filter(e=>!e.name.startsWith(`.`)&&(e.isDirectory()||e.isSymbolicLink()&&U(I(n,e.name)))).map(e=>e.name).sort((e,t)=>e.localeCompare(t));return{path:n,parent:P(n)===n?null:P(n),home:N(),dirs:r,detected:m(n)}}const U=e=>{try{return M.statSync(e).isDirectory()}catch{return!1}},W=I(x,`uploads`),G=e=>e.split(/[\\/]+/).filter(e=>e&&e!==`.`&&e!==`..`);async function K(e,t){M.mkdirSync(P(e),{recursive:!0}),M.writeFileSync(e,Buffer.from(await t.arrayBuffer()))}async function ue(e){M.mkdirSync(W,{recursive:!0,mode:448});for(let e of M.readdirSync(W)){let t=I(W,e);Date.now()-M.statSync(t).mtimeMs>864e5&&M.rmSync(t,{recursive:!0,force:!0})}let t=M.mkdtempSync(I(W,`u-`)),n=e.getAll(`file`).filter(e=>e instanceof File);if(!n.length)throw Error(`no files in the upload`);let r=e.getAll(`path`).map(String);if(n.length===1&&/\.(zip|tar|tgz|tar\.gz)$/i.test(n[0].name)){let e=I(t,`.upload-`+G(n[0].name).pop());await K(e,n[0]);let r=I(t,`files`);M.mkdirSync(r);let i=/\.zip$/i.test(e),a=i?j(`unzip`,[`-q`,e,`-d`,r]):j(`tar`,[`-xf`,e,`-C`,r]);if(a.error||a.status!==0){let t=i?j(`tar`,[`-xf`,e,`-C`,r]):a;if(t.error||t.status!==0)throw Error(`could not unpack ${n[0].name}`)}return M.rmSync(e),{path:q(r),files:de(r)}}let i=I(t,`files`);for(let[e,t]of n.entries()){let n=G(r[e]||t.name);n.length&&await K(I(i,...n),t)}return{path:q(i),files:n.length}}function q(e){for(;;){let t=M.readdirSync(e).filter(e=>e!==`__MACOSX`&&e!==`.DS_Store`);if(t.length!==1||!U(I(e,t[0])))return e;e=I(e,t[0])}}function de(e){return M.readdirSync(e,{withFileTypes:!0,recursive:!0}).filter(e=>e.isFile()).length}function fe(e){if(e!=null&&e!==``){if(typeof e!=`string`||!F(e)||!U(e))throw Error(`cwd must be an existing folder`);return e}}const pe=M.readFileSync(new URL(`./index.html`,import.meta.url),`utf8`),J=/* @__PURE__ */ new Map,me=/* @__PURE__ */ new Set([`ui`,`web`,`tui`]);function he(){let e=I(P(r),`ui-token`);try{let t=M.readFileSync(e,`utf8`).trim();if(t.length>=32)return t}catch{}let t=R(24).toString(`hex`);return M.mkdirSync(P(e),{recursive:!0,mode:448}),M.writeFileSync(e,t+`
2
- `,{mode:384}),t}const Y=(e,t=200)=>Response.json(e,{status:t,headers:{"cache-control":`no-store`}}),X=(e,t=500)=>Y({error:e?.message??String(e)},t);function Z(e,t){let n=`${Date.now().toString(36)}${Math.random().toString(36).slice(2,6)}`,r=/* @__PURE__ */ new Set,i=A(e,()=>r.forEach(e=>e()),{password:t.password,cwd:t.cwd,env:{LANDFALL_RUN:n}}),a={id:n,argv:e,server:t.server,startedAt:(/* @__PURE__ */ new Date()).toISOString(),undoable:O(e),job:i,listeners:r};J.set(n,a);for(let[e,t]of J){if(J.size<=50)break;t.job.code!==void 0&&J.delete(e)}return a}const ge=e=>e.map((t,n)=>[`-p`,`--password`].includes(e[n-1])?`***`:t.replace(/^(--password=).*/,`$1***`)),Q=e=>({id:e.id,argv:ge(e.argv),server:e.server,startedAt:e.startedAt,undoable:e.undoable,code:e.job.code,stopped:!!e.job.stopped});function _e(e,t,n){let r=new TextEncoder,i=t,a=()=>{},o=new ReadableStream({start(t){let o=e=>t.enqueue(r.encode(e)),s=()=>{let{lines:t,dropped:n,code:r}=e.job,s=Math.max(i,n);n+t.length>s&&(o(`data: ${JSON.stringify({from:s,lines:t.slice(s-n)})}\n\n`),i=n+t.length),r!==void 0&&(o(`event: done\ndata: ${JSON.stringify(Q(e))}\n\n`),a())},c=setInterval(()=>o(`: beat
3
-
4
- `),15e3);a=()=>{clearInterval(c),e.listeners.delete(s);try{t.close()}catch{}},e.listeners.add(s),n.addEventListener(`abort`,a),s()},cancel:()=>a()});return new Response(o,{headers:{"content-type":`text/event-stream`,"cache-control":`no-store`}})}async function ve(e,r){let[d,f,m]=r,x=e.method;if(d===`state`&&x===`GET`){let e=b();return Y({version:w,cwd:process.cwd(),project:s(process.cwd())??null,projects:T(),defaultImage:i,paths:{root:te,sites:le,traefik:t,journal:a,proxy:o},servers:k().map(t=>({...t,target:D(t.name),saved:e.servers[t.name]??null})),jobs:[...J.values()].reverse().map(Q)})}if(d===`servers`&&f){let{server:t}=c(f);if(x===`GET`&&!m)return Y(await v(t));if(x===`GET`&&m===`firewall`){let{login:e,probe:n,...r}=await y(t);return Y({...r,asRoot:u(e).user===`root`})}if(x===`GET`&&m===`config`){let e=re(f),[n,r]=await Promise.all([Promise.all(e.domains.map(e=>h(t,e))),oe(t)]);return Y({...e,dns:n,suggest:r})}if(x===`GET`&&m===`history`){let e=(await ae(t)).reverse();return Y(e.map(e=>({...e,pending:C(e).length})))}if(x===`GET`&&m===`facts`){let e=await ne(t),r=await E(t,e);return Y({...e,ips:r,sslipIp:n(r)??null})}if(x===`GET`&&m===`updates`){let n=new URL(e.url).searchParams.get(`refresh`)===`1`,r=await ie(t,n),i=await g(t);return Y({packages:r,images:await _(t),engine:{...i,...se(i)}})}if(x===`GET`&&m===`dns`){let n=(new URL(e.url).searchParams.get(`domains`)??``).split(`,`).filter(e=>l.test(e)).slice(0,50),r=await E(t),[i,a]=await Promise.all([ce(n,r),Promise.all(n.map(S))]);return Y({ips:r,domains:i.map((e,t)=>({...e,https:a[t]}))})}if(x===`POST`&&m==="default"){let e=b();return e.servers[f]??={host:f},e.default=f,ee(e),Y({ok:!0})}}if(d===`jobs`){if(x===`POST`&&!f){let t=await e.json(),n=t.argv;if(!Array.isArray(n)||!n.length||!n.every(e=>typeof e==`string`)||me.has(n[0]))return X(`argv must be a list of landfall arguments`,400);let r;try{r=fe(t.cwd)}catch(e){return X(e,400)}return Y(Q(Z(n,{server:t.server,password:t.password||void 0,cwd:r})))}let t=f?J.get(f):void 0;if(f&&!t)return X(`no job ${f}`,404);if(t&&x===`GET`&&m===`events`)return _e(t,Number(new URL(e.url).searchParams.get(`from`))||0,e.signal);if(t&&x===`GET`&&!m)return Y({...Q(t),lines:t.job.lines,from:t.job.dropped});if(t&&x===`POST`&&m===`stop`)return t.job.stop(),Y(Q(t))}if(d===`fs`&&x===`GET`)return Y(H(new URL(e.url).searchParams.get(`path`)??void 0));if(d===`projects`&&x===`GET`)return Y(T());if(d===`upload`&&x===`POST`){let t=await ue(await e.formData());return Y({...t,detected:H(t.path).detected})}if(d===`open`&&x===`POST`){let{url:t}=await e.json();return!t||!/^https?:\/\//.test(t)?X(`not a web address`,400):(p(t),Y({ok:!0}))}return X(`not found`,404)}const $=(e,t)=>e.length===t.length&&z(Buffer.from(e),Buffer.from(t)),ye=e=>(e.headers.get(`cookie`)??``).split(/;\s*/).find(e=>e.startsWith(`landfall=`))?.slice(9)??``;async function be(e){f(!0);let t=he(),n=B(xe(async e=>{let n=new URL(e.url),r=e.headers.get(`host`)??``;if(!/^(127\.0\.0\.1|localhost|\[::1\])(:\d+)?$/.test(r))return new Response(`forbidden host`,{status:403});let i=e.headers.get(`origin`);if(i&&new URL(i).host!==r)return new Response(`forbidden origin`,{status:403});if(n.pathname===`/login`)return $(n.searchParams.get(`t`)??``,t)?new Response(null,{status:303,headers:{location:`/`,"set-cookie":`landfall=${t}; HttpOnly; SameSite=Strict; Path=/; Max-Age=31536000`}}):new Response(`wrong or missing token, use the link lf ui printed`,{status:403});if(!$(ye(e),t))return new Response("Open the link `lf ui` printed (it logs this browser in).",{status:401,headers:{"content-type":`text/plain`}});if(n.pathname===`/`)return new Response(pe,{headers:{"content-type":`text/html; charset=utf-8`,"cache-control":`no-store`}});if(n.pathname.startsWith(`/api/`))try{return await ve(e,n.pathname.slice(5).split(`/`).filter(Boolean).map(decodeURIComponent))}catch(e){return X(e)}return new Response(`not found`,{status:404})}));n.requestTimeout=0;let r=await new Promise(t=>{n.once(`error`,t),n.listen(e.port,`127.0.0.1`,()=>t(void 0))});if(r){if(r.code!==`EADDRINUSE`)throw r;let n=`http://127.0.0.1:${e.port}/login?t=${t}`;return await fetch(`http://127.0.0.1:${e.port}/api/state`,{headers:{cookie:`landfall=${t}`}}).then(e=>e.ok,()=>!1)?(console.log(`lf ui already runs: ${d.cyan(n)}`),e.open&&p(n),0):(console.error(`${d.red(`error:`)} port ${e.port} is in use; pick another with --port`),1)}let i=`http://127.0.0.1:${n.address().port}/login?t=${t}`;return console.log(`${d.bold(`lf ui`)} ${d.dim(w)} on ${d.cyan(i)}`),console.log(d.dim(`Only this machine can reach it, and only with that link. Ctrl-C stops it (and any running commands).`)),e.open&&p(i),new Promise(e=>{let t=()=>{for(let e of J.values())e.job.stop();n.closeAllConnections(),n.close(),e(0)};process.once(`SIGINT`,t),process.once(`SIGTERM`,t)})}function xe(e){return async(t,n)=>{let r=new AbortController;n.on(`close`,()=>r.abort());let i;try{let n=new Headers;for(let[e,r]of Object.entries(t.headers))for(let t of[r??[]].flat())n.append(e,t);let a=t.method===`GET`||t.method===`HEAD`?void 0:V.toWeb(t);i=await e(new Request(`http://${t.headers.host??`127.0.0.1`}${t.url}`,{method:t.method,headers:n,body:a,signal:r.signal,duplex:`half`}))}catch{i=new Response(`bad request`,{status:400})}for(let[e,t]of i.headers)e!==`set-cookie`&&n.setHeader(e,t);let a=i.headers.getSetCookie();if(a.length&&n.setHeader(`set-cookie`,a),n.writeHead(i.status),!i.body)return n.end();let o=V.fromWeb(i.body);n.on(`close`,()=>o.destroy()),o.pipe(n)}}export{be as webUi};
@@ -1,8 +0,0 @@
1
- import{E as e,H as t,M as n,N as r,P as i,T as a,U as o,V as s,b as c,g as l,h as u,i as d,j as f,m as p,n as m,r as ee,t as te,y as h}from"./cli.mjs";import{i as ne,n as re,r as g,t as ie}from"./job-DOi_DnYQ.mjs";import{spawn as ae}from"node:child_process";import _ from"string-width";import{basename as oe}from"node:path";import{Box as v,Text as y,render as b,useApp as se,useInput as ce,useStdout as x}from"ink";import{useEffect as S,useRef as C,useState as w}from"react";import{Fragment as T,jsx as E,jsxs as D}from"react/jsx-runtime";function le(e){return/permission denied/i.test(e)?`No key login yet. Press u to set it up: landfall asks for the password once and installs a key.`:/resolve|not known|nodename/i.test(e)?`Host name not found. Check the entry in ~/.ssh/config.`:/timed out|refused|no route|unreachable/i.test(e)?`Server not reachable. Is it running, and is the ssh port right?`:`Press R to retry.`}function ue({load:e}){return e?e.kind===`loading`?/* @__PURE__ */ E(O,{}):e.kind===`error`?/* @__PURE__ */ E(y,{color:`red`,children:`●`}):e.data.ready?/* @__PURE__ */ E(y,{color:`green`,children:`●`}):/* @__PURE__ */ E(y,{color:`yellow`,children:`●`}):/* @__PURE__ */ E(y,{dimColor:!0,children:`○`})}function O(){let[e,t]=w(0);return S(()=>{let e=setInterval(()=>t(e=>(e+1)%10),80);return()=>clearInterval(e)},[]),/* @__PURE__ */ E(y,{color:`cyan`,children:`⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏`[e]})}function de(){let e=x().stdout,[t,n]=w({cols:e.columns||80,rows:e.rows||24});return S(()=>{let t=()=>n({cols:e.columns,rows:e.rows});return e.on(`resize`,t),()=>void e.off(`resize`,t)},[e]),t}function k(e,t,n,r){let i=r.current;return t<i&&(i=t),t>=i+n&&(i=t-n+1),i=Math.max(0,Math.min(i,e.length-n)),r.current=i,{items:e.slice(i,i+n),offset:i}}function A({title:e,focused:t,width:n,height:r,children:i}){return/* @__PURE__ */ D(v,{flexDirection:`column`,borderStyle:`round`,borderColor:t?`cyan`:`gray`,width:n,flexGrow:+!n,height:r,paddingX:1,overflow:`hidden`,children:[/* @__PURE__ */ E(v,{marginBottom:1,children:e}),i]})}function fe(e,t,n){let r=[[]],i=0;for(let a of e){let e=_(`${a[0]} ${a[1]}`)+3;i&&i+e-3>t&&r.length<n&&(r.push([]),i=0),r[r.length-1].push(a),i+=e}return r}function pe({lines:e}){return e.map((e,t)=>/* @__PURE__ */ E(y,{wrap:`truncate`,children:e.map(([e,t],n)=>/* @__PURE__ */ D(y,{children:[n?` `:` `,/* @__PURE__ */ E(y,{color:`cyan`,bold:!0,children:e}),` `,t]},e))},t))}function j({state:a,onAction:o}){let{exit:s}=se(),{cols:l,rows:h}=de(),[_,b]=w(g),[x,le]=w(Math.min(a.server,_.length)),[O,j]=w(a.site),[M,_e]=w(a.focus),[N,P]=w({kind:`browse`}),[F,I]=w(``),[,L]=w(0),xe=C(0),Te=C(0),Ee=C(0),De=C(0),R=C(void 0),z=n(process.cwd()),B=_[x],V=B&&a.cache.get(B.name),H=V?.kind===`ok`?V.data.sites:[],U=H[Math.min(O,H.length-1)];S(()=>{Object.assign(a,{server:x,site:O,focus:M})},[x,O,M]),S(()=>{if(!B||!a.connect.has(B.name)||a.cache.has(B.name))return;let e=B.name;a.cache.set(e,{kind:`loading`}),L(e=>e+1),u(r(e).server).then(t=>a.cache.set(e,{kind:`ok`,data:t}),t=>a.cache.set(e,{kind:`error`,error:t.message??String(t)})).finally(()=>L(e=>e+1))},[B?.name,V,a.connect.has(B?.name??``)]);let W=(e,t={})=>{let n=t.server??B?.name,r=`${Date.now().toString(36)}${Math.random().toString(36).slice(2,6)}`,i=!1,o=ie(e,()=>{if(o.code!==void 0&&!i){i=!0,n&&a.cache.delete(n),b(g());let s=t.after?.(o)||(n&&re(e)?ke(r,n,e):void 0);P(e=>e.kind===`run`&&e.job===o?{...e,next:s}:e)}L(e=>e+1)},{password:t.password,env:{LANDFALL_RUN:r}});P({kind:`run`,job:o})},Oe=e=>{o({argv:e,server:B?.name}),s()},G=()=>{let e=R.current;if(!e)return P({kind:`browse`});e.kind===`firewall`?Ne(e.server,e.index):Fe(e.server,e.index,e.id)},ke=(e,t,n)=>[{key:`z`,label:`undo this`,run:()=>K(`Undo \`lf ${n.join(` `)}\` on ${t}?`,`Reverts the changes this command recorded in the server's journal.`,`Undo it`,()=>W([`undo`,e,`-s`,t,`-y`],{server:t,after:()=>[]}))}],K=(e,t,n,r)=>P({kind:`choose`,title:e,body:t,index:0,options:[{label:n,pick:r},{label:`Cancel`,pick:G}]}),q=(e,t,n,r=!1,i=``)=>P({kind:`prompt`,title:e,hint:t,value:i,secret:r,submit:n});function J(e,t=e){W([`setup`,...e,`--check`],{server:e[0],after:n=>{if(n.code!==0)return;if(n.lines.some(e=>e.includes(`no key login yet`)))return[{key:`p`,label:`enter the password to install a key`,run:()=>Ae(e,t)}];let r=n.lines.filter(e=>/^\s+→ /.test(e)).length;return[{key:`y`,label:r?`apply ${r} change${r===1?``:`s`}`:`finish setup (nothing to install)`,run:()=>W([`setup`,...e,`-y`],{server:e[0]})},{key:`o`,label:`change options (traefik, firewall, ssh, deploy user)`,run:()=>Y(t)}]}})}function Y(e){let t=f().servers[e[0]]??{host:e[0]},n=[],r=(...e)=>n.push(...e),i=(e,t,n,r=0)=>P({kind:`choose`,title:e,body:t,options:n,index:r}),a=(e,t,n,i,a)=>q(e,t,e=>(e.trim()&&r(`--${i}=${e.trim()}`),a()),!1,n),o=()=>i(`Setup 1/4 · Traefik`,`Reverse proxy with automatic HTTPS for your sites.
2
- If the server already runs a Traefik on 80/443, setup routes sites through it instead.`,[{label:`Own Traefik, ports published by Docker`,hint:`default; Docker forwards 80/443 to the container`,pick:()=>(r(`--no-host-network`),s())},{label:`Own Traefik on the host network`,hint:`sees real client IPs (also IPv6); ufw guards 80/443`,pick:()=>(r(`--host-network`),s())},{label:`No Traefik`,hint:`add it later with lf traefik setup`,pick:()=>(r(`--no-traefik`),u())}],+!!t.hostNetwork),s=()=>a(`Setup · Traefik image`,`Docker image to run. Enter keeps it.`,t.traefikImage??`traefik:v3`,`image`,c),c=()=>a(`Setup · Let's Encrypt e-mail`,`Optional contact for the certificate account. Empty skips.`,t.email??``,`email`,l),l=()=>a(`Setup · Traefik dashboard`,`Optional domain for the dashboard behind basic auth; the password is shown after setup. Empty skips.`,``,`dashboard`,u),u=()=>i(`Setup 2/4 · Firewall`,`ufw: deny incoming except ssh, 80 and 443.
3
- Skipped automatically if it would block ports in use.`,[{label:`Enable ufw`,pick:d},{label:`Leave the firewall alone`,hint:`manage it later with f`,pick:()=>(r(`--no-firewall`),d())}]),d=()=>i(`Setup 3/4 · SSH`,`Turn off password login so only keys work.
4
- Skipped automatically if other users still log in with a password.`,[{label:`Key login only`,pick:p},{label:`Leave sshd alone`,pick:()=>(r(`--no-harden`),p())}]),p=()=>q(`Setup 4/4 · Deploy user`,`landfall deploys as this user (docker group, your key). Empty keeps the login user.`,e=>(r(`--deploy-user=${e.trim()}`),m()),!1,`deploy`),m=()=>J([...e,...n],e);o()}function Ae(e,t=e){let n=e.includes(`--host`)?`root@${e[e.indexOf(`--host`)+1]}`:ne(e[0]);q(`Password for ${n}`,`Used once to install landfall's ssh key (ssh-copy-id). It is not stored; afterwards only the key is used.`,n=>{if(!n)return G();W([`setup`,...e,`--key-only`,`-y`],{password:n,server:e[0],after:n=>n.code===0?J(e,t):[{key:`p`,label:`try the password again`,run:()=>Ae(e,t)}]})},!0)}function je(){q(`Add a server`,`Its name. Use an alias from ~/.ssh/config (like vps), or pick a new name for a fresh machine.`,t=>{if(t=t.trim(),!t)return G();e().includes(t)||f().servers[t]?P({kind:`choose`,title:`${t} is in your ssh config`,index:0,options:[{label:`Set it up now`,hint:`choose options, see what it would change, then apply`,pick:()=>Y([t])},{label:`Only add it to landfall`,hint:`no changes on the server`,pick:()=>W([`server`,`add`,t],{server:t})},{label:`Cancel`,pick:G}]}):q(`Add ${t}`,`IP address or hostname. lf logs in as root once with its password and installs an ssh key.`,e=>e.trim()?Y([t,`--host`,e.trim()]):G())})}function Me(){let e=z?.server??B?.name;if(!e)return I(`Pick a server on the left first.`);if(z)return K(`Deploy ${z.name} to ${e}?`,`From ${process.cwd()}\nServed on ${z.domains.join(`, `)} (settings from .landfall.json)`,`Deploy`,()=>W([`deploy`,`-y`,...z.server?[]:[`-s`,e]],{server:e}));q(`Deploy ${oe(process.cwd())} to ${e}`,`Domain(s) to serve it on, e.g. example.com www.example.com. Their DNS must point at the server.`,t=>{let n=t.split(/[\s,]+/).filter(Boolean);if(!n.length)return G();W([`deploy`,`-y`,`-s`,e,...n.flatMap(e=>[`-d`,e])],{server:e})})}function Ne(e,t=0){R.current={kind:`firewall`,server:e,index:t},P({kind:`firewall`,server:e,index:t});let n=t=>P(n=>n.kind===`firewall`&&n.server===e?{...n,...t,index:Math.min(n.index,t.data?.rules.length??0)}:n);d(r(e).server).then(e=>n({data:e}),e=>n({error:e.message??String(e)}))}function Pe(e,t){if(N.kind!==`firewall`)return;let{server:n,data:r,index:i}=N;if(t.escape||t.leftArrow)return R.current=void 0,G();if(e===`R`)return Ne(n,i);if(!r)return;let a=[`-s`,n],o=r.rules[i],s=String(i+1),c=e=>W(e,{server:n,after:t=>t.code!==0&&!e.includes(`--force`)&&t.lines.some(e=>e.includes(`--force`))?[{key:`f`,label:`do it anyway`,run:()=>c([...e,`--force`])}]:void 0}),l=()=>q(`Open a port`,`port[/proto], e.g. 5432, 51820/udp or 6000:6007/tcp`,e=>{if(!e.trim())return G();q(`Who may reach ${e.trim()}?`,`An address or network like 10.0.0.0/8. Leave empty for anyone.`,t=>c([`firewall`,`allow`,e.trim(),...a,...t.trim()?[`--from`,t.trim()]:[]]))}),u=e=>{R.current={kind:`firewall`,server:n,index:e},P({...N,index:e})};if(t.upArrow||e===`k`)return u(Math.max(0,i-1));if(t.downArrow||e===`j`)return u(Math.min(r.rules.length,i+1));if(e===`t`)return r.ufw===`active`?K(`Turn the firewall on ${n} off?`,`Every listening port becomes reachable from anywhere.`,`Turn it off`,()=>c([`firewall`,`disable`,...a,`-y`])):c([`firewall`,`enable`,...a]);if(e===`a`||!o&&t.return)return l();if(o){if(e===` `)return c([`firewall`,`update`,s,`--action`,o.action===`deny`||o.action===`reject`?`allow`:`deny`,...a]);if(e===`e`||t.return){let e=o.port?`${o.port}${o.proto?`/${o.proto}`:``}`:``;return q(`Edit rule ${s}: ${o.spec}`,`port[/proto]`,t=>q(`Who may reach ${t.trim()||e}?`,`An address or network like 10.0.0.0/8. Leave empty for anyone.`,n=>c([`firewall`,`update`,s,...a,...t.trim()&&t.trim()!==e?[`--port`,t.trim()]:[],`--from`,n.trim()||`any`]),!1,o.from??``),!1,e)}if(t.delete||t.backspace)return K(`Remove rule ${s}?`,o.spec,`Remove it`,()=>c([`firewall`,`rm`,s,...a,`-y`]))}}function Fe(e,t=0,n){R.current={kind:`history`,server:e,index:t,id:n},P({kind:`history`,server:e,index:t});let i=t=>P(r=>{if(r.kind!==`history`||r.server!==e)return r;let i=n?t.runs?.findIndex(e=>e.id===n)??-1:-1;return{...r,...t,index:i>=0?i:Math.min(r.index,Math.max(0,(t.runs?.length??1)-1))}});ee(r(e).server).then(e=>i({runs:e.reverse()}),e=>i({error:e.message??String(e)}))}function Ie(e,t){if(N.kind!==`history`)return;let{server:n,runs:r,index:i}=N;if(t.escape||t.leftArrow)return R.current=void 0,G();if(e===`R`)return Fe(n,i,r?.[i]?.id);if(!r?.length)return;let a=e=>{R.current={kind:`history`,server:n,index:e,id:r[e].id},P({...N,index:e})};if(t.upArrow||e===`k`)return a(Math.max(0,i-1));if(t.downArrow||e===`j`)return a(Math.min(r.length-1,i+1));let o=r[i],s=e=>W([`undo`,...e,`-s`,n,`-y`],{server:n,after:()=>[]});if(e===`z`||t.return){let e=m(o);if(!e.length)return I(`#${o.n} has nothing left to undo`);let t=r.filter(e=>e.n>o.n&&m(e).length),i=[...e.map(e=>`↺ ${e.what}`).reverse(),...t.length?[``,`Later runs build on it and stay: ${t.map(e=>`#${e.n}`).join(`, `)}`]:[]].join(`
5
- `);return K(`Undo #${o.n} ${o.cmd} on ${n}?`,i,`Revert ${e.length} change${e.length>1?`s`:``}`,()=>s([String(o.n)]))}if(e===`U`){let e=r.flatMap(m);return e.length?K(`Undo everything landfall did on ${n}?`,`${e.length} changes, newest first. Sites, Traefik, the deploy user and landfall's firewall rules go away.`,`Revert everything`,()=>s([`--all`])):I(`nothing left to undo`)}}function Le(e,t){if(!B){(t.return||e===`a`)&&je();return}let n=B.name;if(t.return||t.rightArrow||t.tab){if(!a.connect.has(n))return a.connect.add(n),L(e=>e+1);H.length&&_e(`sites`)}else{if((t.delete||t.backspace)&&B.registered)return K(`Forget ${n}?`,`Removes it from landfall (and the Host block landfall wrote to ~/.ssh/config).
6
- Nothing on the server is changed.`,`Forget ${n}`,()=>W([`server`,`rm`,n,`-y`]));switch(e){case`c`:case`u`:return J([n]);case`i`:return W([`server`,`info`,n]);case`t`:return W([`traefik`,`status`,`-s`,n]);case`f`:return Ne(n);case`h`:return Fe(n);case`x`:return Oe([`ssh`,n]);case`a`:return je();case`*`:{let e=f();return e.servers[n]??={host:n},e.default=n,i(e),b(g()),I(`${n} is now the default server`)}}}}function Re(e,n){if(!B||!U)return;let r=[`-s`,B.name],i=U.meta.name;if(n.return)return P({kind:`detail`});if(n.delete||n.backspace)return K(`Delete ${i} from ${B.name}?`,`Stops it and removes all its files and releases.
7
- This cannot be undone.`,`Delete ${i}`,()=>W([`sites`,`rm`,i,...r,`-y`]));switch(e){case`l`:return W([`logs`,i,...r,`-f`]);case`r`:return W([`restart`,i,...r]);case`s`:return W([c(U.status)?`stop`:`start`,i,...r]);case`b`:{let e=p(U.meta).filter(e=>e.at<(U.meta.releases.find(e=>e.id===U.meta.current)?.at??``));return e.length?K(`Roll back ${i}?`,`From ${U.meta.current} to ${e[0].id} (${t(e[0].at)})`,`Roll back`,()=>W([`rollback`,i,...r,`-y`])):I(`${i} has no older release to go back to`)}case`e`:return U.meta.kind===`static`?I(`static sites have no environment variables`):q(`Environment of ${i}`,`KEY=VALUE pairs to set (the container restarts). Leave empty to just show them.`,e=>W([`env`,i,...r,...e.split(/\s+/).filter(Boolean)]));case`o`:{let e=`https://${U.domains[0]}`;return ae(process.platform===`darwin`?`open`:`xdg-open`,[e],{stdio:`ignore`,detached:!0}).unref(),I(`opened ${e}`)}}}let ze=N.kind===`run`?N.job.code===void 0?[[`esc`,`stop`],[`↑↓ space`,`scroll`],[`g G`,`top / end`]]:[...(N.next??[]).map(e=>[e.key,e.label]),[`esc`,`back`],[`↑↓ space`,`scroll`],[`g G`,`top / end`]]:N.kind===`firewall`?[[`esc`,`back`],[`↑↓`,`rule`],[`t`,N.data?.ufw===`active`?`turn off`:`turn on`],[`a`,`add`],[`e`,`edit`],[`space`,`allow ↔ deny`],[`⌫`,`remove`],[`R`,`reload`]]:N.kind===`history`?[[`esc`,`back`],[`↑↓`,`run`],[`z ↵`,`undo run`],[`U`,`undo everything`],[`R`,`reload`]]:N.kind===`choose`?[[`↑↓`,`choose`],[`↵`,`ok`],[`esc`,`back`]]:N.kind===`prompt`?[[`↵`,`ok`],[`esc`,`cancel`]]:N.kind===`help`?[[`↑↓ space`,`scroll`],[`any key`,`close`]]:B?M===`servers`&&N.kind===`browse`?[[`?`,`help`],[`q`,`quit`],[`↑↓`,`server`],[`↵`,B&&a.connect.has(B.name)?`sites`:`connect`],[`u`,`check & set up`],[`d`,`deploy here`],[`i`,`info`],[`t`,`traefik`],[`f`,`firewall`],[`h`,`history`],[`x`,`shell ↗`],[`*`,`default`],[`a`,`add`],...B.registered?[[`⌫`,`forget`]]:[]]:[[`?`,`help`],[`←`,`servers`],[`↑↓`,`site`],[`↵`,`details`],[`l`,`logs`],[`r`,`restart`],[`s`,U&&c(U.status)?`stop`:`start`],[`b`,`roll back`],[`e`,`env`],[`o`,`open`],[`⌫`,`delete`]]:[[`↵`,`add a server`],[`d`,`deploy this folder`],[`?`,`help`],[`q`,`quit`]],X=l<72,Be=fe(ze,l-1,h<16?2:3),Z=Math.max(6,h-3-Be.length),Ve=Math.min(36,Math.floor(l*.4),Math.max(26,..._.map(e=>e.name.length+14))),Q=X&&Z<16,He=X&&!Q?Math.max(5,Math.min(_.length+5,Math.floor(Z*.4))):Z,Ue=X&&!Q?Z-He:Z,We=X?l:l-Ve;ce((e,t)=>{I(``);let n=N.kind===`run`&&N.job.code===void 0;if(t.ctrl&&e===`c`)return n?N.job.stop():s();if(N.kind===`run`){let r=Ce(Z),i=Math.max(0,Se(N.job,l).length-r),a=e=>P({...N,top:e>=i?void 0:Math.max(0,e)}),o=Math.min(N.top??i,i);return t.escape||e===`q`||t.leftArrow?n?N.job.stop():G():t.upArrow||e===`k`?a(o-1):t.downArrow||e===`j`?a(o+1):t.pageUp?a(o-r):t.pageDown||e===` `?a(o+r):e===`g`||t.home?a(0):e===`G`||t.end?a(i):N.next?.find(t=>t.key===e)?.run()}if(N.kind===`prompt`){if(t.escape)return G();if(t.return)return N.submit(N.value);if(t.backspace||t.delete)return P({...N,value:N.value.slice(0,-1)});e&&!t.ctrl&&!t.meta&&P({...N,value:N.value+e})}else{if(N.kind===`choose`){if(t.escape)return G();if(t.upArrow||t.downArrow){let e=t.upArrow?-1:1;return P({...N,index:(N.index+e+N.options.length)%N.options.length})}return t.return?N.options[N.index].pick():void 0}if(N.kind===`help`){let n=Math.max(0,ve(l).length-(Z-4)),r=e=>P({...N,top:Math.max(0,Math.min(n,e))});return t.upArrow||e===`k`?r(N.top-1):t.downArrow||e===`j`?r(N.top+1):e===` `||t.pageDown?r(N.top+Z-4):t.pageUp?r(N.top-(Z-4)):G()}if(N.kind===`firewall`&&e!==`q`&&e!==`?`)return Pe(e,t);if(N.kind===`history`&&e!==`q`&&e!==`?`)return Ie(e,t);if(e===`q`)return s();if(e===`?`)return P({kind:`help`,top:0});if(e===`d`)return Me();if(e===`R`)return a.cache.clear(),b(g()),L(e=>e+1);if(N.kind===`detail`)return t.escape||t.leftArrow?G():Re(e,t);if(M===`servers`)return t.upArrow||e===`k`?(j(0),le(e=>Math.max(0,e-1))):t.downArrow||e===`j`?(j(0),le(e=>Math.min(_.length,e+1))):t.escape?s():Le(e,t);if(t.escape||t.leftArrow)return _e(`servers`);if(t.upArrow||e===`k`)return j(e=>Math.max(0,e-1));if(t.downArrow||e===`j`)return j(e=>Math.min(H.length-1,e+1));Re(e,t)}});let Ge=/* @__PURE__ */ D(v,{justifyContent:`space-between`,children:[/* @__PURE__ */ E(v,{flexShrink:0,children:/* @__PURE__ */ D(y,{children:[/* @__PURE__ */ E(y,{bold:!0,color:`cyan`,children:`landfall`}),` `,/* @__PURE__ */ E(y,{dimColor:!0,children:te})]})}),/* @__PURE__ */ E(v,{flexShrink:1,flexGrow:0,marginLeft:2,overflow:`hidden`,children:/* @__PURE__ */ E(y,{dimColor:!0,wrap:`truncate`,children:z?`this folder: ${z.name} → ${z.server??`?`} · ${z.domains.join(`, `)}`:`this folder: ${oe(process.cwd())} (not deployed yet · d to deploy)`})})]}),$;if(l<40||h<12)$=/* @__PURE__ */ D(y,{color:`yellow`,children:[`Make the terminal bigger: `,l,`×`,h,`, landfall needs 40×12.`]});else if(N.kind===`help`)$=/* @__PURE__ */ E(ye,{height:Z,width:l,top:N.top});else if(N.kind===`firewall`)$=/* @__PURE__ */ E(ge,{mode:N,height:Z,top:Ee});else if(N.kind===`history`)$=/* @__PURE__ */ E(be,{mode:N,height:Z,top:De});else if(N.kind===`run`)$=/* @__PURE__ */ E(we,{job:N.job,top:N.top,height:Z,width:l});else if(N.kind===`choose`||N.kind===`prompt`)$=/* @__PURE__ */ E(A,{title:/* @__PURE__ */ E(y,{bold:!0,children:N.title}),focused:!0,height:Z,children:N.kind===`choose`?/* @__PURE__ */ D(v,{flexDirection:`column`,children:[N.body&&/* @__PURE__ */ E(v,{marginBottom:1,children:/* @__PURE__ */ E(y,{children:N.body})}),N.options.map((e,t)=>/* @__PURE__ */ D(y,{color:t===N.index?`cyan`:void 0,children:[t===N.index?`› `:` `,/* @__PURE__ */ E(y,{bold:t===N.index,children:e.label}),e.hint&&/* @__PURE__ */ D(y,{dimColor:!0,children:[` `,e.hint]})]},e.label))]}):/* @__PURE__ */ D(v,{flexDirection:`column`,children:[/* @__PURE__ */ E(y,{dimColor:!0,children:N.hint}),/* @__PURE__ */ E(v,{marginTop:1,borderStyle:`single`,borderColor:`cyan`,paddingX:1,children:/* @__PURE__ */ D(y,{children:[N.secret?`•`.repeat(N.value.length):N.value,/* @__PURE__ */ E(y,{inverse:!0,children:` `})]})})]})});else{let e=k([..._,void 0],x,He-4,xe),t=_.findIndex(e=>!e.registered);$=/* @__PURE__ */ D(v,{flexDirection:X?`column`:`row`,children:[!(Q&&M===`sites`)&&/* @__PURE__ */ E(A,{title:/* @__PURE__ */ E(y,{bold:!0,children:`Servers`}),focused:M===`servers`,width:X?void 0:Ve,height:He,children:e.items.map((n,r)=>{let i=e.offset+r,o=i===x,s=n?/* @__PURE__ */ D(T,{children:[/* @__PURE__ */ E(ue,{load:a.cache.get(n.name)}),` `,/* @__PURE__ */ E(y,{bold:o,children:n.name}),n.isDefault&&/* @__PURE__ */ E(y,{dimColor:!0,children:` default`}),!n.registered&&i===t&&/* @__PURE__ */ E(y,{dimColor:!0,children:` ssh config`})]}):/* @__PURE__ */ E(y,{color:`green`,children:`+ add a server`});return/* @__PURE__ */ D(y,{wrap:`truncate`,color:o&&M===`servers`?`cyan`:void 0,children:[o?`› `:` `,s]},n?.name??`+`)})}),!(Q&&M===`servers`)&&/* @__PURE__ */ E(he,{entry:B,load:V,sites:H,siteIdx:O,detail:N.kind===`detail`,focused:M===`sites`,height:Ue,width:We,top:Te})]})}let Ke=F||(N.kind===`run`?N.job.code===void 0?`running… esc stops it`:`finished · esc goes back`:B?me(B,V):`Servers are machines you reach with ssh. Sites run on them behind Traefik with HTTPS.`);return/* @__PURE__ */ D(v,{flexDirection:`column`,width:l,height:h-1,children:[Ge,$,/* @__PURE__ */ E(y,{dimColor:!F,color:F?`yellow`:void 0,wrap:`truncate`,children:` `+Ke}),/* @__PURE__ */ E(pe,{lines:Be})]})}function me(e,t){let n=[ne(e.name)];return e.registered||n.push(`from ~/.ssh/config`),t?.kind===`ok`&&(n.push(t.data.ready?`${t.data.sites.length} site${t.data.sites.length===1?``:`s`}`:`not set up for landfall yet`),t.data.others.length&&n.push(`${t.data.others.length} not by landfall`)),n.join(` · `)}function he(e){if(!e.entry)return/* @__PURE__ */ D(A,{title:/* @__PURE__ */ E(y,{bold:!0,children:`Add a server`}),height:e.height,children:[/* @__PURE__ */ E(y,{children:`Press ↵ to add one. Two ways:`}),/* @__PURE__ */ E(y,{children:` `}),/* @__PURE__ */ D(y,{children:[/* @__PURE__ */ E(y,{bold:!0,children:`A server you already reach with ssh`}),` (e.g. `,/* @__PURE__ */ E(y,{color:`cyan`,children:`ssh vps`}),`): enter its alias.`]}),/* @__PURE__ */ D(y,{children:[/* @__PURE__ */ E(y,{bold:!0,children:`A fresh machine`}),`: enter a new name and its IP. landfall asks for the root password once and installs a key.`]}),/* @__PURE__ */ E(y,{children:` `}),/* @__PURE__ */ E(y,{dimColor:!0,children:`Setup installs Docker and Traefik (HTTPS). It checks the server first and never touches what already runs there.`})]});let n=e.entry.name,r=e.load;if(!r)return/* @__PURE__ */ D(A,{title:/* @__PURE__ */ E(y,{bold:!0,children:n}),height:e.height,children:[/* @__PURE__ */ E(y,{dimColor:!0,children:ne(n)}),/* @__PURE__ */ E(y,{children:` `}),/* @__PURE__ */ D(y,{children:[`Not connected. Press`,` `,/* @__PURE__ */ E(y,{color:`cyan`,bold:!0,children:`↵`}),` `,`to connect and show its sites.`]})]});if(r.kind===`loading`)return/* @__PURE__ */ E(A,{title:/* @__PURE__ */ E(y,{bold:!0,children:n}),height:e.height,children:/* @__PURE__ */ D(y,{children:[/* @__PURE__ */ E(O,{}),` connecting…`]})});if(r.kind===`error`)return/* @__PURE__ */ D(A,{title:/* @__PURE__ */ E(y,{bold:!0,children:n}),height:e.height,children:[/* @__PURE__ */ E(y,{color:`red`,children:r.error}),/* @__PURE__ */ E(y,{children:` `}),/* @__PURE__ */ E(y,{children:le(r.error)})]});let{ready:i,traefik:a,proxy:o,sites:s}=r.data,l=/* @__PURE__ */ D(y,{wrap:`truncate`,children:[/* @__PURE__ */ D(y,{bold:!0,children:[`Sites on `,n]}),/* @__PURE__ */ D(y,{dimColor:!0,children:[` `,o,` `]}),a?/* @__PURE__ */ E(y,{color:c(a)?`green`:`red`,children:a}):/* @__PURE__ */ E(y,{color:`yellow`,children:`not installed`})]});if(!i)return/* @__PURE__ */ D(A,{title:l,height:e.height,children:[/* @__PURE__ */ D(y,{children:[n,` is reachable, but landfall has not set it up yet.`]}),/* @__PURE__ */ E(y,{children:` `}),/* @__PURE__ */ D(y,{children:[`Press`,` `,/* @__PURE__ */ E(y,{color:`cyan`,bold:!0,children:`u`}),` `,`to check what is installed. Nothing changes until you confirm the list (Docker, firewall, deploy user, Traefik).`]})]});if(!s.length)return/* @__PURE__ */ D(A,{title:l,height:e.height,children:[/* @__PURE__ */ E(y,{children:`No sites yet.`}),/* @__PURE__ */ E(y,{children:` `}),/* @__PURE__ */ D(y,{children:[`Start landfall inside a project folder and press`,` `,/* @__PURE__ */ E(y,{color:`cyan`,bold:!0,children:`d`}),` `,`to deploy it here. A folder with index.html, dist/ or build/ is served as a static site; a Dockerfile is built on the server.`]})]});let u=Math.min(e.siteIdx,s.length-1);if(e.detail)return/* @__PURE__ */ E(_e,{s:s[u],height:e.height});let d=k(s,u,e.height-4,e.top),f=e.width-8,p=Math.min(Math.max(...s.map(e=>e.meta.name.length))+2,Math.max(8,Math.floor(f*.4))),m=f-p,ee=m>=40,te=m>=24;return/* @__PURE__ */ E(A,{title:l,focused:e.focused,height:e.height,children:d.items.map((n,r)=>{let i=d.offset+r===u&&e.focused;return/* @__PURE__ */ D(v,{children:[/* @__PURE__ */ E(v,{width:2,flexShrink:0,children:/* @__PURE__ */ E(y,{color:i?`cyan`:void 0,children:i?`›`:` `})}),/* @__PURE__ */ E(v,{width:2,flexShrink:0,children:/* @__PURE__ */ E(y,{color:c(n.status)?`green`:`red`,children:c(n.status)?`●`:`○`})}),/* @__PURE__ */ E(v,{width:p,flexShrink:0,paddingRight:1,children:/* @__PURE__ */ E(y,{bold:i,color:i?`cyan`:void 0,wrap:`truncate`,children:n.meta.name})}),ee&&/* @__PURE__ */ E(v,{width:12,children:/* @__PURE__ */ E(y,{dimColor:!0,children:n.kind})}),/* @__PURE__ */ E(v,{flexGrow:1,flexShrink:1,children:/* @__PURE__ */ E(y,{wrap:`truncate`,children:n.domains.join(` `)})}),te&&/* @__PURE__ */ E(v,{width:10,flexShrink:0,justifyContent:`flex-end`,children:/* @__PURE__ */ E(y,{dimColor:!0,children:t(n.meta.updatedAt)})})]},n.meta.name)})})}const M={open:`green`,limited:`cyan`,blocked:`red`,local:void 0};function ge({mode:e,height:t,top:n}){let r=e.data,i=/* @__PURE__ */ D(y,{bold:!0,children:[`Firewall `,/* @__PURE__ */ E(y,{dimColor:!0,children:`on`}),` `,e.server]});if(!r)return/* @__PURE__ */ E(A,{title:i,focused:!0,height:t,children:e.error?/* @__PURE__ */ E(y,{color:`red`,children:e.error}):/* @__PURE__ */ D(y,{children:[/* @__PURE__ */ E(O,{}),` reading ufw…`]})});let o=t-4,s=[...r.rules,void 0],c=k(s,e.index,Math.max(2,Math.min(s.length,Math.floor((o-4)/2))),n),l=Math.max(0,o-c.items.length-5),u=r.ports.slice(0,l),d=r.ufw===`active`?/* @__PURE__ */ E(y,{color:`green`,children:`on`}):/* @__PURE__ */ E(y,{color:`yellow`,children:r.ufw===`missing`?`not installed`:`off`});return/* @__PURE__ */ D(A,{title:i,focused:!0,height:t,children:[/* @__PURE__ */ D(y,{wrap:`truncate`,children:[`ufw `,d,/* @__PURE__ */ D(y,{dimColor:!0,children:[` · `,`incoming without a rule: `,r.policy===`ACCEPT`?`allowed`:`blocked`,` · `,`ssh port `,r.sshPort,a(r.login).user===`root`?` · as root`:``]})]}),/* @__PURE__ */ D(v,{marginTop:1,flexDirection:`column`,children:[/* @__PURE__ */ E(y,{bold:!0,children:`Rules`}),c.items.map((t,n)=>{let r=c.offset+n,i=r===e.index;return/* @__PURE__ */ D(y,{wrap:`truncate`,color:i?`cyan`:void 0,children:[i?`› `:` `,t?/* @__PURE__ */ D(T,{children:[/* @__PURE__ */ D(y,{dimColor:!0,children:[String(r+1).padStart(2),` `]}),/* @__PURE__ */ E(y,{color:t.action===`deny`||t.action===`reject`?`red`:`green`,children:t.action.padEnd(6)}),t.spec.replace(/^\w+\s+/,``)]}):/* @__PURE__ */ E(y,{color:`green`,children:`+ add a rule`})]},t?.spec??`+`)})]}),/* @__PURE__ */ D(v,{marginTop:1,flexDirection:`column`,children:[/* @__PURE__ */ E(y,{bold:!0,children:`Listening ports`}),u.map(e=>/* @__PURE__ */ D(y,{wrap:`truncate`,children:[` `,e.port.padEnd(8),e.proc.padEnd(17),/* @__PURE__ */ E(y,{color:M[e.state],dimColor:e.state===`local`,children:e.why})]},`${e.port} ${e.proc}`)),r.ports.length>u.length&&/* @__PURE__ */ D(y,{dimColor:!0,children:[` … `,r.ports.length-u.length,` more (lf firewall status -s `,e.server,`)`]})]})]})}function _e({s:e,height:n}){let r=e.meta,i=p(r).slice(0,Math.max(1,n-10));return/* @__PURE__ */ D(A,{title:/* @__PURE__ */ D(y,{children:[/* @__PURE__ */ E(y,{bold:!0,children:r.name}),` `,/* @__PURE__ */ E(y,{dimColor:!0,children:e.kind}),` `,/* @__PURE__ */ E(y,{color:c(e.status)?`green`:`red`,children:e.status})]}),focused:!0,height:n,children:[e.domains.map(e=>/* @__PURE__ */ D(y,{color:`cyan`,children:[`https://`,e]},e)),/* @__PURE__ */ E(y,{dimColor:!0,children:r.adopted??l(r.name)}),/* @__PURE__ */ E(y,{children:` `}),/* @__PURE__ */ E(y,{bold:!0,children:`Services`}),e.services.map(e=>/* @__PURE__ */ D(y,{children:[` `,e.service,` `,/* @__PURE__ */ E(y,{color:c(e.status)?`green`:h(e.status)?void 0:`red`,dimColor:h(e.status),children:e.status})]},e.container)),!e.services.length&&/* @__PURE__ */ E(y,{dimColor:!0,children:` none running`}),/* @__PURE__ */ E(y,{children:` `}),/* @__PURE__ */ E(y,{bold:!0,children:`Releases`}),i.map(e=>/* @__PURE__ */ D(y,{children:[e.id===r.current?/* @__PURE__ */ E(y,{color:`green`,children:`● `}):` `,e.id,` `,/* @__PURE__ */ E(y,{dimColor:!0,children:t(e.at)}),e.services?.length?/* @__PURE__ */ D(y,{dimColor:!0,children:[` `,e.services.map(e=>e.name).join(` `)]}):null,e.id===r.current?/* @__PURE__ */ E(y,{dimColor:!0,children:` (live)`}):null]},e.id))]})}const N=[[`u`,`check, then set up / repair on y`],[`i`,`OS, memory, disk, containers`],[`t`,`traefik status and routes`],[`f`,`firewall: rules, ports, on / off`],[`h`,`history: what landfall changed, undo it`],[`x`,`open a shell (leaves the dashboard)`],[`*`,`make default server`],[`a`,`add a server`]],P=[[`↵`,`details and releases`],[`l`,`follow logs (esc stops)`],[`r / s`,`restart / stop or start`],[`b`,`roll back to previous release`],[`e`,`environment variables`],[`o`,`open in browser`],[`⌫`,`delete site / forget server`]],F=[`Servers are machines you reach with ssh (anything in ~/.ssh/config works). Set up installs Docker and Traefik there; Traefik gets HTTPS certificates from Let's Encrypt for every site automatically.`,`Sites are deployed from a project folder: static files, a Dockerfile, or an image. Each deploy is a release you can roll back to. .landfall.json in the folder remembers where it goes.`,`Every change landfall makes is written to /opt/landfall/journal.log on the server. h shows it; z reverts a run, also right after a command finished.`];function I(e,t){let n=[],r=``;for(let i of e.split(` `))r&&_(`${r} ${i}`)>t?(n.push(r),r=i):r=r?`${r} ${i}`:i;return[...n,r]}function ve(e){let t=e-4,n=(e,t,n=10)=>/* @__PURE__ */ D(y,{wrap:`truncate`,children:[/* @__PURE__ */ E(y,{color:`cyan`,bold:!0,children:e.padEnd(n)}),t]}),r=[];for(let e of F)r.push(...I(e,t).map(e=>/* @__PURE__ */ E(y,{wrap:`truncate`,children:e})),/* @__PURE__ */ E(y,{children:` `}));if(t>=92){r.push(/* @__PURE__ */ D(y,{children:[/* @__PURE__ */ E(y,{bold:!0,children:`Servers`.padEnd(46)}),/* @__PURE__ */ E(y,{bold:!0,children:`Sites`})]}));for(let e=0;e<Math.max(N.length,P.length);e++){let[t,i]=[N[e],P[e]];r.push(/* @__PURE__ */ D(v,{children:[/* @__PURE__ */ E(v,{width:46,children:t?n(...t):/* @__PURE__ */ E(y,{children:` `})}),i?n(...i):/* @__PURE__ */ E(y,{children:` `})]}))}}else r.push(/* @__PURE__ */ E(y,{bold:!0,children:`Servers`}),...N.map(e=>n(...e)),/* @__PURE__ */ E(y,{children:` `}),/* @__PURE__ */ E(y,{bold:!0,children:`Sites`}),...P.map(e=>n(...e)));return r.push(/* @__PURE__ */ E(y,{children:` `}),n(`d`,`deploy the folder landfall was started in`),n(`R`,`reload`),/* @__PURE__ */ E(y,{children:` `})),r.push(...I("Commands run inside the dashboard and show their output; esc stops or goes back. Each is a normal landfall command, shown as `$ lf …`, so the same thing works without the dashboard.",t).map(e=>/* @__PURE__ */ E(y,{dimColor:!0,wrap:`truncate`,children:e}))),r}function ye({height:e,width:t,top:n}){let r=ve(t),i=Math.max(1,e-4),a=Math.min(n,Math.max(0,r.length-i)),o=r.length-a-i;return/* @__PURE__ */ E(A,{title:/* @__PURE__ */ D(y,{children:[/* @__PURE__ */ E(y,{bold:!0,children:`How landfall works`}),o>0&&/* @__PURE__ */ D(y,{dimColor:!0,children:[` ↓ `,o,` more`]})]}),focused:!0,height:e,children:r.slice(a,a+i).map((e,t)=>/* @__PURE__ */ E(v,{children:e},a+t))})}function be({mode:e,height:n,top:r}){let i=/* @__PURE__ */ D(y,{bold:!0,children:[`History `,/* @__PURE__ */ E(y,{dimColor:!0,children:`on`}),` `,e.server,` `,/* @__PURE__ */ E(y,{dimColor:!0,children:`· /opt/landfall/journal.log`})]}),{runs:a}=e;if(!a?.length)return/* @__PURE__ */ E(A,{title:i,focused:!0,height:n,children:e.error?/* @__PURE__ */ E(y,{color:`red`,children:e.error}):a?/* @__PURE__ */ D(y,{children:[`landfall has not changed anything on `,e.server,` yet.`]}):/* @__PURE__ */ D(y,{children:[/* @__PURE__ */ E(O,{}),` reading the journal…`]})});let o=n-4,s=a[e.index],c=k(a,e.index,Math.max(1,Math.min(a.length,Math.max(Math.ceil(o/2),o-s.steps.length-1))),r),l=Math.max(1,o-c.items.length-2),u=s.steps.slice(0,s.steps.length>l?l-1:l);return/* @__PURE__ */ D(A,{title:i,focused:!0,height:n,children:[c.items.map((n,r)=>{let i=c.offset+r===e.index,a=m(n).length;return/* @__PURE__ */ D(y,{wrap:`truncate`,color:i?`cyan`:void 0,children:[i?`› `:` `,/* @__PURE__ */ E(y,{bold:!0,children:`#${n.n}`.padEnd(5)}),/* @__PURE__ */ E(y,{dimColor:!0,children:t(n.time).padEnd(9)}),/* @__PURE__ */ E(y,{bold:i,dimColor:!a,children:n.cmd||`?`}),a?/* @__PURE__ */ D(y,{color:`cyan`,children:[` ↺`,a]}):null,/* @__PURE__ */ D(y,{dimColor:!0,children:[` `,n.actor]})]},n.id)}),/* @__PURE__ */ D(v,{marginTop:1,flexDirection:`column`,children:[u.map(e=>/* @__PURE__ */ D(y,{wrap:`truncate`,children:[` `,e.reverts?/* @__PURE__ */ D(y,{dimColor:!0,children:[`↩ `,e.what]}):e.undone?/* @__PURE__ */ D(y,{dimColor:!0,children:[`✓ `,e.what,` (undone)`]}):e.undo?/* @__PURE__ */ D(T,{children:[/* @__PURE__ */ E(y,{color:`cyan`,children:`↺`}),` `,e.what]}):/* @__PURE__ */ D(y,{dimColor:!0,children:[`· `,e.what]})]},e.line)),s.steps.length>u.length&&/* @__PURE__ */ D(y,{dimColor:!0,children:[` … `,s.steps.length-u.length,` more (lf history -s `,e.server,`)`]})]})]})}const L=e=>/^==> /.test(e)?`cyan`:/^\s*✓/.test(e)?`green`:/^\s*!/.test(e)?`yellow`:/^\s*(error|✗)/i.test(e)?`red`:void 0;function xe(e,t){let n=[],r=``;for(let i of e)_(r+i)>t&&(n.push(r),r=``),r+=i;return n.push(r),n}const Se=(e,t)=>e.lines.flatMap(e=>xe(e,Math.max(10,t-4)).map(t=>({r:t,color:L(e)}))),Ce=e=>Math.max(1,e-4);function we({job:e,top:t,height:n,width:r}){let i=Se(e,r),a=Ce(n),o=Math.max(0,i.length-a),s=Math.min(t??o,o),c=i.slice(s,s+a),l=i.length-s-c.length,u=e.code===void 0?/* @__PURE__ */ E(O,{}):e.code===0?/* @__PURE__ */ E(y,{color:`green`,children:`✓ done`}):e.code===null?/* @__PURE__ */ E(y,{color:`yellow`,children:`stopped`}):/* @__PURE__ */ D(y,{color:`red`,children:[`✗ exit `,e.code]});return/* @__PURE__ */ E(A,{title:/* @__PURE__ */ D(y,{wrap:`truncate`,children:[/* @__PURE__ */ D(y,{bold:!0,children:[`$ lf `,e.argv.join(` `)]}),` `,u,l>0&&/* @__PURE__ */ D(y,{dimColor:!0,children:[` ↓ `,l,` more line`,l===1?``:`s`]})]}),focused:!0,height:n,children:c.map(({r:e,color:t},n)=>/* @__PURE__ */ E(y,{color:t,wrap:`truncate`,children:e||` `},n))})}async function Te(e){let t={server:0,site:0,focus:`servers`,cache:/* @__PURE__ */ new Map,connect:/* @__PURE__ */ new Set};for(;;){let n;s(!0),process.stdout.write(`\x1B[?1049h\x1B[H`);try{await b(/* @__PURE__ */ E(j,{state:t,onAction:e=>n=e}),{exitOnCtrlC:!1}).waitUntilExit()}finally{process.stdout.write(`\x1B[?1049l`),s(!1)}if(!n)return 0;console.log(o.dim(`$ lf ${n.argv.join(` `)}\n`));let r=()=>{};process.on(`SIGINT`,r);let i=await e(n.argv).finally(()=>process.off(`SIGINT`,r));n.server&&t.cache.delete(n.server),await Ee(i===0?o.green(`✓ done`):o.red(`✗ failed`))}}function Ee(e){process.stdout.write(`\n${e} ${o.dim(`press any key to go back to the dashboard`)}`);let{stdin:t}=process;return new Promise(e=>{t.setRawMode(!0),t.resume(),t.once(`data`,()=>{t.setRawMode(!1),t.pause(),process.stdout.write(`
8
- `),e()})})}export{Te as dashboard};