windmill-cli 1.776.0 → 1.777.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.
Files changed (2) hide show
  1. package/esm/main.js +421 -19
  2. package/package.json +1 -1
package/esm/main.js CHANGED
@@ -16784,7 +16784,7 @@ var init_OpenAPI = __esm(() => {
16784
16784
  PASSWORD: undefined,
16785
16785
  TOKEN: getEnv3("WM_TOKEN"),
16786
16786
  USERNAME: undefined,
16787
- VERSION: "1.776.0",
16787
+ VERSION: "1.777.0",
16788
16788
  WITH_CREDENTIALS: true,
16789
16789
  interceptors: {
16790
16790
  request: new Interceptors,
@@ -27289,7 +27289,7 @@ var init_auth = __esm(async () => {
27289
27289
  });
27290
27290
 
27291
27291
  // src/core/constants.ts
27292
- var WM_FORK_PREFIX = "wm-fork", VERSION = "1.776.0";
27292
+ var WM_FORK_PREFIX = "wm-fork", VERSION = "1.777.0";
27293
27293
 
27294
27294
  // src/utils/git.ts
27295
27295
  var exports_git = {};
@@ -39479,11 +39479,45 @@ function initWebSocket() {
39479
39479
 
39480
39480
  initWebSocket()
39481
39481
 
39482
+ /** A runnable call leaves this page over the WebSocket without touching the DOM,
39483
+ * so the session recorder of \`wmill app dev --recording\` (which frames the app)
39484
+ * has nothing else to tell it a step is still waiting on the backend. Announcing
39485
+ * the request and its answer to the shell mirrors what the deployed runner posts
39486
+ * across the same boundary. */
39487
+ const framed = typeof window !== 'undefined' && window.parent !== window
39488
+
39489
+ function notifyRecorder(type: string, reqId: string) {
39490
+ if (framed) window.parent.postMessage({ type, reqId }, window.location.origin)
39491
+ }
39492
+
39493
+ // A reload takes the previous context and its WebSocket with it, so whatever it
39494
+ // had in flight can never answer. Announcing a fresh module is how the shell
39495
+ // learns those calls are dead: a message posted from the unloading document
39496
+ // would be dropped with the realm that sent it, and this runs before any app
39497
+ // code can issue a call of its own.
39498
+ if (framed) {
39499
+ window.parent.postMessage({ type: 'wmillDevReady' }, window.location.origin)
39500
+ }
39501
+
39502
+ function tracked(type: string, reqId: string, resolve: (v: any) => void, reject: (e: any) => void) {
39503
+ notifyRecorder(type, reqId)
39504
+ let settled = false
39505
+ const done = () => {
39506
+ if (settled) return
39507
+ settled = true
39508
+ notifyRecorder(type + 'Res', reqId)
39509
+ }
39510
+ return {
39511
+ resolve: (v: any) => { done(); resolve(v) },
39512
+ reject: (e: any) => { done(); reject(e) }
39513
+ }
39514
+ }
39515
+
39482
39516
  async function doRequest(type: string, o: object) {
39483
39517
  await wsReady
39484
39518
  return new Promise((resolve, reject) => {
39485
39519
  const reqId = Math.random().toString(36)
39486
- reqs[reqId] = { resolve, reject }
39520
+ reqs[reqId] = tracked(type, reqId, resolve, reject)
39487
39521
  ws?.send(JSON.stringify({ ...o, type, reqId }))
39488
39522
  })
39489
39523
  }
@@ -39530,7 +39564,7 @@ export function streamJob(
39530
39564
  return new Promise(async (resolve, reject) => {
39531
39565
  await wsReady
39532
39566
  const reqId = Math.random().toString(36)
39533
- reqs[reqId] = { resolve, reject, onUpdate }
39567
+ reqs[reqId] = { ...tracked('streamJob', reqId, resolve, reject), onUpdate }
39534
39568
  ws?.send(JSON.stringify({ jobId, type: 'streamJob', reqId }))
39535
39569
  })
39536
39570
  }
@@ -61469,7 +61503,7 @@ var init_openflow = __esm(() => {
61469
61503
  openflow_default = {
61470
61504
  openapi: "3.0.3",
61471
61505
  info: {
61472
- version: "1.776.0",
61506
+ version: "1.777.0",
61473
61507
  title: "OpenFlow Spec",
61474
61508
  contact: {
61475
61509
  name: "Ruben Fiszel",
@@ -70316,8 +70350,8 @@ async function elementsToMap(els, ignore, json, skips, specificItems, branchOver
70316
70350
  }
70317
70351
  }
70318
70352
  if (isRawAppFile(path13)) {
70319
- const suffix = path13.split(getFolderSuffix("raw_app") + SEP9).pop();
70320
- if (suffix?.startsWith("dist/") || suffix == "wmill.d.ts" || suffix == "package-lock.json" || suffix == "DATATABLES.md") {
70353
+ const suffix = path13.split(getFolderSuffix("raw_app") + SEP9).pop()?.replaceAll(SEP9, "/");
70354
+ if (suffix?.startsWith("dist/") || suffix?.startsWith(RECORDINGS_FOLDER + "/") || suffix == "wmill.d.ts" || suffix == "package-lock.json" || suffix == "DATATABLES.md") {
70321
70355
  continue;
70322
70356
  }
70323
70357
  }
@@ -73863,6 +73897,9 @@ async function collectAppFiles(localPath) {
73863
73897
  if (entry.name === APP_BACKEND_FOLDER || entry.name === "node_modules" || entry.name === "dist" || entry.name === ".claude" || entry.name === "sql_to_apply") {
73864
73898
  continue;
73865
73899
  }
73900
+ if (basePath === "/" && entry.name === RECORDINGS_FOLDER) {
73901
+ continue;
73902
+ }
73866
73903
  await readDirRecursive2(fullPath + SEP11, relativePath + "/");
73867
73904
  } else if (entry.isFile()) {
73868
73905
  if (entry.name === "raw_app.yaml" || entry.name === "package-lock.json" || entry.name === "DATATABLES.md" || entry.name === "AGENTS.md" || entry.name === "wmill.d.ts") {
@@ -74026,6 +74063,7 @@ __export(exports_app_metadata, {
74026
74063
  generateLocksCommand: () => generateLocksCommand,
74027
74064
  generateAppLocksInternal: () => generateAppLocksInternal,
74028
74065
  filterWorkspaceDependenciesForApp: () => filterWorkspaceDependenciesForApp,
74066
+ RECORDINGS_FOLDER: () => RECORDINGS_FOLDER,
74029
74067
  APP_BACKEND_FOLDER: () => APP_BACKEND_FOLDER
74030
74068
  });
74031
74069
  import path15 from "node:path";
@@ -74552,7 +74590,7 @@ async function generateLocksCommand(opts, appPath) {
74552
74590
  }
74553
74591
  }
74554
74592
  }
74555
- var import_yaml19, TOP_HASH2 = "__app_hash", APP_BACKEND_FOLDER = "backend";
74593
+ var import_yaml19, TOP_HASH2 = "__app_hash", APP_BACKEND_FOLDER = "backend", RECORDINGS_FOLDER = "recordings";
74556
74594
  var init_app_metadata = __esm(async () => {
74557
74595
  init_colors2();
74558
74596
  init_log();
@@ -74769,12 +74807,246 @@ var init_generate_agents = __esm(async () => {
74769
74807
  generate_agents_default = command9;
74770
74808
  });
74771
74809
 
74810
+ // src/commands/app/devRecorderBundle.gen.ts
74811
+ var DEV_RECORDER_BUNDLE = 'var __wmillRecorder=(()=>{var ne=Object.defineProperty;var Fe=Object.getOwnPropertyDescriptor;var Xe=Object.getOwnPropertyNames;var Be=Object.prototype.hasOwnProperty;var je=(t,o)=>{for(var n in o)ne(t,n,{get:o[n],enumerable:!0})},Ke=(t,o,n,s)=>{if(o&&typeof o=="object"||typeof o=="function")for(let l of Xe(o))!Be.call(t,l)&&l!==n&&ne(t,l,{get:()=>o[l],enumerable:!(s=Fe(o,l))||s.enumerable});return t};var We=t=>Ke(ne({},"__esModule",{value:!0}),t);var mt={};je(mt,{createRawAppRecording:()=>Ie});var G="data-wm-rec-target",D="data-wm-no-record";function Ye(t,o){let n=(u,c,f)=>{let d=f.trim();if(!d||/^(data:|blob:|about:|https?:|\\/\\/|#)/i.test(d))return u;try{return`url(${c}${new URL(d,o).href}${c})`}catch{return u}},s="",l=0;for(;l<t.length;){let u=t[l];if(u===\'"\'||u==="\'"){let c=Ve(t,l);s+=t.slice(l,c),l=c}else if(u==="/"&&t[l+1]==="*"){let c=t.indexOf("*/",l+2),f=c===-1?t.length:c+2;s+=t.slice(l,f),l=f}else{let c=/^url\\(\\s*([\'"]?)([^\'")]+)\\1\\s*\\)/i.exec(t.slice(l));c?(s+=n(c[0],c[1],c[2]),l+=c[0].length):(s+=u,l++)}}return s}function Ve(t,o){let n=t[o],s=o+1;for(;s<t.length;)if(t[s]==="\\\\")s+=2;else{if(t[s]===n)return s+1;s++}return t.length}function C(t){return!!t&&typeof t=="object"&&t.nodeType===1}function g(t,o){return t.tagName===o}function Y(t){return t?"\\u2022\\u2022\\u2022\\u2022\\u2022\\u2022\\u2022\\u2022":""}function re(t,o){let n=[],s=o;for(;s&&s!==t;){let l=s.parentElement;if(!l)return;n.unshift(Array.prototype.indexOf.call(l.children,s)),s=l}return s===t?n:void 0}function ie(t,o){let n=t;for(let s of o)if(n=n?.children[s],!n)return;return n}var ze=new Set(["class","colspan","cols","disabled","height","hidden","id","multiple","open","readonly","rows","rowspan","size","type","width"]);function _(t){return!!t.closest(`[${D}]`)}function Ae(t){let o=t.tagName.toLowerCase(),n=(t.getAttribute("type")??"text").toLowerCase();return`${o==="input"?`input[${n}]`:o} (redacted)`}var Te=String.raw`(?:[-\\w\\u00a0-\\uffff]|\\\\[0-9a-fA-F]{1,6}[ \\t\\r\\n\\f]?|\\\\[^\\r\\n\\f0-9a-fA-F])`,Ge=new RegExp(String.raw`\\.(${Te}+)`,"g"),Je=new RegExp(String.raw`#(${Te}+)`,"g");function Ee(t){return t.replace(/\\\\([0-9a-fA-F]{1,6})[ \\t\\r\\n\\f]?|\\\\([^])/g,(o,n,s)=>n?String.fromCodePoint(parseInt(n,16)):s)}function Qe(t){let o=new Set,n=new Set;for(let s of Array.from(t.querySelectorAll("style"))){if(_(s))continue;let l=s.textContent??"";for(let u of l.matchAll(Ge))o.add(Ee(u[1]));for(let u of l.matchAll(Je))n.add(Ee(u[1]))}return{classes:o,ids:n}}function Ze(t,o){let n=Qe(o),s=[...o.hasAttribute(D)?[o]:[],...Array.from(o.querySelectorAll(`[${D}]`))];for(let l of s){l.replaceChildren(t.createTextNode("\\u2022\\u2022\\u2022")),l.setAttribute(D,"");for(let u of Array.from(l.attributes)){if(u.name===D)continue;let c=u.localName.toLowerCase();if(!ze.has(c))l.removeAttributeNode(u);else if(c==="class"){let f=u.value.split(/\\s+/).filter(d=>d&&n.classes.has(d));f.length?l.setAttribute("class",f.join(" ")):l.removeAttributeNode(u)}else c==="id"&&!n.ids.has(u.value)&&l.removeAttributeNode(u)}}}var et=4e6,tt=8e6;function nt(t,o){let n=t.querySelectorAll("canvas"),s=o.querySelectorAll("canvas");if(n.length!==s.length)return;let l=tt;for(let u=0;u<n.length;u++){let c=n[u];if(!c.width||!c.height)continue;let f=c.width*c.height;if(f>et||f>l)continue;l-=f;let d;try{d=c.toDataURL("image/webp",.85)}catch{continue}if(!d.startsWith("data:image/"))continue;let m=c.getBoundingClientRect();if(!m.width||!m.height)continue;let y=t.defaultView?.getComputedStyle(c).display,v=!y||y==="inline"?"inline-block":y,F=s[u],X=F.getAttribute("style");F.setAttribute("style",`${X?X+";":""}display:${v};box-sizing:border-box;width:${m.width}px;height:${m.height}px;background-image:url("${d}");background-size:100% 100%;background-repeat:no-repeat`)}}function rt(t,o){let n=t.querySelectorAll("select"),s=o.querySelectorAll("select");if(n.length===s.length)for(let l=0;l<n.length;l++){let u=n[l];if(!Array.from(u.selectedOptions).some(f=>_(f)))continue;let c=t.createElement("option");c.setAttribute("selected",""),c.textContent="\\u2022\\u2022\\u2022",s[l].replaceChildren(c)}}function it(t,o){let n="input, textarea, select",s=t.querySelectorAll(n),l=o.querySelectorAll(n);if(s.length===l.length)for(let u=0;u<s.length;u++){let c=s[u],f=l[u];if(c.tagName!==f.tagName)return;if(g(c,"INPUT")){let d=c,m=f;d.type==="checkbox"||d.type==="radio"?d.checked?m.setAttribute("checked",""):m.removeAttribute("checked"):d.type==="password"||_(d)?m.setAttribute("value",Y(d.value)):d.type!=="file"&&m.setAttribute("value",d.value)}else if(g(c,"TEXTAREA"))f.textContent=c.value;else if(g(c,"SELECT")){let d=c,m=f;for(let y=0;y<d.options.length;y++){let v=m.options[y];v&&(d.options[y].selected?v.setAttribute("selected",""):v.removeAttribute("selected"))}}}}function be(t){return Array.from(t).map(o=>{let n=o.styleSheet;if(!n)return o.cssText;try{let s=be(n.cssRules),l=n.media?.mediaText;return l?`@media ${l} {\n${s}\n}`:s}catch{return o.cssText}}).join(`\n`)}function ot(t,o){let n=be(o);t.href&&(n=Ye(n,t.href));let s=t.media?.mediaText;return s&&(n=`@media ${s} {\n${n}\n}`),n}function st(t,o,n){for(let s of Array.from(t.styleSheets)){let l=s.ownerNode;if(!C(l))continue;if(s.disabled){let m=re(o,l),y=m?ie(n,m):void 0;y&&(y.setAttribute("media","not all"),y.tagName==="STYLE"&&(y.textContent=""));continue}if(_(l))continue;let u;try{let m=s.cssRules;if(!m)continue;u=m}catch{continue}let c=re(o,l);if(!c)continue;let f=ie(n,c);if(!f)continue;let d=ot(s,u);if(l.tagName==="LINK"){let m=t.createElement("style");m.textContent=d,f.replaceWith(m)}else l.tagName==="STYLE"&&(f.textContent=d)}}function Se(t,o={}){let n=t.documentElement,s=n.cloneNode(!0);if(it(t,s),st(t,n,s),nt(t,s),rt(t,s),Ze(t,s),o.target){let d=re(n,o.target);(d?ie(s,d):void 0)?.setAttribute(G,"")}s.querySelectorAll("template, noscript").forEach(d=>d.remove()),s.querySelectorAll("script").forEach(d=>d.remove()),s.querySelectorAll(\'meta[http-equiv="refresh" i]\').forEach(d=>d.remove()),s.querySelectorAll("*").forEach(d=>{for(let m of Array.from(d.attributes))m.name.toLowerCase().startsWith("on")&&d.removeAttribute(m.name)});let l=t.defaultView,u=Math.round(l?.scrollY??t.documentElement.scrollTop??0),c=Math.round(l?.scrollX??t.documentElement.scrollLeft??0);if(u>0||c>0){let d=t.createElement("style");d.textContent=`html { margin-top: -${u}px !important; margin-left: -${c}px !important; }`,s.querySelector("head")?.appendChild(d)}let f=s.querySelector("head");if(o.baseHref&&f&&!f.querySelector("base")){let d=t.createElement("base");d.setAttribute("href",o.baseHref),f.prepend(d)}return`<!DOCTYPE html>${s.outerHTML}`}var gt=`[${G}] {\n\toutline: 3px solid #ef4444 !important;\n\toutline-offset: 2px !important;\n\tbox-shadow: 0 0 0 6px rgba(239, 68, 68, 0.25) !important;\n}`;function oe(t){if(!t||_(t))return"";let o=t;return t.querySelector(`[${D}]`)&&(o=t.cloneNode(!0),o.querySelectorAll(`[${D}]`).forEach(n=>n.remove())),(o.textContent??"").replace(/\\s+/g," ").trim()}function ye(t,o=40){let n=oe(t);return n.length>o?`${n.slice(0,o)}\\u2026`:n}function we(t){let o=t.tagName.toLowerCase(),n=(t.getAttribute("type")??"text").toLowerCase(),s=o==="input"?`input[${n}]`:o,l=t.labels?.[0],u=t.getAttribute("aria-label")||(l&&!_(l)?ye(l):"")||(o==="input"&&["button","submit","reset"].includes(n)?t.getAttribute("value"):"")||t.getAttribute("placeholder")||t.getAttribute("title")||ye(t)||t.getAttribute("name")||t.getAttribute("id")||"";return u?`${s} "${u}"`:s}function Re(t){let o=[],n=t,s=0;for(;n&&s<5;){let l=n.tagName.toLowerCase();if(n.id){o.unshift(`#${n.id}`);break}let u=typeof n.className=="string"?n.className.trim().split(/\\s+/).filter(Boolean)[0]:void 0,c=n.parentElement,f=u?`${l}.${u}`:l;if(c){let d=Array.from(c.children).filter(m=>m.tagName===n.tagName);d.length>1&&(f+=`:nth-of-type(${d.indexOf(n)+1})`)}o.unshift(f),n=c,s++}return o.join(" > ")}function Le(t,o,n){switch(t){case"click":return`Clicked ${o}`;case"fill":return`Filled ${o} with "${n??""}"`;case"select":return`Selected "${n??""}" in ${o}`;case"toggle":return n?`${n==="checked"?"Checked":"Unchecked"} ${o}`:`Toggled ${o}`;case"submit":return`Submitted ${o}`;case"key":return`Pressed ${n??"key"} in ${o}`;case"navigate":return n?`Navigated to ${n}`:"Reloaded the app"}}var V=400,ct=3e3,ve=6e4,Ce=800,_e=new Set(["button","submit","reset","image"]),ut=new Set(["range","color","date","time","datetime-local","month","week"]),dt=new Set(["","text","search","url","tel","email","password","number"]),J=200,ft=250,xe=500;function Ie(){let t=!1,o=0,n=0,s="",l,u,c=[],f=[],d=new Map,m=0,y=!1,v=!1,F={width:0,height:0},X="",z=[],A,M,O=0,L,b,h,q=new Set,Q,N=0,B,Z=!1;function ke(e){return new Promise(a=>{let i=()=>{if(N===0||Date.now()-e>=ve||!P()){a();return}setTimeout(i,V)};i()})}let Me=e=>new Promise(a=>setTimeout(a,e));function P(){try{return u?.contentDocument??void 0}catch{return}}function $(e){if(e===void 0)return;let a=d.get(e);if(a!==void 0)return a;if(m+e.length>41943040){y=!0,v=!0;return}let i=f.length;return f.push(e),d.set(e,i),m+=e.length,i}function S(e){if(v)return;let a=P();if(a)try{return Se(a,{target:e,baseHref:X})}catch(i){console.warn("raw app recorder: snapshot failed",i);return}}function Ne(e){return e.replace(` ${G}=""`,"")}function j(){h&&(h.observer.disconnect(),clearTimeout(h.timer),clearTimeout(h.cap),h=void 0)}function se(e){j();let a=P();if(!a)return;let i=()=>{if(N>0&&h&&Date.now()-h.startedAt<ve){clearTimeout(h.timer),h.timer=setTimeout(i,V);return}j(),e.after=$(S())},r=new MutationObserver(()=>{h&&(clearTimeout(h.timer),h.timer=setTimeout(i,V))});r.observe(a,{subtree:!0,childList:!0,attributes:!0,characterData:!0}),h={step:e,observer:r,startedAt:Date.now(),timer:setTimeout(i,V),cap:setTimeout(i,ct)}}function ae(e){if(!h)return;let a=h.step;j();let i=e!==void 0&&(L?.html===e||b?.html===e);a.after=$(i?Ne(e):S())}function x(e,a,i,r,w=!1){if(!t)return;let E=Date.now()-n,p=c[c.length-1],R=!!p&&!!a&&ue(M,a)&&p.kind===e&&(w||Ue(a)&&E-O<ft);if(R||ae(i),c.length>=500&&!R){y=!0,v=!0;return}let T=!!a&&_(a),H=le(a?T?Ae(a):we(a):"the app")??"the app",W=r&&r.length>J?`${r.slice(0,J)}\\u2026`:r,k=!T||!W?W:e==="toggle"?void 0:Y(W),ge=Le(e,H,k);if(R&&p){p.value=k,p.label=ge,O=E,se(p);return}let he={t:E,kind:e,label:ge,target:H,selector:a&&!T?le(Re(a)):void 0,value:k,before:$(i??(e==="key"?S(a):void 0))};c.push(he),i!==void 0&&L?.html===i&&(L=void 0),i!==void 0&&b?.html===i&&(b=void 0),M=a,O=E,o=c.length,se(he)}function Pe(e){let i=e.closest("label")?.control;return!i||e===i||i.contains(e)?!1:!e.closest("a, button, input, select, textarea")}function $e(e){return e.ctrlKey||e.metaKey||e.altKey?!1:e.key.length===1||[" ","Enter","ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End"].includes(e.key)}function ee(e){if(g(e,"SELECT"))return!0;if(!g(e,"INPUT"))return!1;let a=e.type;return!K(e)&&!_e.has(a)}function He(e){return g(e,"BUTTON")?(e.type||"submit")==="submit":g(e,"INPUT")&&["submit","image"].includes(e.type)}function De(e){let a=c[c.length-1];return a?.kind==="key"&&a.value==="Enter"&&!!e&&!!M&&e.contains(M)&&Date.now()-n-O<xe}function Oe(e){return g(e,"TEXTAREA")||e.isContentEditable}function qe(e){return g(e,"BUTTON")||g(e,"SUMMARY")||g(e,"A")&&e.hasAttribute("href")?!0:g(e,"INPUT")&&_e.has(e.type)}function Ue(e){return g(e,"INPUT")&&ut.has(e.type)}function le(e){if(e!==void 0)return e.length>J?`${e.slice(0,J)}\\u2026`:e}function K(e){return g(e,"TEXTAREA")||e.isContentEditable?!0:g(e,"INPUT")&&dt.has(e.type)}function ce(e){let a=g(e,"INPUT")||g(e,"TEXTAREA")?e.value:oe(e);return g(e,"INPUT")&&e.type==="password"||_(e)?Y(a):a}function U(e){let a=L?.el;if(!a)return;if(a===e||a.contains(e)||e.contains(a))return L?.html;let i=e.labels;if(i&&Array.from(i).some(r=>r===a||r.contains(a)))return L?.html}function ue(e,a){if(!e||!a)return!1;if(e===a)return!0;let i=e,r=a;return i.type==="radio"&&r.type==="radio"&&!!i.name&&i.name===r.name&&i.form===r.form}function te(e){if(b)return ue(b.el,e)?b.html:void 0}function I(){if(!A)return;let{el:e,before:a}=A;clearTimeout(A.timer),A=void 0,U(e)!==void 0&&(L=void 0),b?.el===e&&(b=void 0),x("fill",e,a,ce(e))}function de(e){let a=(i,r)=>{e.addEventListener(i,r,!0),z.push(()=>e.removeEventListener(i,r,!0))};a("pointerdown",i=>{let r=C(i.target)?i.target:void 0;r&&(L={el:r,html:S(r)})}),a("click",i=>{let r=C(i.target)?i.target:void 0;r&&(A&&A.el!==r&&I(),!(K(r)||ee(r)||g(r,"OPTION"))&&(i.detail===0&&He(r)&&De(r.closest("form"))||Pe(r)||x("click",r,U(r)??S(r))))}),a("focusin",i=>{let r=C(i.target)?i.target:void 0;L&&(!r||U(r)===void 0)&&(L=void 0)}),a("beforeinput",i=>{let r=C(i.target)?i.target:void 0;!r||!K(r)||A?.el===r||U(r)===void 0&&(b={el:r,html:S(r),repeat:!1})}),a("input",i=>{let r=C(i.target)?i.target:void 0;if(!(!r||!K(r)))if(A&&A.el!==r&&I(),A)clearTimeout(A.timer),A.timer=setTimeout(I,Ce);else{let w=U(r),E=te(r),p=w??E??S(r);ae(p),A={el:r,before:p,timer:setTimeout(I,Ce)}}}),a("change",i=>{let r=C(i.target)?i.target:void 0;if(!r)return;if(K(r)){I();return}A&&I();let w=U(r),E=te(r),p=w??E,R=w===void 0&&E!==void 0&&!!b?.repeat;if(g(r,"SELECT")){let T=Array.from(r.selectedOptions),H=T.map(k=>k.label||k.value).join(", "),W=T.some(k=>_(k));x("select",r,p,W?Y(H):H,R)}else if(g(r,"INPUT")){let T=r;["checkbox","radio"].includes(T.type)?x("toggle",r,p,T.checked?"checked":"unchecked",R):T.type==="file"?x("fill",r,p,Array.from(T.files??[]).map(H=>H.name).join(", ")):x("fill",r,p,ce(r),R)}}),a("submit",i=>{let r=C(i.target)?i.target:void 0;I();let w=c[c.length-1];(w?.kind==="click"||w?.kind==="key"&&w.value==="Enter")&&M&&r&&r.contains(M)&&Date.now()-n-O<xe||x("submit",r,S(r))}),a("keydown",i=>{let r=C(i.target)?i.target:void 0;r&&ee(r)&&$e(i)&&(i.repeat&&b&&te(r)!==void 0?b.repeat=!0:b={el:r,html:S(r),repeat:i.repeat}),!(i.key!=="Enter"&&i.key!=="Escape")&&(i.key==="Enter"&&r&&(ee(r)||qe(r)||Oe(r))||(I(),x("key",r,i.repeat?void 0:S(r),i.key,i.repeat)))})}function fe(){z.forEach(e=>e()),z=[]}function me(e){let a=e.contentWindow,i=E=>{let p=E.data;if(!p||typeof p!="object"||E.source!==window)return;let{type:R,reqId:T}=p;typeof R!="string"||!R.endsWith("Res")||(q.delete(T),N=q.size)},r=()=>{let E=P();!E||E===Q||(a?.addEventListener("message",i),Q=E)},w=E=>{let p=E.data;if(!p||typeof p!="object"||E.source!==a)return;let{type:R,reqId:T}=p;typeof R!="string"||R.endsWith("Res")||T===void 0||(r(),q.add(T),N=q.size)};return window.addEventListener("message",w),r(),()=>{window.removeEventListener("message",w),a?.removeEventListener("message",i),Q=void 0}}function pe(){fe();let e=h?.step;j(),A&&clearTimeout(A.timer),A=void 0,L=void 0,b=void 0;let a=P();if(!a)return;de(a),u&&(B?.(),B=me(u));let i=S();if(e&&(e.after=$(i)),f.length===0){$(i);return}x("navigate",void 0,i,a.location?.hash||void 0)}return{get active(){return t},get stepCount(){return o},get stopping(){return Z},start(e,a){u=e;let i=P();return i?.documentElement?(t=!0,n=Date.now(),s=a.appPath,l=a.workspace,c=[],M=void 0,O=0,o=0,f=[],d=new Map,m=0,y=!1,v=!1,X=typeof window<"u"?window.location.origin:"",F={width:e.clientWidth||i.documentElement.clientWidth,height:e.clientHeight||i.documentElement.clientHeight},i.readyState==="complete"&&i.location?.href!=="about:blank"&&$(S()),de(i),e.addEventListener("load",pe),q.clear(),N=0,B=me(e),!0):(u=void 0,!1)},async stop(){if(I(),fe(),t=!1,h&&N>0){Z=!0;let a=h.startedAt;await ke(a),P()&&await Me(V),Z=!1}if(h){let a=h.step;j(),a.after=$(S())}B?.(),B=void 0,q.clear(),N=0,u?.removeEventListener("load",pe),L=void 0,b=void 0,u=void 0;let e={version:1,type:"app",recorded_at:new Date().toISOString(),app_path:s,workspace:l,total_duration_ms:Date.now()-n,viewport:F,frames:f,steps:c,truncated:y||void 0};return c=[],f=[],d=new Map,m=0,e},download(e){let a=new Blob([JSON.stringify(e)],{type:"application/json"}),i=URL.createObjectURL(a),r=document.createElement("a");r.href=i,r.download=`app-recording-${(e.app_path||"untitled").replace(/\\//g,"-")}-${Date.now()}.json`,r.click(),URL.revokeObjectURL(i)}}}return We(mt);})();\n';
74812
+ var init_devRecorderBundle_gen = () => {};
74813
+
74814
+ // src/commands/app/devRecorder.ts
74815
+ function isOwnOrigin(origin, host) {
74816
+ if (origin === undefined)
74817
+ return true;
74818
+ if (host === undefined)
74819
+ return false;
74820
+ try {
74821
+ return new URL(origin).host === host;
74822
+ } catch {
74823
+ return false;
74824
+ }
74825
+ }
74826
+ function recordingFileName(now, attempt = 0) {
74827
+ const stamp = now.toISOString().slice(0, 23).replace(/[:T.]/g, "-");
74828
+ return `recording-${stamp}${attempt > 0 ? `-${attempt}` : ""}.json`;
74829
+ }
74830
+ function isRecordingFileName(file) {
74831
+ return /^[A-Za-z0-9._-]+\.json$/.test(file) && !file.includes("..");
74832
+ }
74833
+ function createRecorderShellHTML(opts) {
74834
+ const config = JSON.stringify({
74835
+ appPath: opts.appPath,
74836
+ workspace: opts.workspace,
74837
+ playerBaseUrl: opts.playerBaseUrl ?? null,
74838
+ savePath: RECORDER_SAVE_PATH
74839
+ }).replace(/</g, "\\u003c");
74840
+ return `
74841
+ <!DOCTYPE html>
74842
+ <html lang="en">
74843
+ <head>
74844
+ <meta charset="UTF-8">
74845
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
74846
+ <title>Windmill App Dev Recording</title>
74847
+ <style>
74848
+ * { margin: 0; padding: 0; box-sizing: border-box; }
74849
+ html, body { height: 100%; }
74850
+ body {
74851
+ display: flex;
74852
+ flex-direction: column;
74853
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
74854
+ background: #18181b;
74855
+ }
74856
+ #wm-rec-bar {
74857
+ display: flex;
74858
+ align-items: center;
74859
+ gap: 12px;
74860
+ padding: 8px 12px;
74861
+ color: #e4e4e7;
74862
+ font-size: 13px;
74863
+ border-bottom: 1px solid #27272a;
74864
+ flex-wrap: wrap;
74865
+ }
74866
+ #wm-rec-bar button, #wm-rec-bar a.wm-rec-action {
74867
+ font: inherit;
74868
+ display: inline-flex;
74869
+ align-items: center;
74870
+ gap: 6px;
74871
+ padding: 5px 12px;
74872
+ border-radius: 6px;
74873
+ border: 1px solid #3f3f46;
74874
+ background: #27272a;
74875
+ color: #e4e4e7;
74876
+ cursor: pointer;
74877
+ text-decoration: none;
74878
+ }
74879
+ #wm-rec-bar button:hover, #wm-rec-bar a.wm-rec-action:hover { background: #3f3f46; }
74880
+ #wm-rec-bar button:disabled { opacity: 0.5; cursor: not-allowed; }
74881
+ #wm-rec-toggle.recording { background: #dc2626; border-color: #dc2626; color: white; }
74882
+ #wm-rec-toggle.recording:hover { background: #b91c1c; }
74883
+ .wm-rec-dot { width: 8px; height: 8px; border-radius: 50%; background: #dc2626; }
74884
+ #wm-rec-toggle.recording .wm-rec-dot { background: white; }
74885
+ #wm-rec-status { color: #a1a1aa; }
74886
+ #wm-rec-hint { margin-left: auto; color: #71717a; font-size: 12px; }
74887
+ #wm-rec-hint code { background: #27272a; padding: 1px 5px; border-radius: 4px; }
74888
+ #wm-rec-frame { flex: 1; width: 100%; border: 0; background: white; }
74889
+ [hidden] { display: none !important; }
74890
+ </style>
74891
+ </head>
74892
+ <body>
74893
+ <div id="wm-rec-bar">
74894
+ <button id="wm-rec-toggle"><span class="wm-rec-dot"></span><span id="wm-rec-toggle-label">Record</span></button>
74895
+ <span id="wm-rec-status">Not recording</span>
74896
+ <a id="wm-rec-open" class="wm-rec-action" target="_blank" rel="noopener" hidden>Open in player</a>
74897
+ <button id="wm-rec-download" hidden>Download JSON</button>
74898
+ <span id="wm-rec-hint">Passwords are masked. Mark sensitive elements with <code>data-wm-no-record</code></span>
74899
+ </div>
74900
+ <iframe id="wm-rec-frame" src="/"></iframe>
74901
+ <script src="${RECORDER_BUNDLE_PATH}"></script>
74902
+ <script>
74903
+ (function () {
74904
+ var config = ${config};
74905
+ var iframe = document.getElementById('wm-rec-frame');
74906
+ var toggle = document.getElementById('wm-rec-toggle');
74907
+ var toggleLabel = document.getElementById('wm-rec-toggle-label');
74908
+ var status = document.getElementById('wm-rec-status');
74909
+ var openLink = document.getElementById('wm-rec-open');
74910
+ var downloadBtn = document.getElementById('wm-rec-download');
74911
+ var recorder = window.__wmillRecorder.createRawAppRecording();
74912
+ var recording = null;
74913
+ var ticker = null;
74914
+
74915
+ // The dev-server shim posts both the request and its answer up to the
74916
+ // shell; the recorder reads the answer off the framed window, the way the
74917
+ // deployed runner delivers it. Relaying is what makes a step wait for the
74918
+ // job it launched instead of recording the spinner.
74919
+ var pending = [];
74920
+ var stranded = [];
74921
+ window.addEventListener('message', function (e) {
74922
+ var frameWindow = iframe.contentWindow;
74923
+ if (!frameWindow || e.source !== frameWindow) return;
74924
+ var data = e.data;
74925
+ if (!data || typeof data.type !== 'string') return;
74926
+ // A fresh document announced itself, so every call the previous one had
74927
+ // in flight died with its WebSocket.
74928
+ if (data.type === 'wmillDevReady') {
74929
+ stranded = stranded.concat(pending);
74930
+ pending = [];
74931
+ return;
74932
+ }
74933
+ if (data.reqId === undefined) return;
74934
+ if (!/Res$/.test(data.type)) {
74935
+ pending.push(data.reqId);
74936
+ return;
74937
+ }
74938
+ pending = pending.filter(function (id) { return id !== data.reqId });
74939
+ frameWindow.postMessage(data, window.location.origin);
74940
+ });
74941
+
74942
+ // The recorder keeps a reload's outstanding request ids on purpose (a
74943
+ // deployed app is answered by its parent, which survives). Here the answer
74944
+ // died with the frame's WebSocket, so Stop would wait out the whole job
74945
+ // budget unless the shell settles them. Deferred past the load handlers:
74946
+ // the recorder rebinds its listener onto the new document in one of them.
74947
+ iframe.addEventListener('load', function () {
74948
+ if (stranded.length === 0) return;
74949
+ var ids = stranded;
74950
+ stranded = [];
74951
+ setTimeout(function () {
74952
+ var frameWindow = iframe.contentWindow;
74953
+ if (!frameWindow) return;
74954
+ ids.forEach(function (reqId) {
74955
+ frameWindow.postMessage({ type: 'unloadRes', reqId: reqId }, window.location.origin);
74956
+ });
74957
+ }, 0);
74958
+ });
74959
+
74960
+ function setStatus(text) { status.textContent = text; }
74961
+
74962
+ function steps(n) { return n + (n === 1 ? ' step' : ' steps'); }
74963
+
74964
+ function tick() {
74965
+ // Stop can wait a minute on a runnable the last step launched, so the
74966
+ // toolbar has to say what it is waiting for rather than look wedged.
74967
+ if (recorder.stopping) setStatus('Waiting for the last job to finish…');
74968
+ else if (recorder.active) {
74969
+ setStatus('Recording: ' + steps(recorder.stepCount));
74970
+ }
74971
+ }
74972
+
74973
+ function start() {
74974
+ recording = null;
74975
+ openLink.hidden = true;
74976
+ downloadBtn.hidden = true;
74977
+ if (!recorder.start(iframe, { appPath: config.appPath, workspace: config.workspace })) {
74978
+ setStatus('Cannot record: the app document is unreachable');
74979
+ return;
74980
+ }
74981
+ toggle.classList.add('recording');
74982
+ toggleLabel.textContent = 'Stop';
74983
+ tick();
74984
+ ticker = setInterval(tick, 250);
74985
+ }
74986
+
74987
+ async function stop() {
74988
+ toggle.disabled = true;
74989
+ setStatus('Finishing…');
74990
+ // The ticker outlives the click: it is what reports the drain below.
74991
+ recording = await recorder.stop();
74992
+ clearInterval(ticker);
74993
+ ticker = null;
74994
+ toggle.classList.remove('recording');
74995
+ toggleLabel.textContent = 'Record';
74996
+ downloadBtn.hidden = false;
74997
+ setStatus(steps(recording.steps.length) + ' recorded');
74998
+ // Only now: starting a fresh recording drops the one being uploaded.
74999
+ await save();
75000
+ toggle.disabled = false;
75001
+ }
75002
+
75003
+ async function save() {
75004
+ try {
75005
+ var res = await fetch(config.savePath, {
75006
+ method: 'POST',
75007
+ headers: { 'Content-Type': 'application/json' },
75008
+ body: JSON.stringify(recording)
75009
+ });
75010
+ var body = await res.json();
75011
+ if (!res.ok) throw new Error(body.error || res.statusText);
75012
+ setStatus(steps(recording.steps.length) + ' saved to ' + body.file);
75013
+ if (config.playerBaseUrl) {
75014
+ var src = window.location.origin + config.savePath + '/' + body.file;
75015
+ openLink.href = config.playerBaseUrl + 'replay?src=' + encodeURIComponent(src);
75016
+ openLink.hidden = false;
75017
+ }
75018
+ } catch (e) {
75019
+ setStatus('Recorded, but saving failed: ' + (e && e.message ? e.message : e));
75020
+ }
75021
+ }
75022
+
75023
+ toggle.addEventListener('click', function () {
75024
+ if (recorder.active) stop();
75025
+ else start();
75026
+ });
75027
+
75028
+ downloadBtn.addEventListener('click', function () {
75029
+ if (recording) recorder.download(recording);
75030
+ });
75031
+ })();
75032
+ </script>
75033
+ </body>
75034
+ </html>
75035
+ `;
75036
+ }
75037
+ var RECORDER_BUNDLE_PATH = "/__wm_recorder.js", RECORDER_SHELL_PATH = "/__record", RECORDER_SAVE_PATH = "/__recordings";
75038
+ var init_devRecorder = __esm(async () => {
75039
+ init_devRecorderBundle_gen();
75040
+ await init_app_metadata();
75041
+ });
75042
+
74772
75043
  // src/commands/app/dev.ts
74773
75044
  import { sep as SEP13 } from "node:path";
74774
75045
  import * as http2 from "node:http";
74775
75046
  import * as fs13 from "node:fs";
74776
75047
  import * as path17 from "node:path";
74777
75048
  import process17 from "node:process";
75049
+ import { Buffer as Buffer5 } from "node:buffer";
74778
75050
  import { writeFileSync as writeFileSync6 } from "node:fs";
74779
75051
  async function dev(opts, appFolder) {
74780
75052
  GLOBAL_CONFIG_OPT.noCdToRoot = true;
@@ -74867,6 +75139,16 @@ async function dev(opts, appFolder) {
74867
75139
  if (!fs13.existsSync(distDir)) {
74868
75140
  fs13.mkdirSync(distDir);
74869
75141
  }
75142
+ const recordingEnabled = opts.recording ?? false;
75143
+ const recordingsDir = path17.join(process17.cwd(), RECORDINGS_FOLDER);
75144
+ const playerBaseUrl = workspace.remote.endsWith("/") ? workspace.remote : `${workspace.remote}/`;
75145
+ const playerOrigin = (() => {
75146
+ try {
75147
+ return new URL(playerBaseUrl).origin;
75148
+ } catch {
75149
+ return;
75150
+ }
75151
+ })();
74870
75152
  const clients = [];
74871
75153
  function notifyClients() {
74872
75154
  clients.forEach((client) => {
@@ -74970,8 +75252,122 @@ data: reload
74970
75252
  info(colors.gray(`ℹ️ No runnables folder found (will not watch for runnable changes)
74971
75253
  `));
74972
75254
  }
75255
+ const MAX_RECORDING_BYTES = 104857600;
75256
+ function sendJson(res, status, body) {
75257
+ res.writeHead(status, { "Content-Type": "application/json" });
75258
+ res.end(JSON.stringify(body));
75259
+ }
75260
+ async function writeRecording(body) {
75261
+ const now = new Date;
75262
+ for (let attempt = 0;attempt < 100; attempt++) {
75263
+ const file = recordingFileName(now, attempt);
75264
+ try {
75265
+ await fs13.promises.writeFile(path17.join(recordingsDir, file), body, {
75266
+ flag: "wx"
75267
+ });
75268
+ return file;
75269
+ } catch (error2) {
75270
+ if (error2?.code !== "EEXIST")
75271
+ throw error2;
75272
+ }
75273
+ }
75274
+ throw new Error("Could not find a free recording file name");
75275
+ }
75276
+ function saveRecording(req, res) {
75277
+ if (!isOwnOrigin(req.headers.origin, req.headers.host)) {
75278
+ sendJson(res, 403, { error: "Cross-origin recording upload refused" });
75279
+ return;
75280
+ }
75281
+ const chunks = [];
75282
+ let size = 0;
75283
+ let refused = false;
75284
+ req.on("error", (error2) => {
75285
+ if (!refused) {
75286
+ warn(colors.yellow(`Recording upload failed: ${error2.message}`));
75287
+ }
75288
+ });
75289
+ req.on("data", (chunk) => {
75290
+ if (refused)
75291
+ return;
75292
+ size += chunk.length;
75293
+ if (size > MAX_RECORDING_BYTES) {
75294
+ refused = true;
75295
+ res.on("finish", () => req.destroy());
75296
+ sendJson(res, 413, {
75297
+ error: `Recording exceeds ${MAX_RECORDING_BYTES} bytes`
75298
+ });
75299
+ return;
75300
+ }
75301
+ chunks.push(chunk);
75302
+ });
75303
+ req.on("end", async () => {
75304
+ if (refused)
75305
+ return;
75306
+ const body = Buffer5.concat(chunks).toString("utf-8");
75307
+ try {
75308
+ JSON.parse(body);
75309
+ } catch {
75310
+ sendJson(res, 400, { error: "Body is not valid JSON" });
75311
+ return;
75312
+ }
75313
+ let file;
75314
+ try {
75315
+ await fs13.promises.mkdir(recordingsDir, { recursive: true });
75316
+ file = await writeRecording(body);
75317
+ } catch (error2) {
75318
+ error(colors.red(`Failed to save recording: ${error2.message}`));
75319
+ sendJson(res, 500, { error: error2.message });
75320
+ return;
75321
+ }
75322
+ info(colors.green(`\uD83C\uDFAC Recording saved to ${path17.join(RECORDINGS_FOLDER, file)}`));
75323
+ info(colors.gray(` Replay it at ${playerBaseUrl}replay (open the file, or use ?src=)`));
75324
+ sendJson(res, 200, { file });
75325
+ });
75326
+ }
75327
+ function serveRecording(url, res) {
75328
+ const file = url.slice(RECORDER_SAVE_PATH.length + 1);
75329
+ if (!isRecordingFileName(file)) {
75330
+ sendJson(res, 400, { error: "Invalid recording name" });
75331
+ return;
75332
+ }
75333
+ const filePath = path17.join(recordingsDir, file);
75334
+ if (!fs13.existsSync(filePath)) {
75335
+ sendJson(res, 404, { error: "Recording not found" });
75336
+ return;
75337
+ }
75338
+ res.writeHead(200, {
75339
+ "Content-Type": "application/json",
75340
+ ...playerOrigin ? { "Access-Control-Allow-Origin": playerOrigin } : {}
75341
+ });
75342
+ fs13.createReadStream(filePath).on("error", () => res.end()).pipe(res);
75343
+ }
74973
75344
  const server = http2.createServer((req, res) => {
74974
75345
  const url = req.url || "/";
75346
+ if (recordingEnabled) {
75347
+ const pathname = url.split("?")[0];
75348
+ if (pathname === RECORDER_BUNDLE_PATH) {
75349
+ res.writeHead(200, { "Content-Type": "application/javascript" });
75350
+ res.end(DEV_RECORDER_BUNDLE);
75351
+ return;
75352
+ }
75353
+ if (pathname === RECORDER_SAVE_PATH && req.method === "POST") {
75354
+ saveRecording(req, res);
75355
+ return;
75356
+ }
75357
+ if (pathname.startsWith(`${RECORDER_SAVE_PATH}/`) && req.method === "GET") {
75358
+ serveRecording(pathname, res);
75359
+ return;
75360
+ }
75361
+ if (pathname === RECORDER_SHELL_PATH) {
75362
+ res.writeHead(200, { "Content-Type": "text/html" });
75363
+ res.end(createRecorderShellHTML({
75364
+ appPath,
75365
+ workspace: workspaceId,
75366
+ playerBaseUrl
75367
+ }));
75368
+ return;
75369
+ }
75370
+ }
74975
75371
  if (url === "/__events") {
74976
75372
  res.writeHead(200, {
74977
75373
  "Content-Type": "text/event-stream",
@@ -75358,14 +75754,18 @@ data: reload
75358
75754
  }
75359
75755
  server.listen(port, host, () => {
75360
75756
  const url = `http://${host}:${port}`;
75757
+ const openUrl = recordingEnabled ? `${url}${RECORDER_SHELL_PATH}` : url;
75361
75758
  info(colors.bold.green(`\uD83D\uDE80 Dev server running at ${url}`));
75362
75759
  info(colors.cyan(`\uD83D\uDD0C WebSocket server running at ws://${host}:${port}`));
75363
75760
  info(colors.gray(`\uD83D\uDCE6 Serving files from: ${process17.cwd()}`));
75364
- info(colors.gray(`\uD83D\uDD04 Live reload enabled
75365
- `));
75761
+ info(colors.gray(`\uD83D\uDD04 Live reload enabled`));
75762
+ if (recordingEnabled) {
75763
+ info(colors.magenta(`\uD83C\uDFAC Session recording at ${openUrl} : press Record in the toolbar. Recordings are saved to ${RECORDINGS_FOLDER}/`));
75764
+ }
75765
+ info("");
75366
75766
  if (shouldOpen) {
75367
75767
  try {
75368
- openApp(apps.browser, { arguments: [url] }).catch((error2) => {
75768
+ openApp(apps.browser, { arguments: [openUrl] }).catch((error2) => {
75369
75769
  error(colors.yellow(`Failed to open browser automatically: ${error2.message}`));
75370
75770
  });
75371
75771
  info(colors.gray("Opened browser for you"));
@@ -75972,11 +76372,12 @@ var init_dev = __esm(async () => {
75972
76372
  init_app_metadata(),
75973
76373
  init_raw_apps(),
75974
76374
  init_generate_agents(),
75975
- init_resource_folders()
76375
+ init_resource_folders(),
76376
+ init_devRecorder()
75976
76377
  ]);
75977
76378
  command10 = new Command().description("Start a development server for building apps with live reload and hot module replacement").arguments("[app_folder:string]").option("--port <port:number>", "Port to run the dev server on (will find next available port if occupied)").option("--host <host:string>", "Host to bind the dev server to", {
75978
76379
  default: DEFAULT_HOST
75979
- }).option("--entry <entry:string>", "Entry point file (default: index.ts for Svelte/Vue, index.tsx otherwise)").option("--no-open", "Don't automatically open the browser").action(dev);
76380
+ }).option("--entry <entry:string>", "Entry point file (default: index.ts for Svelte/Vue, index.tsx otherwise)").option("--no-open", "Don't automatically open the browser").option("--recording", "Frame the app in a shell with a Record button, to capture a replayable session recording of the app under development").action(dev);
75980
76381
  dev_default = command10;
75981
76382
  });
75982
76383
 
@@ -88940,6 +89341,7 @@ app related commands
88940
89341
  - \`--host <host:string>\` - Host to bind the dev server to
88941
89342
  - \`--entry <entry:string>\` - Entry point file (default: index.ts for Svelte/Vue, index.tsx otherwise)
88942
89343
  - \`--no-open\` - Don't automatically open the browser
89344
+ - \`--recording\` - Frame the app in a shell with a Record button, to capture a replayable session recording of the app under development
88943
89345
  - \`app lint [app_folder:string]\` - Lint a raw app folder to validate structure and buildability
88944
89346
  - \`--fix\` - Attempt to fix common issues (not implemented yet)
88945
89347
  - \`app new\` - create a new raw app from a template
@@ -100684,7 +101086,7 @@ await __promiseAll([
100684
101086
  init_confirm(),
100685
101087
  init_utils()
100686
101088
  ]);
100687
- import { Buffer as Buffer5 } from "node:buffer";
101089
+ import { Buffer as Buffer6 } from "node:buffer";
100688
101090
  import { readFile as readFile4, writeFile as writeFile26 } from "node:fs/promises";
100689
101091
  import { basename as basename11 } from "node:path";
100690
101092
  function formatBytes(n2) {
@@ -100773,15 +101175,15 @@ async function download(opts, fileKey, outputPath) {
100773
101175
  });
100774
101176
  let buf;
100775
101177
  if (typeof body === "string") {
100776
- buf = Buffer5.from(body, "utf-8");
101178
+ buf = Buffer6.from(body, "utf-8");
100777
101179
  } else if (body instanceof Blob) {
100778
- buf = Buffer5.from(await body.arrayBuffer());
101180
+ buf = Buffer6.from(await body.arrayBuffer());
100779
101181
  } else if (body instanceof ArrayBuffer) {
100780
- buf = Buffer5.from(body);
101182
+ buf = Buffer6.from(body);
100781
101183
  } else if (body == null) {
100782
- buf = Buffer5.alloc(0);
101184
+ buf = Buffer6.alloc(0);
100783
101185
  } else {
100784
- buf = Buffer5.from(JSON.stringify(body), "utf-8");
101186
+ buf = Buffer6.from(JSON.stringify(body), "utf-8");
100785
101187
  }
100786
101188
  if (opts.stdout) {
100787
101189
  process.stdout.write(buf);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "windmill-cli",
3
- "version": "1.776.0",
3
+ "version": "1.777.0",
4
4
  "description": "CLI for Windmill",
5
5
  "license": "Apache 2.0",
6
6
  "type": "module",