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.
- package/esm/main.js +421 -19
- 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.
|
|
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.
|
|
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] =
|
|
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.
|
|
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: [
|
|
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
|
|
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 =
|
|
101178
|
+
buf = Buffer6.from(body, "utf-8");
|
|
100777
101179
|
} else if (body instanceof Blob) {
|
|
100778
|
-
buf =
|
|
101180
|
+
buf = Buffer6.from(await body.arrayBuffer());
|
|
100779
101181
|
} else if (body instanceof ArrayBuffer) {
|
|
100780
|
-
buf =
|
|
101182
|
+
buf = Buffer6.from(body);
|
|
100781
101183
|
} else if (body == null) {
|
|
100782
|
-
buf =
|
|
101184
|
+
buf = Buffer6.alloc(0);
|
|
100783
101185
|
} else {
|
|
100784
|
-
buf =
|
|
101186
|
+
buf = Buffer6.from(JSON.stringify(body), "utf-8");
|
|
100785
101187
|
}
|
|
100786
101188
|
if (opts.stdout) {
|
|
100787
101189
|
process.stdout.write(buf);
|