@juuno-sdk/cli 3.0.0 → 4.0.1

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@juuno-sdk/cli",
3
- "version": "3.0.0",
4
- "description": "Official CLI for Juuno external app development - simulator, deployment, and tools",
3
+ "version": "4.0.1",
4
+ "description": "Official CLI for Juuno external app development: deploy, list and inspect apps",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "bin": {
@@ -18,27 +18,29 @@
18
18
  "import": "./dist/index.js"
19
19
  }
20
20
  },
21
- "dependencies": {
22
- "inquirer": "^14.0.2",
23
- "jszip": "^3.10.1",
24
- "@juuno-sdk/app-sdk": "^3.0.0"
21
+ "scripts": {
22
+ "dev": "pnpm build && node ./bin/cli.js",
23
+ "prepack": "pnpm build",
24
+ "build": "pnpm build:cli",
25
+ "build:cli": "tsc --project tsconfig.build.json",
26
+ "type-check": "tsc --project tsconfig.build.json --noEmit",
27
+ "test": "pnpm test:unit",
28
+ "test:unit": "vitest run",
29
+ "test:unit:watch": "vitest"
25
30
  },
26
- "peerDependencies": {
27
- "vue": "^3.5.41"
31
+ "dependencies": {
32
+ "inquirer": "catalog:",
33
+ "jszip": "catalog:",
34
+ "semver": "catalog:"
28
35
  },
29
36
  "devDependencies": {
30
- "@tsconfig/node22": "^22.0.5",
31
- "@types/node": "^26.2.0",
32
- "@vitejs/plugin-vue": "^6.0.8",
33
- "@vue/tsconfig": "^0.9.1",
34
- "typescript": "^5.9.3",
35
- "vite": "^8.2.1",
36
- "vitest": "^4.1.10",
37
- "vue": "^3.5.41",
38
- "vue-tsc": "^3.3.9",
39
- "@juuno/simulator-ui": "1.0.0",
40
- "@juuno/service-vitejs-plugins": "0.0.1",
41
- "@juuno/eslint-config": "0.0.1"
37
+ "@juuno/eslint-config": "workspace:*",
38
+ "@tsconfig/node22": "catalog:",
39
+ "@types/node": "catalog:",
40
+ "@types/semver": "catalog:",
41
+ "@vue/tsconfig": "catalog:",
42
+ "typescript": "catalog:",
43
+ "vitest": "catalog:"
42
44
  },
43
45
  "keywords": [
44
46
  "juuno",
@@ -52,15 +54,5 @@
52
54
  "publishConfig": {
53
55
  "registry": "https://registry.npmjs.org/",
54
56
  "access": "public"
55
- },
56
- "scripts": {
57
- "dev": "pnpm build && node ./bin/cli.js",
58
- "build": "pnpm build:cli && pnpm build:simulator",
59
- "build:cli": "tsc --project tsconfig.build.json",
60
- "build:simulator": "vite build",
61
- "type-check": "vue-tsc --project tsconfig.build.json --noEmit --composite false && vue-tsc --project tsconfig.vitest.json --noEmit --composite false",
62
- "test": "pnpm test:unit",
63
- "test:unit": "vitest run",
64
- "test:unit:watch": "vitest"
65
57
  }
66
- }
58
+ }
@@ -1 +0,0 @@
1
- import{defineComponent as V,ref as w,computed as M,onMounted as j,onUnmounted as D,createElementBlock as d,openBlock as r,normalizeClass as m,createElementVNode as e,createCommentVNode as A,toDisplayString as _,createTextVNode as O,withDirectives as R,createBlock as $,resolveDynamicComponent as U,vShow as B,shallowRef as T,reactive as I,unref as J,markRaw as P,createApp as q}from"vue";import{provideAppConfigContext as z}from"@juuno-sdk/app-sdk";(function(){const y=document.createElement("link").relList;if(y&&y.supports&&y.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))b(o);new MutationObserver(o=>{for(const l of o)if(l.type==="childList")for(const n of l.addedNodes)n.tagName==="LINK"&&n.rel==="modulepreload"&&b(n)}).observe(document,{childList:!0,subtree:!0});function h(o){const l={};return o.integrity&&(l.integrity=o.integrity),o.referrerPolicy&&(l.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?l.credentials="include":o.crossOrigin==="anonymous"?l.credentials="omit":l.credentials="same-origin",l}function b(o){if(o.ep)return;o.ep=!0;const l=h(o);fetch(o.href,l)}})();const H="modulepreload",W=function(a){return"/"+a},N={},F=function(y,h,b){let o=Promise.resolve();if(h&&h.length>0){let n=function(s){return Promise.all(s.map(p=>Promise.resolve(p).then(x=>({status:"fulfilled",value:x}),x=>({status:"rejected",reason:x}))))};document.getElementsByTagName("link");const i=document.querySelector("meta[property=csp-nonce]"),f=(i==null?void 0:i.nonce)||(i==null?void 0:i.getAttribute("nonce"));o=n(h.map(s=>{if(s=W(s),s in N)return;N[s]=!0;const p=s.endsWith(".css"),x=p?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${s}"]${x}`))return;const k=document.createElement("link");if(k.rel=p?"stylesheet":H,p||(k.as="script"),k.crossOrigin="",k.href=s,f&&k.setAttribute("nonce",f),document.head.appendChild(k),p)return new Promise((E,L)=>{k.addEventListener("load",E),k.addEventListener("error",()=>L(new Error(`Unable to preload CSS for ${s}`)))})}))}function l(n){const i=new Event("vite:preloadError",{cancelable:!0});if(i.payload=n,window.dispatchEvent(i),!i.defaultPrevented)throw n}return o.then(n=>{for(const i of n||[])i.status==="rejected"&&l(i.reason);return y().catch(l)})},K={class:"header"},X={class:"header-left"},G={class:"app-info"},Q={class:"app-name"},Y={key:0,class:"app-version"},Z={class:"header-right"},ee={key:0,class:"status-badge live"},te={key:0,width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},oe={key:1,width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},ne={key:0,class:"loading-state"},le={class:"url-info"},se={key:1,class:"error-state"},ie={class:"error-message"},ae={class:"error-details"},re={key:2,class:"main-content"},ue={class:"tabs"},ce={key:0,class:"tab-controls"},de={class:"tab-panels"},ve={class:"player-frame"},pe={key:1,class:"placeholder"},fe={class:"player-controls"},me={class:"controls-group"},ye=["title"],he={key:0,width:"20",height:"20",viewBox:"0 0 24 24",fill:"currentColor"},ge={key:1,width:"20",height:"20",viewBox:"0 0 24 24",fill:"currentColor"},we=["title"],ke={key:0,width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},Ce={key:1,width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},be=["title"],xe={key:0,width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},_e={key:1,width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},Ee={class:"controls-info"},Le={class:"tab-panel config-panel"},Se={key:1,class:"placeholder"},Me={class:"tab-panel meta-panel"},Ae={class:"meta-code"},Pe={name:"SimulatorLayout"},Re=V({...Pe,props:{playerComponent:{},configComponent:{},scene:{},appUrl:{},isLoading:{type:Boolean},error:{},manifest:{},liveReloadActive:{type:Boolean}},emits:["reload"],setup(a,{emit:y}){const h=a,b=y,o=w("player"),l=w("landscape"),n=w(!1),i=w(!1),f=w(!0),s=w(!0),p=w(!1),x=w(3e4),k=M(()=>{var v;return((v=h.manifest)==null?void 0:v.name)||"External App"}),E=M(()=>{var v;return((v=h.manifest)==null?void 0:v.version)||""}),L=M(()=>l.value==="landscape"?"aspect-landscape":"aspect-portrait"),S=M(()=>{var v;try{return JSON.stringify(((v=h.scene)==null?void 0:v.meta)||{},null,2)}catch{return"{}"}});function g(){b("reload")}async function C(){try{await navigator.clipboard.writeText(S.value)}catch{console.warn("Failed to copy to clipboard")}}function u(v){v.key==="Escape"&&n.value&&(n.value=!1)}return j(()=>{window.addEventListener("keydown",u)}),D(()=>{window.removeEventListener("keydown",u)}),(v,t)=>(r(),d("div",{class:m(["simulator-layout",{"dark-mode":i.value}])},[e("header",K,[e("div",X,[t[11]||(t[11]=e("h1",null,"Juuno Simulator",-1)),e("div",G,[e("span",Q,_(k.value),1),E.value?(r(),d("span",Y,"v"+_(E.value),1)):A("",!0)])]),e("div",Z,[a.liveReloadActive?(r(),d("div",ee,[...t[12]||(t[12]=[e("span",{class:"status-dot"},null,-1),O(" Live ",-1)])])):A("",!0),e("button",{class:"icon-btn",title:"Reload app",onClick:g},[...t[13]||(t[13]=[e("svg",{width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},[e("path",{d:"M23 4v6h-6M1 20v-6h6M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"})],-1)])]),e("button",{class:"icon-btn",title:"Toggle dark mode",onClick:t[0]||(t[0]=c=>i.value=!i.value)},[i.value?(r(),d("svg",oe,[...t[15]||(t[15]=[e("path",{d:"M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"},null,-1)])])):(r(),d("svg",te,[...t[14]||(t[14]=[e("circle",{cx:"12",cy:"12",r:"5"},null,-1),e("path",{d:"M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"},null,-1)])]))])])]),a.isLoading?(r(),d("div",ne,[t[16]||(t[16]=e("div",{class:"spinner"},null,-1)),t[17]||(t[17]=e("p",null,"Loading app...",-1)),e("p",le,_(a.appUrl),1)])):a.error?(r(),d("div",se,[t[19]||(t[19]=e("div",{class:"error-icon"},"!",-1)),t[20]||(t[20]=e("h2",null,"Failed to Load App",-1)),e("p",ie,_(a.error),1),e("div",ae,[e("p",null,[t[18]||(t[18]=e("strong",null,"App URL:",-1)),O(" "+_(a.appUrl),1)]),e("button",{class:"retry-btn",onClick:g},"Try Again")])])):(r(),d("div",re,[e("div",ue,[e("button",{class:m(["tab",{active:o.value==="player"}]),onClick:t[1]||(t[1]=c=>o.value="player")}," Player ",2),e("button",{class:m(["tab",{active:o.value==="config"}]),onClick:t[2]||(t[2]=c=>o.value="config")}," Config ",2),e("button",{class:m(["tab",{active:o.value==="meta"}]),onClick:t[3]||(t[3]=c=>o.value="meta")}," Scene Meta ",2),o.value==="player"?(r(),d("div",ce,[e("button",{class:m(["control-btn",{active:l.value==="landscape"}]),title:"Landscape (16:9)",onClick:t[4]||(t[4]=c=>l.value="landscape")},[...t[21]||(t[21]=[e("svg",{width:"18",height:"12",viewBox:"0 0 18 12",fill:"currentColor"},[e("rect",{x:"0.5",y:"0.5",width:"17",height:"11",rx:"1",fill:"none",stroke:"currentColor"})],-1)])],2),e("button",{class:m(["control-btn",{active:l.value==="portrait"}]),title:"Portrait (9:16)",onClick:t[5]||(t[5]=c=>l.value="portrait")},[...t[22]||(t[22]=[e("svg",{width:"12",height:"18",viewBox:"0 0 12 18",fill:"currentColor"},[e("rect",{x:"0.5",y:"0.5",width:"11",height:"17",rx:"1",fill:"none",stroke:"currentColor"})],-1)])],2),e("button",{class:m(["control-btn",{active:n.value}]),title:"Fullscreen (Esc to exit)",onClick:t[6]||(t[6]=c=>n.value=!n.value)},[...t[23]||(t[23]=[e("svg",{width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},[e("path",{d:"M8 3H5a2 2 0 00-2 2v3m18 0V5a2 2 0 00-2-2h-3m0 18h3a2 2 0 002-2v-3M3 16v3a2 2 0 002 2h3"})],-1)])],2)])):A("",!0)]),e("div",de,[R(e("div",{class:m(["tab-panel player-panel",{fullscreen:n.value}])},[e("div",{class:m(["player-container",L.value])},[e("div",ve,[a.playerComponent?(r(),$(U(a.playerComponent),{key:a.scene.idx,scene:a.scene,playing:f.value,"is-visible":s.value,"duration-ms":x.value,muted:p.value},null,8,["scene","playing","is-visible","duration-ms","muted"])):(r(),d("div",pe,"No player component"))])],2),e("div",fe,[e("div",me,[e("button",{class:m(["player-btn",{active:f.value}]),title:f.value?"Pause":"Play",onClick:t[7]||(t[7]=c=>f.value=!f.value)},[f.value?(r(),d("svg",he,[...t[24]||(t[24]=[e("rect",{x:"6",y:"4",width:"4",height:"16"},null,-1),e("rect",{x:"14",y:"4",width:"4",height:"16"},null,-1)])])):(r(),d("svg",ge,[...t[25]||(t[25]=[e("polygon",{points:"5,3 19,12 5,21"},null,-1)])]))],10,ye),e("button",{class:m(["player-btn",{active:!s.value}]),title:s.value?"Hide (simulate off-screen)":"Show",onClick:t[8]||(t[8]=c=>s.value=!s.value)},[s.value?(r(),d("svg",ke,[...t[26]||(t[26]=[e("path",{d:"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"},null,-1),e("circle",{cx:"12",cy:"12",r:"3"},null,-1)])])):(r(),d("svg",Ce,[...t[27]||(t[27]=[e("path",{d:"M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19m-6.72-1.07a3 3 0 11-4.24-4.24"},null,-1),e("line",{x1:"1",y1:"1",x2:"23",y2:"23"},null,-1)])]))],10,we),e("button",{class:m(["player-btn",{active:p.value}]),title:p.value?"Unmute":"Mute",onClick:t[9]||(t[9]=c=>p.value=!p.value)},[p.value?(r(),d("svg",_e,[...t[29]||(t[29]=[e("polygon",{points:"11,5 6,9 2,9 2,15 6,15 11,19"},null,-1),e("line",{x1:"23",y1:"9",x2:"17",y2:"15"},null,-1),e("line",{x1:"17",y1:"9",x2:"23",y2:"15"},null,-1)])])):(r(),d("svg",xe,[...t[28]||(t[28]=[e("polygon",{points:"11,5 6,9 2,9 2,15 6,15 11,19"},null,-1),e("path",{d:"M19.07 4.93a10 10 0 010 14.14M15.54 8.46a5 5 0 010 7.07"},null,-1)])]))],10,be)]),e("div",Ee,[e("span",{class:m(["state-badge",{inactive:!f.value}])},_(f.value?"Playing":"Paused"),3),e("span",{class:m(["state-badge",{inactive:!s.value}])},_(s.value?"Visible":"Hidden"),3),e("span",{class:m(["state-badge",{inactive:!p.value}])},_(p.value?"Muted":"Sound On"),3)])]),n.value?(r(),d("button",{key:0,class:"exit-fullscreen",onClick:t[10]||(t[10]=c=>n.value=!1)}," Exit Fullscreen ")):A("",!0)],2),[[B,o.value==="player"]]),R(e("div",Le,[a.configComponent?(r(),$(U(a.configComponent),{key:"config-"+a.scene.idx})):(r(),d("div",Se,"No config component"))],512),[[B,o.value==="config"]]),R(e("div",Me,[e("div",{class:"meta-header"},[t[30]||(t[30]=e("h3",null,"scene.meta",-1)),e("button",{class:"copy-btn",onClick:C},"Copy")]),e("pre",Ae,_(S.value),1)],512),[[B,o.value==="meta"]])])]))],2))}}),Be=(a,y)=>{const h=a.__vccOpts||a;for(const[b,o]of y)h[b]=o;return h},$e=Be(Re,[["__scopeId","data-v-d3c29943"]]),Oe={name:"JuunoCLI"},Ue=V({...Oe,setup(a){const y=w(window.__JUUNO_EXTERNAL_APP_URL__||"http://localhost:5002/index.js"),h=w(!1),b=w(null),o=[],l=T(null),n=T(null),i=w(!0),f=w(null),s=I({idx:"simulator-scene-1",name:"Simulator Test Scene",app:null,app_idx:"simulator-app-1",created_at:new Date().toISOString(),display_time:5e3,language:"en",order:0,playlist_idx:"simulator-playlist-1",updated_at:new Date().toISOString(),translations:{},meta:{title:"Test App",backgroundColor:"#2c3e50",textColor:"#ecf0f1"},mediaAssets:[]}),x=z({async save(g){console.log("Simulator: Scene updated",g),Object.assign(s,g)}},{scene:s});async function k(){try{const g=await fetch(`${y.value}/manifest.json`);if(g.ok){const C=await g.json();return b.value=C,C}}catch{}return b.value=null,null}function E(g){o.forEach(C=>C.remove()),o.length=0,g.forEach(C=>{const u=document.createElement("link");u.rel="stylesheet",u.href=new URL(C,y.value).toString(),document.head.appendChild(u),o.push(u)})}async function L(){var g,C;try{i.value=!0,f.value=null,console.log(`Loading external app from: ${y.value}`);const u=await k(),v=[];(g=u==null?void 0:u.resources)!=null&&g.player&&v.push(...u.resources.player.filter(c=>c.endsWith(".css"))),(C=u==null?void 0:u.resources)!=null&&C.config&&v.push(...u.resources.config.filter(c=>c.endsWith(".css"))),v.length>0&&(console.log("Loading CSS files:",v),E(v));const t=await F(()=>import("external-app/player"),[]);if(t.default)l.value=P(t.default);else if(typeof t.render=="function")l.value=P(t.render());else throw new Error("External app must export a default component or render function from /player");try{const c=await F(()=>import("external-app/config"),[]);c.default?n.value=P(c.default):typeof c.render=="function"?n.value=P(c.render()):n.value=null}catch{n.value=null}x.load(s),console.log("āœ… External app loaded successfully"),i.value=!1}catch(u){console.error("Failed to load external app:",u),f.value=u instanceof Error?u.message:"Failed to load external app",i.value=!1}}function S(){console.log("šŸ”„ Reloading app..."),window.location.reload()}return j(L),(g,C)=>(r(),$(J($e),{"player-component":l.value,"config-component":n.value,scene:s,"app-url":y.value,"is-loading":i.value,error:f.value,manifest:b.value,"live-reload-active":h.value,onReload:S},null,8,["player-component","config-component","scene","app-url","is-loading","error","manifest","live-reload-active"]))}}),Te=q(Ue);Te.mount("#app");
@@ -1 +0,0 @@
1
- [data-v-d3c29943]{box-sizing:border-box}.simulator-layout[data-v-d3c29943]{color:#1a1a2e;background-color:#f8f9fa;flex-direction:column;min-height:100vh;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;transition:background-color .2s,color .2s;display:flex}.simulator-layout.dark-mode[data-v-d3c29943]{color:#e8e8e8;background-color:#1a1a2e}.header[data-v-d3c29943]{color:#fff;background:linear-gradient(135deg,#6366f1 0%,#8b5cf6 100%);justify-content:space-between;align-items:center;padding:.75rem 1.5rem;display:flex}.header-left[data-v-d3c29943]{align-items:center;gap:1.5rem;display:flex}.header h1[data-v-d3c29943]{letter-spacing:-.02em;margin:0;font-size:1.125rem;font-weight:600}.app-info[data-v-d3c29943]{background:#ffffff26;border-radius:4px;align-items:center;gap:.5rem;padding:.25rem .75rem;font-size:.8125rem;display:flex}.app-name[data-v-d3c29943]{font-weight:500}.app-version[data-v-d3c29943]{opacity:.8;font-size:.75rem}.header-right[data-v-d3c29943]{align-items:center;gap:.75rem;display:flex}.status-badge[data-v-d3c29943]{border-radius:999px;align-items:center;gap:.375rem;padding:.25rem .625rem;font-size:.75rem;font-weight:500;display:flex}.status-badge.live[data-v-d3c29943]{color:#86efac;background:#22c55e33}.status-dot[data-v-d3c29943]{background:#22c55e;border-radius:50%;width:6px;height:6px;animation:2s infinite pulse-d3c29943}@keyframes pulse-d3c29943{0%,to{opacity:1}50%{opacity:.5}}.icon-btn[data-v-d3c29943]{color:#fff;cursor:pointer;background:#ffffff1a;border:none;border-radius:6px;justify-content:center;align-items:center;width:32px;height:32px;transition:background .15s;display:flex}.icon-btn[data-v-d3c29943]:hover{background:#fff3}.loading-state[data-v-d3c29943],.error-state[data-v-d3c29943]{text-align:center;flex-direction:column;flex:1;justify-content:center;align-items:center;padding:3rem;display:flex}.spinner[data-v-d3c29943]{border:3px solid #e5e7eb;border-top-color:#6366f1;border-radius:50%;width:40px;height:40px;animation:.8s linear infinite spin-d3c29943}.dark-mode .spinner[data-v-d3c29943]{border-color:#8b5cf6 #374151 #374151}@keyframes spin-d3c29943{to{transform:rotate(360deg)}}.url-info[data-v-d3c29943]{color:#6b7280;margin-top:.75rem;font-family:SF Mono,Monaco,monospace;font-size:.8125rem}.error-icon[data-v-d3c29943]{color:#dc2626;background:#fef2f2;border-radius:50%;justify-content:center;align-items:center;width:48px;height:48px;margin-bottom:1rem;font-size:1.5rem;font-weight:700;display:flex}.dark-mode .error-icon[data-v-d3c29943]{background:#450a0a}.error-message[data-v-d3c29943]{color:#dc2626;margin:.5rem 0;font-weight:500}.error-details[data-v-d3c29943]{background:#fff;border-radius:8px;margin-top:1.5rem;padding:1rem;font-size:.875rem}.dark-mode .error-details[data-v-d3c29943]{background:#1f2937}.retry-btn[data-v-d3c29943]{color:#fff;cursor:pointer;background:#6366f1;border:none;border-radius:6px;margin-top:1rem;padding:.5rem 1rem;font-weight:500;transition:background .15s}.retry-btn[data-v-d3c29943]:hover{background:#4f46e5}.main-content[data-v-d3c29943]{flex-direction:column;flex:1;gap:1rem;padding:1rem;display:flex}.tabs[data-v-d3c29943]{background:#fff;border-radius:8px;align-items:center;gap:.25rem;padding:.25rem;display:flex;box-shadow:0 1px 2px #0000000d}.dark-mode .tabs[data-v-d3c29943]{background:#1f2937}.tab[data-v-d3c29943]{color:#6b7280;cursor:pointer;background:0 0;border:none;border-radius:6px;padding:.5rem 1rem;font-size:.875rem;font-weight:500;transition:all .15s}.tab[data-v-d3c29943]:hover{color:#1f2937}.dark-mode .tab[data-v-d3c29943]:hover{color:#e5e7eb}.tab.active[data-v-d3c29943]{color:#fff;background:#6366f1}.tab-controls[data-v-d3c29943]{gap:.25rem;margin-left:auto;display:flex}.control-btn[data-v-d3c29943]{color:#9ca3af;cursor:pointer;background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;width:36px;height:32px;transition:all .15s;display:flex}.control-btn[data-v-d3c29943]:hover{color:#4b5563;background:#f3f4f6}.dark-mode .control-btn[data-v-d3c29943]:hover{color:#d1d5db;background:#374151}.control-btn.active[data-v-d3c29943]{color:#6366f1;background:#ede9fe}.dark-mode .control-btn.active[data-v-d3c29943]{color:#c4b5fd;background:#4c1d95}.tab-panels[data-v-d3c29943]{flex-direction:column;flex:1;display:flex}.tab-panel[data-v-d3c29943]{background:#fff;border-radius:8px;flex:1;overflow:auto;box-shadow:0 1px 3px #0000000d}.dark-mode .tab-panel[data-v-d3c29943]{background:#1f2937}.player-panel[data-v-d3c29943]{background:#111827;flex-direction:column;display:flex}.player-panel.fullscreen[data-v-d3c29943]{z-index:1000;border-radius:0;padding:0;position:fixed;inset:0}.player-container[data-v-d3c29943]{flex:1;transition:all .3s;display:flex}.player-container.aspect-landscape[data-v-d3c29943]{aspect-ratio:16/9}.player-container.aspect-portrait[data-v-d3c29943]{aspect-ratio:9/16}.fullscreen .player-container[data-v-d3c29943]{width:100%;height:100%}.fullscreen .player-container.aspect-portrait[data-v-d3c29943]{width:auto;height:100%}.player-frame[data-v-d3c29943]{background:#000;flex:1;position:relative;overflow:hidden}.player-frame[data-v-d3c29943]>*{width:100%;height:100%;position:absolute;inset:0}.exit-fullscreen[data-v-d3c29943]{color:#fff;cursor:pointer;z-index:1001;background:#000000bf;border:none;border-radius:6px;padding:.5rem 1rem;font-size:.875rem;position:fixed;top:1rem;right:1rem}.exit-fullscreen[data-v-d3c29943]:hover{background:#000000e6}.player-controls[data-v-d3c29943]{background:#ffffff1a;justify-content:center;align-items:center;gap:1.5rem;padding:.75rem 1rem;display:flex}.fullscreen .player-controls[data-v-d3c29943]{z-index:1001;background:#000000bf;position:fixed;bottom:1rem;left:50%;transform:translate(-50%)}.controls-group[data-v-d3c29943]{gap:.5rem;display:flex}.player-btn[data-v-d3c29943]{color:#e5e7eb;cursor:pointer;background:#ffffff1a;border:none;border-radius:8px;justify-content:center;align-items:center;width:40px;height:40px;transition:all .15s;display:flex}.player-btn[data-v-d3c29943]:hover{color:#fff;background:#fff3}.player-btn.active[data-v-d3c29943]{color:#fff;background:#6366f1}.controls-info[data-v-d3c29943]{gap:.5rem;display:flex}.state-badge[data-v-d3c29943]{color:#86efac;background:#22c55e33;border-radius:4px;padding:.25rem .5rem;font-size:.75rem;font-weight:500}.state-badge.inactive[data-v-d3c29943]{color:#fca5a5;background:#ef444433}.config-panel[data-v-d3c29943]{padding:1.5rem;overflow-y:auto}.meta-panel[data-v-d3c29943]{padding:1rem}.meta-header[data-v-d3c29943]{justify-content:space-between;align-items:center;margin-bottom:.75rem;display:flex}.meta-header h3[data-v-d3c29943]{color:#6b7280;margin:0;font-size:.875rem;font-weight:600}.copy-btn[data-v-d3c29943]{color:#4b5563;cursor:pointer;background:#f3f4f6;border:none;border-radius:4px;padding:.25rem .5rem;font-size:.75rem}.dark-mode .copy-btn[data-v-d3c29943]{color:#d1d5db;background:#374151}.copy-btn[data-v-d3c29943]:hover{background:#e5e7eb}.dark-mode .copy-btn[data-v-d3c29943]:hover{background:#4b5563}.meta-code[data-v-d3c29943]{white-space:pre-wrap;word-break:break-word;background:#f9fafb;border-radius:6px;margin:0;padding:1rem;font-family:SF Mono,Monaco,Courier New,monospace;font-size:.8125rem;line-height:1.6;overflow-x:auto}.dark-mode .meta-code[data-v-d3c29943]{color:#e5e7eb;background:#111827}.placeholder[data-v-d3c29943]{color:#9ca3af;justify-content:center;align-items:center;height:100%;min-height:200px;font-style:italic;display:flex}@media (width<=640px){.header[data-v-d3c29943]{padding:.5rem 1rem}.header h1[data-v-d3c29943]{font-size:1rem}.app-info[data-v-d3c29943]{display:none}.tabs[data-v-d3c29943]{flex-wrap:wrap}.tab-controls[data-v-d3c29943]{justify-content:center;width:100%;margin-top:.5rem;margin-left:0}}
@@ -1 +0,0 @@
1
- import{computed as e,createApp as t,createBlock as n,createCommentVNode as r,createElementBlock as i,createElementVNode as a,createTextVNode as o,defineComponent as s,markRaw as c,normalizeClass as l,onMounted as u,onUnmounted as d,openBlock as f,reactive as p,ref as m,resolveDynamicComponent as h,shallowRef as g,toDisplayString as _,unref as v,vShow as y,withDirectives as b}from"vue";import{provideAppConfigContext as x}from"@juuno-sdk/app-sdk";(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var S={class:`header`},C={class:`header-left`},w={class:`app-info`},T={class:`app-name`},E={key:0,class:`app-version`},D={class:`header-right`},ee={key:0,class:`status-badge live`},O={key:0,width:`16`,height:`16`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`},te={key:1,width:`16`,height:`16`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`},ne={key:0,class:`loading-state`},re={class:`url-info`},ie={key:1,class:`error-state`},ae={class:`error-message`},oe={class:`error-details`},se={key:2,class:`main-content`},ce={class:`tabs`},le={key:0,class:`tab-controls`},k={class:`tab-panels`},A={class:`player-frame`},j={key:1,class:`placeholder`},M={class:`player-controls`},N={class:`controls-group`},P=[`title`],F={key:0,width:`20`,height:`20`,viewBox:`0 0 24 24`,fill:`currentColor`},I={key:1,width:`20`,height:`20`,viewBox:`0 0 24 24`,fill:`currentColor`},L=[`title`],R={key:0,width:`20`,height:`20`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`},z={key:1,width:`20`,height:`20`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`},B=[`title`],V={key:0,width:`20`,height:`20`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`},H={key:1,width:`20`,height:`20`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`},U={class:`controls-info`},ue={class:`tab-panel config-panel`},de={key:1,class:`placeholder`},fe={class:`tab-panel meta-panel`},pe={class:`meta-code`},W=((e,t)=>{let n=e.__vccOpts||e;for(let[e,r]of t)n[e]=r;return n})(s({name:`SimulatorLayout`,props:{playerComponent:{},configComponent:{},scene:{},appUrl:{},isLoading:{type:Boolean},error:{},manifest:{},liveReloadActive:{type:Boolean}},emits:[`reload`],setup(t,{emit:s}){let c=t,p=s,g=m(`player`),v=m(`landscape`),x=m(!1),W=m(!1),G=m(!0),K=m(!0),q=m(!1),J=m(3e4),Y=e(()=>c.manifest?.name||`External App`),X=e(()=>c.manifest?.version||``),me=e(()=>v.value===`landscape`?`aspect-landscape`:`aspect-portrait`),Z=e(()=>{try{return JSON.stringify(c.scene?.meta||{},null,2)}catch{return`{}`}});function Q(){p(`reload`)}async function he(){try{await navigator.clipboard.writeText(Z.value)}catch{console.warn(`Failed to copy to clipboard`)}}function $(e){e.key===`Escape`&&x.value&&(x.value=!1)}return u(()=>{window.addEventListener(`keydown`,$)}),d(()=>{window.removeEventListener(`keydown`,$)}),(e,s)=>(f(),i(`div`,{class:l([`simulator-layout`,{"dark-mode":W.value}])},[a(`header`,S,[a(`div`,C,[s[11]||=a(`h1`,null,`Juuno Simulator`,-1),a(`div`,w,[a(`span`,T,_(Y.value),1),X.value?(f(),i(`span`,E,`v`+_(X.value),1)):r(``,!0)])]),a(`div`,D,[t.liveReloadActive?(f(),i(`div`,ee,[...s[12]||=[a(`span`,{class:`status-dot`},null,-1),o(` Live `,-1)]])):r(``,!0),a(`button`,{class:`icon-btn`,title:`Reload app`,onClick:Q},[...s[13]||=[a(`svg`,{width:`16`,height:`16`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`},[a(`path`,{d:`M23 4v6h-6M1 20v-6h6M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15`})],-1)]]),a(`button`,{class:`icon-btn`,title:`Toggle dark mode`,onClick:s[0]||=e=>W.value=!W.value},[W.value?(f(),i(`svg`,te,[...s[15]||=[a(`path`,{d:`M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z`},null,-1)]])):(f(),i(`svg`,O,[...s[14]||=[a(`circle`,{cx:`12`,cy:`12`,r:`5`},null,-1),a(`path`,{d:`M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42`},null,-1)]]))])])]),t.isLoading?(f(),i(`div`,ne,[s[16]||=a(`div`,{class:`spinner`},null,-1),s[17]||=a(`p`,null,`Loading app...`,-1),a(`p`,re,_(t.appUrl),1)])):t.error?(f(),i(`div`,ie,[s[19]||=a(`div`,{class:`error-icon`},`!`,-1),s[20]||=a(`h2`,null,`Failed to Load App`,-1),a(`p`,ae,_(t.error),1),a(`div`,oe,[a(`p`,null,[s[18]||=a(`strong`,null,`App URL:`,-1),o(` `+_(t.appUrl),1)]),a(`button`,{class:`retry-btn`,onClick:Q},`Try Again`)])])):(f(),i(`div`,se,[a(`div`,ce,[a(`button`,{class:l([`tab`,{active:g.value===`player`}]),onClick:s[1]||=e=>g.value=`player`},` Player `,2),a(`button`,{class:l([`tab`,{active:g.value===`config`}]),onClick:s[2]||=e=>g.value=`config`},` Config `,2),a(`button`,{class:l([`tab`,{active:g.value===`meta`}]),onClick:s[3]||=e=>g.value=`meta`},` Scene Meta `,2),g.value===`player`?(f(),i(`div`,le,[a(`button`,{class:l([`control-btn`,{active:v.value===`landscape`}]),title:`Landscape (16:9)`,onClick:s[4]||=e=>v.value=`landscape`},[...s[21]||=[a(`svg`,{width:`18`,height:`12`,viewBox:`0 0 18 12`,fill:`currentColor`},[a(`rect`,{x:`0.5`,y:`0.5`,width:`17`,height:`11`,rx:`1`,fill:`none`,stroke:`currentColor`})],-1)]],2),a(`button`,{class:l([`control-btn`,{active:v.value===`portrait`}]),title:`Portrait (9:16)`,onClick:s[5]||=e=>v.value=`portrait`},[...s[22]||=[a(`svg`,{width:`12`,height:`18`,viewBox:`0 0 12 18`,fill:`currentColor`},[a(`rect`,{x:`0.5`,y:`0.5`,width:`11`,height:`17`,rx:`1`,fill:`none`,stroke:`currentColor`})],-1)]],2),a(`button`,{class:l([`control-btn`,{active:x.value}]),title:`Fullscreen (Esc to exit)`,onClick:s[6]||=e=>x.value=!x.value},[...s[23]||=[a(`svg`,{width:`16`,height:`16`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,"stroke-width":`2`},[a(`path`,{d:`M8 3H5a2 2 0 00-2 2v3m18 0V5a2 2 0 00-2-2h-3m0 18h3a2 2 0 002-2v-3M3 16v3a2 2 0 002 2h3`})],-1)]],2)])):r(``,!0)]),a(`div`,k,[b(a(`div`,{class:l([`tab-panel player-panel`,{fullscreen:x.value}])},[a(`div`,{class:l([`player-container`,me.value])},[a(`div`,A,[t.playerComponent?(f(),n(h(t.playerComponent),{key:t.scene.idx,scene:t.scene,playing:G.value,"is-visible":K.value,"duration-ms":J.value,muted:q.value},null,8,[`scene`,`playing`,`is-visible`,`duration-ms`,`muted`])):(f(),i(`div`,j,`No player component`))])],2),a(`div`,M,[a(`div`,N,[a(`button`,{class:l([`player-btn`,{active:G.value}]),title:G.value?`Pause`:`Play`,onClick:s[7]||=e=>G.value=!G.value},[G.value?(f(),i(`svg`,F,[...s[24]||=[a(`rect`,{x:`6`,y:`4`,width:`4`,height:`16`},null,-1),a(`rect`,{x:`14`,y:`4`,width:`4`,height:`16`},null,-1)]])):(f(),i(`svg`,I,[...s[25]||=[a(`polygon`,{points:`5,3 19,12 5,21`},null,-1)]]))],10,P),a(`button`,{class:l([`player-btn`,{active:!K.value}]),title:K.value?`Hide (simulate off-screen)`:`Show`,onClick:s[8]||=e=>K.value=!K.value},[K.value?(f(),i(`svg`,R,[...s[26]||=[a(`path`,{d:`M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z`},null,-1),a(`circle`,{cx:`12`,cy:`12`,r:`3`},null,-1)]])):(f(),i(`svg`,z,[...s[27]||=[a(`path`,{d:`M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19m-6.72-1.07a3 3 0 11-4.24-4.24`},null,-1),a(`line`,{x1:`1`,y1:`1`,x2:`23`,y2:`23`},null,-1)]]))],10,L),a(`button`,{class:l([`player-btn`,{active:q.value}]),title:q.value?`Unmute`:`Mute`,onClick:s[9]||=e=>q.value=!q.value},[q.value?(f(),i(`svg`,H,[...s[29]||=[a(`polygon`,{points:`11,5 6,9 2,9 2,15 6,15 11,19`},null,-1),a(`line`,{x1:`23`,y1:`9`,x2:`17`,y2:`15`},null,-1),a(`line`,{x1:`17`,y1:`9`,x2:`23`,y2:`15`},null,-1)]])):(f(),i(`svg`,V,[...s[28]||=[a(`polygon`,{points:`11,5 6,9 2,9 2,15 6,15 11,19`},null,-1),a(`path`,{d:`M19.07 4.93a10 10 0 010 14.14M15.54 8.46a5 5 0 010 7.07`},null,-1)]]))],10,B)]),a(`div`,U,[a(`span`,{class:l([`state-badge`,{inactive:!G.value}])},_(G.value?`Playing`:`Paused`),3),a(`span`,{class:l([`state-badge`,{inactive:!K.value}])},_(K.value?`Visible`:`Hidden`),3),a(`span`,{class:l([`state-badge`,{inactive:!q.value}])},_(q.value?`Muted`:`Sound On`),3)])]),x.value?(f(),i(`button`,{key:0,class:`exit-fullscreen`,onClick:s[10]||=e=>x.value=!1},` Exit Fullscreen `)):r(``,!0)],2),[[y,g.value===`player`]]),b(a(`div`,ue,[t.configComponent?(f(),n(h(t.configComponent),{key:`config-`+t.scene.idx})):(f(),i(`div`,de,`No config component`))],512),[[y,g.value===`config`]]),b(a(`div`,fe,[a(`div`,{class:`meta-header`},[s[30]||=a(`h3`,null,`scene.meta`,-1),a(`button`,{class:`copy-btn`,onClick:he},`Copy`)]),a(`pre`,pe,_(Z.value),1)],512),[[y,g.value===`meta`]])])]))],2))}}),[[`__scopeId`,`data-v-d3c29943`]]);function G(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}var K=`modulepreload`,q=function(e){return`/`+e},J={},Y=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,import.meta.url).href}r=o(t.map(t=>{if(t=q(t,n),t=s(t),t in J)return;J[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:K,r||(i.as=`script`),i.crossOrigin=``,i.href=t,a&&i.setAttribute(`nonce`,a),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})};t(s({name:`JuunoCLI`,setup(e){let t=m(window.__JUUNO_EXTERNAL_APP_URL__||`http://localhost:5002/index.js`),r=m(!1),i=m(null),a=[],o=g(null),s=g(null),l=m(!0),d=m(null),h=p({idx:`simulator-scene-1`,name:`Simulator Test Scene`,app:null,app_idx:`simulator-app-1`,created_at:new Date().toISOString(),display_time:5e3,language:`en`,order:0,playlist_idx:`simulator-playlist-1`,updated_at:new Date().toISOString(),translations:{},meta:{title:`Test App`,backgroundColor:`#2c3e50`,textColor:`#ecf0f1`},mediaAssets:[]}),_=x({async save(e){console.log(`Simulator: Scene updated`,e),Object.assign(h,e)}},{scene:h});async function y(){try{let e=await fetch(`${t.value}/manifest.json`);if(e.ok){let t=await e.json();return i.value=t,t}}catch{}return i.value=null,null}function b(e){a.forEach(e=>e.remove()),a.length=0,e.forEach(e=>{let n=document.createElement(`link`);n.rel=`stylesheet`,n.href=new URL(e,t.value).toString(),document.head.appendChild(n),a.push(n)})}function S(e){if(!G(e))return null;let t=e;return t.default?t.default:typeof t.render==`function`?t.render():null}function C(e){let t=[];for(let n of[e?.resources?.player,e?.resources?.config])n&&t.push(...n.filter(e=>e.endsWith(`.css`)));return t}async function w(){let e=S(await Y(()=>import(`external-app/player`),[]));if(!e)throw Error(`External app must export a default component or render function from /player`);o.value=c(e)}async function T(){try{let e=S(await Y(()=>import(`external-app/config`),[]));s.value=e?c(e):null}catch(e){console.warn(`Optional config module could not be loaded:`,e),s.value=null}}async function E(){try{l.value=!0,d.value=null,console.log(`Loading external app from: ${t.value}`);let e=C(await y());e.length>0&&(console.log(`Loading CSS files:`,e),b(e)),await w(),await T(),_.load(h),console.log(`āœ… External app loaded successfully`),l.value=!1}catch(e){console.error(`Failed to load external app:`,e),d.value=e instanceof Error?e.message:`Failed to load external app`,l.value=!1}}function D(){console.log(`šŸ”„ Reloading app...`),window.location.reload()}return u(E),(e,a)=>(f(),n(v(W),{"player-component":o.value,"config-component":s.value,scene:h,"app-url":t.value,"is-loading":l.value,error:d.value,manifest:i.value,"live-reload-active":r.value,onReload:D},null,8,[`player-component`,`config-component`,`scene`,`app-url`,`is-loading`,`error`,`manifest`,`live-reload-active`]))}})).mount(`#app`);
@@ -1 +0,0 @@
1
- [data-v-d3c29943]{box-sizing:border-box}.simulator-layout[data-v-d3c29943]{min-height:100vh;display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;background-color:#f8f9fa;color:#1a1a2e;transition:background-color .2s,color .2s}.simulator-layout.dark-mode[data-v-d3c29943]{background-color:#1a1a2e;color:#e8e8e8}.header[data-v-d3c29943]{display:flex;justify-content:space-between;align-items:center;padding:.75rem 1.5rem;background:linear-gradient(135deg,#6366f1,#8b5cf6);color:#fff}.header-left[data-v-d3c29943]{display:flex;align-items:center;gap:1.5rem}.header h1[data-v-d3c29943]{margin:0;font-size:1.125rem;font-weight:600;letter-spacing:-.02em}.app-info[data-v-d3c29943]{display:flex;align-items:center;gap:.5rem;padding:.25rem .75rem;background:#ffffff26;border-radius:4px;font-size:.8125rem}.app-name[data-v-d3c29943]{font-weight:500}.app-version[data-v-d3c29943]{opacity:.8;font-size:.75rem}.header-right[data-v-d3c29943]{display:flex;align-items:center;gap:.75rem}.status-badge[data-v-d3c29943]{display:flex;align-items:center;gap:.375rem;padding:.25rem .625rem;border-radius:999px;font-size:.75rem;font-weight:500}.status-badge.live[data-v-d3c29943]{background:#22c55e33;color:#86efac}.status-dot[data-v-d3c29943]{width:6px;height:6px;border-radius:50%;background:#22c55e;animation:pulse-d3c29943 2s infinite}@keyframes pulse-d3c29943{0%,to{opacity:1}50%{opacity:.5}}.icon-btn[data-v-d3c29943]{display:flex;align-items:center;justify-content:center;width:32px;height:32px;border:none;border-radius:6px;background:#ffffff1a;color:#fff;cursor:pointer;transition:background .15s}.icon-btn[data-v-d3c29943]:hover{background:#fff3}.loading-state[data-v-d3c29943],.error-state[data-v-d3c29943]{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:3rem;text-align:center}.spinner[data-v-d3c29943]{width:40px;height:40px;border:3px solid #e5e7eb;border-top-color:#6366f1;border-radius:50%;animation:spin-d3c29943 .8s linear infinite}.dark-mode .spinner[data-v-d3c29943]{border-color:#374151;border-top-color:#8b5cf6}@keyframes spin-d3c29943{to{transform:rotate(360deg)}}.url-info[data-v-d3c29943]{margin-top:.75rem;font-size:.8125rem;color:#6b7280;font-family:SF Mono,Monaco,monospace}.error-icon[data-v-d3c29943]{width:48px;height:48px;display:flex;align-items:center;justify-content:center;background:#fef2f2;color:#dc2626;border-radius:50%;font-size:1.5rem;font-weight:700;margin-bottom:1rem}.dark-mode .error-icon[data-v-d3c29943]{background:#450a0a}.error-message[data-v-d3c29943]{color:#dc2626;font-weight:500;margin:.5rem 0}.error-details[data-v-d3c29943]{margin-top:1.5rem;padding:1rem;background:#fff;border-radius:8px;font-size:.875rem}.dark-mode .error-details[data-v-d3c29943]{background:#1f2937}.retry-btn[data-v-d3c29943]{margin-top:1rem;padding:.5rem 1rem;background:#6366f1;color:#fff;border:none;border-radius:6px;font-weight:500;cursor:pointer;transition:background .15s}.retry-btn[data-v-d3c29943]:hover{background:#4f46e5}.main-content[data-v-d3c29943]{flex:1;display:flex;flex-direction:column;padding:1rem;gap:1rem}.tabs[data-v-d3c29943]{display:flex;align-items:center;gap:.25rem;padding:.25rem;background:#fff;border-radius:8px;box-shadow:0 1px 2px #0000000d}.dark-mode .tabs[data-v-d3c29943]{background:#1f2937}.tab[data-v-d3c29943]{padding:.5rem 1rem;border:none;border-radius:6px;background:transparent;color:#6b7280;font-size:.875rem;font-weight:500;cursor:pointer;transition:all .15s}.tab[data-v-d3c29943]:hover{color:#1f2937}.dark-mode .tab[data-v-d3c29943]:hover{color:#e5e7eb}.tab.active[data-v-d3c29943]{background:#6366f1;color:#fff}.tab-controls[data-v-d3c29943]{margin-left:auto;display:flex;gap:.25rem}.control-btn[data-v-d3c29943]{display:flex;align-items:center;justify-content:center;width:36px;height:32px;border:none;border-radius:6px;background:transparent;color:#9ca3af;cursor:pointer;transition:all .15s}.control-btn[data-v-d3c29943]:hover{background:#f3f4f6;color:#4b5563}.dark-mode .control-btn[data-v-d3c29943]:hover{background:#374151;color:#d1d5db}.control-btn.active[data-v-d3c29943]{background:#ede9fe;color:#6366f1}.dark-mode .control-btn.active[data-v-d3c29943]{background:#4c1d95;color:#c4b5fd}.tab-panels[data-v-d3c29943]{flex:1;display:flex;flex-direction:column}.tab-panel[data-v-d3c29943]{flex:1;background:#fff;border-radius:8px;box-shadow:0 1px 3px #0000000d;overflow:auto}.dark-mode .tab-panel[data-v-d3c29943]{background:#1f2937}.player-panel[data-v-d3c29943]{display:flex;flex-direction:column;background:#111827}.player-panel.fullscreen[data-v-d3c29943]{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000;border-radius:0;padding:0}.player-container[data-v-d3c29943]{flex:1;display:flex;transition:all .3s ease}.player-container.aspect-landscape[data-v-d3c29943]{aspect-ratio:16 / 9}.player-container.aspect-portrait[data-v-d3c29943]{aspect-ratio:9 / 16}.fullscreen .player-container[data-v-d3c29943]{width:100%;height:100%}.fullscreen .player-container.aspect-portrait[data-v-d3c29943]{width:auto;height:100%}.player-frame[data-v-d3c29943]{flex:1;position:relative;background:#000;overflow:hidden}.player-frame[data-v-d3c29943]>*{position:absolute;top:0;right:0;bottom:0;left:0;width:100%;height:100%}.exit-fullscreen[data-v-d3c29943]{position:fixed;top:1rem;right:1rem;padding:.5rem 1rem;background:#000000bf;color:#fff;border:none;border-radius:6px;font-size:.875rem;cursor:pointer;z-index:1001}.exit-fullscreen[data-v-d3c29943]:hover{background:#000000e6}.player-controls[data-v-d3c29943]{display:flex;align-items:center;justify-content:center;gap:1.5rem;padding:.75rem 1rem;background:#ffffff1a}.fullscreen .player-controls[data-v-d3c29943]{position:fixed;bottom:1rem;left:50%;transform:translate(-50%);background:#000000bf;z-index:1001}.controls-group[data-v-d3c29943]{display:flex;gap:.5rem}.player-btn[data-v-d3c29943]{display:flex;align-items:center;justify-content:center;width:40px;height:40px;border:none;border-radius:8px;background:#ffffff1a;color:#e5e7eb;cursor:pointer;transition:all .15s}.player-btn[data-v-d3c29943]:hover{background:#fff3;color:#fff}.player-btn.active[data-v-d3c29943]{background:#6366f1;color:#fff}.controls-info[data-v-d3c29943]{display:flex;gap:.5rem}.state-badge[data-v-d3c29943]{padding:.25rem .5rem;border-radius:4px;font-size:.75rem;font-weight:500;background:#22c55e33;color:#86efac}.state-badge.inactive[data-v-d3c29943]{background:#ef444433;color:#fca5a5}.config-panel[data-v-d3c29943]{padding:1.5rem;overflow-y:auto}.meta-panel[data-v-d3c29943]{padding:1rem}.meta-header[data-v-d3c29943]{display:flex;justify-content:space-between;align-items:center;margin-bottom:.75rem}.meta-header h3[data-v-d3c29943]{margin:0;font-size:.875rem;font-weight:600;color:#6b7280}.copy-btn[data-v-d3c29943]{padding:.25rem .5rem;background:#f3f4f6;border:none;border-radius:4px;font-size:.75rem;color:#4b5563;cursor:pointer}.dark-mode .copy-btn[data-v-d3c29943]{background:#374151;color:#d1d5db}.copy-btn[data-v-d3c29943]:hover{background:#e5e7eb}.dark-mode .copy-btn[data-v-d3c29943]:hover{background:#4b5563}.meta-code[data-v-d3c29943]{margin:0;padding:1rem;background:#f9fafb;border-radius:6px;font-family:SF Mono,Monaco,Courier New,monospace;font-size:.8125rem;line-height:1.6;overflow-x:auto;white-space:pre-wrap;word-break:break-word}.dark-mode .meta-code[data-v-d3c29943]{background:#111827;color:#e5e7eb}.placeholder[data-v-d3c29943]{display:flex;align-items:center;justify-content:center;height:100%;min-height:200px;color:#9ca3af;font-style:italic}@media (max-width: 640px){.header[data-v-d3c29943]{padding:.5rem 1rem}.header h1[data-v-d3c29943]{font-size:1rem}.app-info[data-v-d3c29943]{display:none}.tabs[data-v-d3c29943]{flex-wrap:wrap}.tab-controls[data-v-d3c29943]{width:100%;justify-content:center;margin-top:.5rem;margin-left:0}}
@@ -1 +0,0 @@
1
- import{defineComponent as I,ref as g,computed as A,onMounted as J,onUnmounted as z,createElementBlock as u,openBlock as c,normalizeClass as p,createElementVNode as t,createCommentVNode as P,toDisplayString as C,createTextVNode as T,withDirectives as R,createBlock as $,resolveDynamicComponent as F,vShow as B,shallowRef as N,reactive as H,unref as K,markRaw as j,createApp as W}from"vue";import{provideAppConfigContext as X}from"@juuno-sdk/app-sdk";(function(){const f=document.createElement("link").relList;if(f&&f.supports&&f.supports("modulepreload"))return;for(const n of document.querySelectorAll('link[rel="modulepreload"]'))w(n);new MutationObserver(n=>{for(const i of n)if(i.type==="childList")for(const r of i.addedNodes)r.tagName==="LINK"&&r.rel==="modulepreload"&&w(r)}).observe(document,{childList:!0,subtree:!0});function m(n){const i={};return n.integrity&&(i.integrity=n.integrity),n.referrerPolicy&&(i.referrerPolicy=n.referrerPolicy),n.crossOrigin==="use-credentials"?i.credentials="include":n.crossOrigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function w(n){if(n.ep)return;n.ep=!0;const i=m(n);fetch(n.href,i)}})();const G="modulepreload",Q=function(l){return"/"+l},V={},D=function(f,m,w){let n=Promise.resolve();if(m&&m.length>0){let r=function(s){return Promise.all(s.map(d=>Promise.resolve(d).then(k=>({status:"fulfilled",value:k}),k=>({status:"rejected",reason:k}))))};document.getElementsByTagName("link");const a=document.querySelector("meta[property=csp-nonce]"),v=(a==null?void 0:a.nonce)||(a==null?void 0:a.getAttribute("nonce"));n=r(m.map(s=>{if(s=Q(s),s in V)return;V[s]=!0;const d=s.endsWith(".css"),k=d?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${s}"]${k}`))return;const h=document.createElement("link");if(h.rel=d?"stylesheet":G,d||(h.as="script"),h.crossOrigin="",h.href=s,v&&h.setAttribute("nonce",v),document.head.appendChild(h),d)return new Promise((x,_)=>{h.addEventListener("load",x),h.addEventListener("error",()=>_(new Error(`Unable to preload CSS for ${s}`)))})}))}function i(r){const a=new Event("vite:preloadError",{cancelable:!0});if(a.payload=r,window.dispatchEvent(a),!a.defaultPrevented)throw r}return n.then(r=>{for(const a of r||[])a.status==="rejected"&&i(a.reason);return f().catch(i)})},Y={class:"header"},Z={class:"header-left"},ee={class:"app-info"},te={class:"app-name"},oe={key:0,class:"app-version"},ne={class:"header-right"},le={key:0,class:"status-badge live"},se={key:0,width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},ie={key:1,width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},re={key:0,class:"loading-state"},ae={class:"url-info"},ce={key:1,class:"error-state"},ue={class:"error-message"},de={class:"error-details"},ve={key:2,class:"main-content"},pe={class:"tabs"},fe={key:0,class:"tab-controls"},me={class:"tab-panels"},ye={class:"player-frame"},ge={key:1,class:"placeholder"},he={class:"player-controls"},we={class:"controls-group"},ke=["title"],Ce={key:0,width:"20",height:"20",viewBox:"0 0 24 24",fill:"currentColor"},be={key:1,width:"20",height:"20",viewBox:"0 0 24 24",fill:"currentColor"},xe=["title"],_e={key:0,width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},Ee={key:1,width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},Le=["title"],Se={key:0,width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},Ae={key:1,width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},Pe={class:"controls-info"},Me={class:"tab-panel config-panel"},Re={key:1,class:"placeholder"},Be={class:"tab-panel meta-panel"},$e={class:"meta-code"},Oe={name:"SimulatorLayout"},Ue=I({...Oe,props:{playerComponent:{},configComponent:{},scene:{},appUrl:{},isLoading:{type:Boolean},error:{},manifest:{},liveReloadActive:{type:Boolean}},emits:["reload"],setup(l,{emit:f}){const m=l,w=f,n=g("player"),i=g("landscape"),r=g(!1),a=g(!1),v=g(!0),s=g(!0),d=g(!1),k=g(3e4),h=A(()=>{var y;return((y=m.manifest)==null?void 0:y.name)||"External App"}),x=A(()=>{var y;return((y=m.manifest)==null?void 0:y.version)||""}),_=A(()=>i.value==="landscape"?"aspect-landscape":"aspect-portrait"),E=A(()=>{var y;try{return JSON.stringify(((y=m.scene)==null?void 0:y.meta)||{},null,2)}catch{return"{}"}});function L(){w("reload")}async function M(){try{await navigator.clipboard.writeText(E.value)}catch{console.warn("Failed to copy to clipboard")}}function S(y){y.key==="Escape"&&r.value&&(r.value=!1)}return J(()=>{window.addEventListener("keydown",S)}),z(()=>{window.removeEventListener("keydown",S)}),(y,e)=>(c(),u("div",{class:p(["simulator-layout",{"dark-mode":a.value}])},[t("header",Y,[t("div",Z,[e[11]||(e[11]=t("h1",null,"Juuno Simulator",-1)),t("div",ee,[t("span",te,C(h.value),1),x.value?(c(),u("span",oe,"v"+C(x.value),1)):P("",!0)])]),t("div",ne,[l.liveReloadActive?(c(),u("div",le,[...e[12]||(e[12]=[t("span",{class:"status-dot"},null,-1),T(" Live ",-1)])])):P("",!0),t("button",{class:"icon-btn",title:"Reload app",onClick:L},[...e[13]||(e[13]=[t("svg",{width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},[t("path",{d:"M23 4v6h-6M1 20v-6h6M3.51 9a9 9 0 0114.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0020.49 15"})],-1)])]),t("button",{class:"icon-btn",title:"Toggle dark mode",onClick:e[0]||(e[0]=o=>a.value=!a.value)},[a.value?(c(),u("svg",ie,[...e[15]||(e[15]=[t("path",{d:"M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"},null,-1)])])):(c(),u("svg",se,[...e[14]||(e[14]=[t("circle",{cx:"12",cy:"12",r:"5"},null,-1),t("path",{d:"M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"},null,-1)])]))])])]),l.isLoading?(c(),u("div",re,[e[16]||(e[16]=t("div",{class:"spinner"},null,-1)),e[17]||(e[17]=t("p",null,"Loading app...",-1)),t("p",ae,C(l.appUrl),1)])):l.error?(c(),u("div",ce,[e[19]||(e[19]=t("div",{class:"error-icon"},"!",-1)),e[20]||(e[20]=t("h2",null,"Failed to Load App",-1)),t("p",ue,C(l.error),1),t("div",de,[t("p",null,[e[18]||(e[18]=t("strong",null,"App URL:",-1)),T(" "+C(l.appUrl),1)]),t("button",{class:"retry-btn",onClick:L},"Try Again")])])):(c(),u("div",ve,[t("div",pe,[t("button",{class:p(["tab",{active:n.value==="player"}]),onClick:e[1]||(e[1]=o=>n.value="player")}," Player ",2),t("button",{class:p(["tab",{active:n.value==="config"}]),onClick:e[2]||(e[2]=o=>n.value="config")}," Config ",2),t("button",{class:p(["tab",{active:n.value==="meta"}]),onClick:e[3]||(e[3]=o=>n.value="meta")}," Scene Meta ",2),n.value==="player"?(c(),u("div",fe,[t("button",{class:p(["control-btn",{active:i.value==="landscape"}]),title:"Landscape (16:9)",onClick:e[4]||(e[4]=o=>i.value="landscape")},[...e[21]||(e[21]=[t("svg",{width:"18",height:"12",viewBox:"0 0 18 12",fill:"currentColor"},[t("rect",{x:"0.5",y:"0.5",width:"17",height:"11",rx:"1",fill:"none",stroke:"currentColor"})],-1)])],2),t("button",{class:p(["control-btn",{active:i.value==="portrait"}]),title:"Portrait (9:16)",onClick:e[5]||(e[5]=o=>i.value="portrait")},[...e[22]||(e[22]=[t("svg",{width:"12",height:"18",viewBox:"0 0 12 18",fill:"currentColor"},[t("rect",{x:"0.5",y:"0.5",width:"11",height:"17",rx:"1",fill:"none",stroke:"currentColor"})],-1)])],2),t("button",{class:p(["control-btn",{active:r.value}]),title:"Fullscreen (Esc to exit)",onClick:e[6]||(e[6]=o=>r.value=!r.value)},[...e[23]||(e[23]=[t("svg",{width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor","stroke-width":"2"},[t("path",{d:"M8 3H5a2 2 0 00-2 2v3m18 0V5a2 2 0 00-2-2h-3m0 18h3a2 2 0 002-2v-3M3 16v3a2 2 0 002 2h3"})],-1)])],2)])):P("",!0)]),t("div",me,[R(t("div",{class:p(["tab-panel player-panel",{fullscreen:r.value}])},[t("div",{class:p(["player-container",_.value])},[t("div",ye,[l.playerComponent?(c(),$(F(l.playerComponent),{key:l.scene.idx,scene:l.scene,playing:v.value,"is-visible":s.value,"duration-ms":k.value,muted:d.value},null,8,["scene","playing","is-visible","duration-ms","muted"])):(c(),u("div",ge,"No player component"))])],2),t("div",he,[t("div",we,[t("button",{class:p(["player-btn",{active:v.value}]),title:v.value?"Pause":"Play",onClick:e[7]||(e[7]=o=>v.value=!v.value)},[v.value?(c(),u("svg",Ce,[...e[24]||(e[24]=[t("rect",{x:"6",y:"4",width:"4",height:"16"},null,-1),t("rect",{x:"14",y:"4",width:"4",height:"16"},null,-1)])])):(c(),u("svg",be,[...e[25]||(e[25]=[t("polygon",{points:"5,3 19,12 5,21"},null,-1)])]))],10,ke),t("button",{class:p(["player-btn",{active:!s.value}]),title:s.value?"Hide (simulate off-screen)":"Show",onClick:e[8]||(e[8]=o=>s.value=!s.value)},[s.value?(c(),u("svg",_e,[...e[26]||(e[26]=[t("path",{d:"M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"},null,-1),t("circle",{cx:"12",cy:"12",r:"3"},null,-1)])])):(c(),u("svg",Ee,[...e[27]||(e[27]=[t("path",{d:"M17.94 17.94A10.07 10.07 0 0112 20c-7 0-11-8-11-8a18.45 18.45 0 015.06-5.94M9.9 4.24A9.12 9.12 0 0112 4c7 0 11 8 11 8a18.5 18.5 0 01-2.16 3.19m-6.72-1.07a3 3 0 11-4.24-4.24"},null,-1),t("line",{x1:"1",y1:"1",x2:"23",y2:"23"},null,-1)])]))],10,xe),t("button",{class:p(["player-btn",{active:d.value}]),title:d.value?"Unmute":"Mute",onClick:e[9]||(e[9]=o=>d.value=!d.value)},[d.value?(c(),u("svg",Ae,[...e[29]||(e[29]=[t("polygon",{points:"11,5 6,9 2,9 2,15 6,15 11,19"},null,-1),t("line",{x1:"23",y1:"9",x2:"17",y2:"15"},null,-1),t("line",{x1:"17",y1:"9",x2:"23",y2:"15"},null,-1)])])):(c(),u("svg",Se,[...e[28]||(e[28]=[t("polygon",{points:"11,5 6,9 2,9 2,15 6,15 11,19"},null,-1),t("path",{d:"M19.07 4.93a10 10 0 010 14.14M15.54 8.46a5 5 0 010 7.07"},null,-1)])]))],10,Le)]),t("div",Pe,[t("span",{class:p(["state-badge",{inactive:!v.value}])},C(v.value?"Playing":"Paused"),3),t("span",{class:p(["state-badge",{inactive:!s.value}])},C(s.value?"Visible":"Hidden"),3),t("span",{class:p(["state-badge",{inactive:!d.value}])},C(d.value?"Muted":"Sound On"),3)])]),r.value?(c(),u("button",{key:0,class:"exit-fullscreen",onClick:e[10]||(e[10]=o=>r.value=!1)}," Exit Fullscreen ")):P("",!0)],2),[[B,n.value==="player"]]),R(t("div",Me,[l.configComponent?(c(),$(F(l.configComponent),{key:"config-"+l.scene.idx})):(c(),u("div",Re,"No config component"))],512),[[B,n.value==="config"]]),R(t("div",Be,[t("div",{class:"meta-header"},[e[30]||(e[30]=t("h3",null,"scene.meta",-1)),t("button",{class:"copy-btn",onClick:M},"Copy")]),t("pre",$e,C(E.value),1)],512),[[B,n.value==="meta"]])])]))],2))}}),Te=(l,f)=>{const m=l.__vccOpts||l;for(const[w,n]of f)m[w]=n;return m},Fe=Te(Ue,[["__scopeId","data-v-d3c29943"]]);function Ne(l){return typeof l=="object"&&l!==null&&!Array.isArray(l)}const je={name:"JuunoCLI"},Ve=I({...je,setup(l){const f=g(window.__JUUNO_EXTERNAL_APP_URL__||"http://localhost:5002/index.js"),m=g(!1),w=g(null),n=[],i=N(null),r=N(null),a=g(!0),v=g(null),s=H({idx:"simulator-scene-1",name:"Simulator Test Scene",app:null,app_idx:"simulator-app-1",created_at:new Date().toISOString(),display_time:5e3,language:"en",order:0,playlist_idx:"simulator-playlist-1",updated_at:new Date().toISOString(),translations:{},meta:{title:"Test App",backgroundColor:"#2c3e50",textColor:"#ecf0f1"},mediaAssets:[]}),k=X({async save(e){console.log("Simulator: Scene updated",e),Object.assign(s,e)}},{scene:s});async function h(){try{const e=await fetch(`${f.value}/manifest.json`);if(e.ok){const o=await e.json();return w.value=o,o}}catch{}return w.value=null,null}function x(e){n.forEach(o=>o.remove()),n.length=0,e.forEach(o=>{const b=document.createElement("link");b.rel="stylesheet",b.href=new URL(o,f.value).toString(),document.head.appendChild(b),n.push(b)})}function _(e){if(!Ne(e))return null;const o=e;return o.default?o.default:typeof o.render=="function"?o.render():null}function E(e){var b,O;const o=[];for(const U of[(b=e==null?void 0:e.resources)==null?void 0:b.player,(O=e==null?void 0:e.resources)==null?void 0:O.config])U&&o.push(...U.filter(q=>q.endsWith(".css")));return o}async function L(){const e=await D(()=>import("external-app/player"),[]),o=_(e);if(!o)throw new Error("External app must export a default component or render function from /player");i.value=j(o)}async function M(){try{const e=await D(()=>import("external-app/config"),[]),o=_(e);r.value=o?j(o):null}catch(e){console.warn("Optional config module could not be loaded:",e),r.value=null}}async function S(){try{a.value=!0,v.value=null,console.log(`Loading external app from: ${f.value}`);const e=await h(),o=E(e);o.length>0&&(console.log("Loading CSS files:",o),x(o)),await L(),await M(),k.load(s),console.log("āœ… External app loaded successfully"),a.value=!1}catch(e){console.error("Failed to load external app:",e),v.value=e instanceof Error?e.message:"Failed to load external app",a.value=!1}}function y(){console.log("šŸ”„ Reloading app..."),window.location.reload()}return J(S),(e,o)=>(c(),$(K(Fe),{"player-component":i.value,"config-component":r.value,scene:s,"app-url":f.value,"is-loading":a.value,error:v.value,manifest:w.value,"live-reload-active":m.value,onReload:y},null,8,["player-component","config-component","scene","app-url","is-loading","error","manifest","live-reload-active"]))}}),De=W(Ve);De.mount("#app");
@@ -1,203 +0,0 @@
1
- /**
2
- * Unified dev command that orchestrates the complete development workflow.
3
- *
4
- * When run without --app, this command:
5
- * 1. Auto-detects the app in the current directory
6
- * 2. Starts vite dev server (with HMR via @juuno/vite-plugin-app-dev)
7
- * 3. Starts the simulator pointing to the dev server
8
- */
9
- import { spawn } from 'child_process';
10
- import { existsSync, readFileSync } from 'fs';
11
- import { createConnection } from 'net';
12
- import { join } from 'path';
13
- import { launchSimulatorWithOptions } from '../simulator/index.js';
14
- import { findFileUpward } from '../utils/fs.js';
15
- /**
16
- * Check if a port is already in use.
17
- */
18
- async function isPortInUse(port) {
19
- return new Promise((resolve) => {
20
- const socket = createConnection({ port, host: 'localhost' });
21
- socket.once('connect', () => {
22
- socket.destroy();
23
- resolve(true);
24
- });
25
- socket.once('error', () => {
26
- socket.destroy();
27
- resolve(false);
28
- });
29
- });
30
- }
31
- /**
32
- * Wait for a port to become available (server started).
33
- */
34
- async function waitForPort(port, timeoutMs = 30000) {
35
- const startTime = Date.now();
36
- while (Date.now() - startTime < timeoutMs) {
37
- if (await isPortInUse(port)) {
38
- return true;
39
- }
40
- await new Promise((resolve) => setTimeout(resolve, 200));
41
- }
42
- return false;
43
- }
44
- /**
45
- * Lockfile lookup table. first hit wins. Adding a manager is one entry.
46
- */
47
- const PACKAGE_MANAGER_LOCKFILES = [
48
- { lockfile: 'pnpm-lock.yaml', manager: 'pnpm' },
49
- { lockfile: 'yarn.lock', manager: 'yarn' },
50
- { lockfile: 'package-lock.json', manager: 'npm' },
51
- ];
52
- function detectPackageManager(cwd) {
53
- for (const { lockfile, manager } of PACKAGE_MANAGER_LOCKFILES) {
54
- if (findFileUpward(cwd, lockfile)) {
55
- return manager;
56
- }
57
- }
58
- return 'npm';
59
- }
60
- /**
61
- * Map a detected package manager to the (command, args) pair that runs
62
- * vite with the configured port. Pure.
63
- */
64
- function buildDevCommand(pm, servePort) {
65
- const args = ['vite', '--port', servePort, '--strictPort'];
66
- if (pm === 'pnpm') {
67
- return { command: 'pnpm', args: ['exec', ...args] };
68
- }
69
- if (pm === 'yarn') {
70
- return { command: 'yarn', args };
71
- }
72
- return { command: 'npx', args };
73
- }
74
- function readDevScriptPresence(cwd) {
75
- const path = join(cwd, 'package.json');
76
- if (!existsSync(path)) {
77
- return false;
78
- }
79
- try {
80
- const packageJson = JSON.parse(readFileSync(path, 'utf-8'));
81
- return !!packageJson.scripts?.dev;
82
- }
83
- catch {
84
- return false;
85
- }
86
- }
87
- /**
88
- * Detect if the current directory contains a Juuno external app.
89
- */
90
- function detectApp(cwd) {
91
- return {
92
- hasViteConfig: existsSync(join(cwd, 'vite.config.ts')) ||
93
- existsSync(join(cwd, 'vite.config.js')),
94
- hasPackageJson: existsSync(join(cwd, 'package.json')),
95
- hasDevScript: readDevScriptPresence(cwd),
96
- packageManager: detectPackageManager(cwd),
97
- };
98
- }
99
- /**
100
- * Run the unified dev command.
101
- */
102
- export async function runDev(options) {
103
- const cwd = process.cwd();
104
- const servePort = options.servePort || '3000';
105
- const simulatorPort = options.simulatorPort || '5004';
106
- console.log('\nšŸ” Detecting app structure...\n');
107
- const detection = detectApp(cwd);
108
- if (!detection.hasPackageJson) {
109
- console.error('Error: No package.json found in current directory.');
110
- console.error('Please run this command from your app directory.');
111
- process.exit(1);
112
- }
113
- if (!detection.hasViteConfig) {
114
- console.error('Error: No vite.config.ts or vite.config.js found.');
115
- console.error('Please ensure your app has a Vite config with @juuno/vite-plugin-app-dev.');
116
- process.exit(1);
117
- }
118
- const pm = detection.packageManager;
119
- console.log(` Package manager: ${pm}`);
120
- console.log(` Vite config: found`);
121
- console.log(` Dev server: http://localhost:${servePort}`);
122
- console.log('');
123
- // Track child processes for cleanup.
124
- const processes = [];
125
- // Cleanup function.
126
- function cleanup() {
127
- console.log('\n\nšŸ‘‹ Stopping all processes...');
128
- processes.forEach((p) => {
129
- try {
130
- p.kill('SIGTERM');
131
- }
132
- catch {
133
- // Ignore errors.
134
- }
135
- });
136
- }
137
- // Handle termination.
138
- process.on('SIGINT', () => {
139
- cleanup();
140
- process.exit(0);
141
- });
142
- process.on('SIGTERM', () => {
143
- cleanup();
144
- process.exit(0);
145
- });
146
- // Check if port is already in use before starting.
147
- if (await isPortInUse(parseInt(servePort))) {
148
- console.error(`Error: Port ${servePort} is already in use.`);
149
- console.error('Try a different port with --serve-port.\n');
150
- process.exit(1);
151
- }
152
- // Step 1: Start Vite dev server.
153
- console.log('šŸš€ Starting Vite dev server...\n');
154
- const { command: devCommand, args: devArgs } = buildDevCommand(pm, servePort);
155
- const devProcess = spawn(devCommand, devArgs, {
156
- cwd,
157
- stdio: ['ignore', 'pipe', 'pipe'],
158
- shell: false,
159
- });
160
- processes.push(devProcess);
161
- // Forward dev server output with prefix.
162
- devProcess.stdout?.on('data', (data) => {
163
- const lines = data.toString().trim().split('\n');
164
- lines.forEach((line) => {
165
- if (line.trim()) {
166
- console.log(`[vite] ${line}`);
167
- }
168
- });
169
- });
170
- devProcess.stderr?.on('data', (data) => {
171
- const lines = data.toString().trim().split('\n');
172
- lines.forEach((line) => {
173
- if (line.trim()) {
174
- console.log(`[vite] ${line}`);
175
- }
176
- });
177
- });
178
- devProcess.on('error', (err) => {
179
- console.error(`[vite] Error: ${err.message}`);
180
- });
181
- // Wait for Vite dev server to be ready.
182
- console.log('ā³ Waiting for Vite dev server...\n');
183
- const serverReady = await waitForPort(parseInt(servePort));
184
- if (!serverReady) {
185
- console.error('Error: Vite dev server did not start within 30 seconds.');
186
- console.error('Please check your vite config for errors.');
187
- cleanup();
188
- process.exit(1);
189
- }
190
- // Give Vite a moment to fully initialize after port is open.
191
- await new Promise((resolve) => setTimeout(resolve, 500));
192
- const appUrl = `http://localhost:${servePort}`;
193
- console.log(`āœ… Vite dev server ready at ${appUrl}\n`);
194
- // Step 2: Launch the simulator.
195
- console.log('šŸŽ® Starting simulator...\n');
196
- console.log(`šŸŒ Simulator: http://localhost:${simulatorPort}\n`);
197
- // Launch the simulator (this is a blocking call that handles its own lifecycle).
198
- await launchSimulatorWithOptions({
199
- appUrl,
200
- port: simulatorPort,
201
- distPath: options.simulatorDistPath,
202
- });
203
- }
@@ -1,177 +0,0 @@
1
- /**
2
- * Simulator logic for launching the app simulator.
3
- * Used by both cli.js (production) and cli-dev.js (development).
4
- */
5
- import { spawn } from 'child_process';
6
- import { join } from 'path';
7
- import { readFileSync, writeFileSync, mkdirSync, copyFileSync, readdirSync, rmSync, existsSync, statSync, } from 'fs';
8
- import { tmpdir } from 'os';
9
- /**
10
- * Copy directory recursively.
11
- */
12
- function copyDir(src, dest) {
13
- const entries = readdirSync(src, { withFileTypes: true });
14
- for (const entry of entries) {
15
- const srcPath = join(src, entry.name);
16
- const destPath = join(dest, entry.name);
17
- if (entry.isDirectory()) {
18
- mkdirSync(destPath, { recursive: true });
19
- copyDir(srcPath, destPath);
20
- }
21
- else {
22
- copyFileSync(srcPath, destPath);
23
- }
24
- }
25
- }
26
- /**
27
- * Launch the app simulator with optional live reload.
28
- */
29
- export function launchSimulator(appUrl, port, distPath) {
30
- return launchSimulatorWithOptions({ appUrl, port, distPath });
31
- }
32
- /**
33
- * Fetch the app manifest to get actual resource paths.
34
- */
35
- async function fetchManifest(appUrl) {
36
- try {
37
- const response = await fetch(`${appUrl}/manifest.json`);
38
- if (!response.ok) {
39
- return null;
40
- }
41
- return await response.json();
42
- }
43
- catch {
44
- return null;
45
- }
46
- }
47
- /**
48
- * Static CDN map for the import-map slots in index.html. Adding a new
49
- * dependency is one entry; the runtime substitution below iterates this
50
- * record so the body never grows.
51
- */
52
- const IMPORT_MAP_CDN = {
53
- vue: 'https://cdn.jsdelivr.net/npm/vue@3.5.22/dist/vue.esm-browser.prod.js',
54
- '@juuno-sdk/app-sdk': 'https://cdn.jsdelivr.net/npm/@juuno-sdk/app-sdk@1.0.2/dist/index.js',
55
- '@vueuse/core': 'https://cdn.jsdelivr.net/npm/@vueuse/core@11.4.1/index.mjs',
56
- '@tiptap/vue-3': 'https://cdn.jsdelivr.net/npm/@tiptap/vue-3@2.10.5/dist/index.js',
57
- '@tiptap/starter-kit': 'https://cdn.jsdelivr.net/npm/@tiptap/starter-kit@2.10.5/dist/index.js',
58
- '@tiptap/extension-underline': 'https://cdn.jsdelivr.net/npm/@tiptap/extension-underline@2.10.5/dist/index.js',
59
- 'lodash-es': 'https://cdn.jsdelivr.net/npm/lodash-es@4.17.21/lodash.js',
60
- 'date-fns': 'https://cdn.jsdelivr.net/npm/date-fns@4.1.0/index.mjs',
61
- 'url-parse': 'https://cdn.jsdelivr.net/npm/url-parse@1.5.10/dist/url-parse.min.js',
62
- };
63
- /**
64
- * Apply the import-map CDN substitutions and the external-app entrypoints
65
- * to a copy of index.html. Pure over the inputs.
66
- */
67
- function applyImportMap(indexHtml, appUrl, playerPath, configPath) {
68
- let result = indexHtml;
69
- for (const [key, url] of Object.entries(IMPORT_MAP_CDN)) {
70
- result = result.replace(`"${key}": ""`, `"${key}": "${url}"`);
71
- }
72
- result = result.replace('"external-app/player": ""', `"external-app/player": "${appUrl}${playerPath}"`);
73
- result = result.replace('"external-app/config": ""', `"external-app/config": "${appUrl}${configPath}"`);
74
- return result;
75
- }
76
- function ensureValidDistDir(distPath) {
77
- if (!existsSync(distPath)) {
78
- console.error(`Error: Build directory does not exist: ${distPath}`);
79
- console.error('Please run "pnpm build" or "pnpm build:simulator" first.');
80
- process.exit(1);
81
- }
82
- try {
83
- const stats = statSync(distPath);
84
- if (!stats.isDirectory()) {
85
- console.error(`Error: Path exists but is not a directory: ${distPath}`);
86
- process.exit(1);
87
- }
88
- }
89
- catch (error) {
90
- console.error(`Error: Cannot access build directory: ${distPath}`);
91
- console.error(`Details: ${error instanceof Error ? error.message : String(error)}`);
92
- process.exit(1);
93
- }
94
- }
95
- function ensureIndexHtmlPresent(indexPath) {
96
- if (existsSync(indexPath)) {
97
- return;
98
- }
99
- console.error(`Error: index.html not found in build directory after copy`);
100
- console.error(`Expected location: ${indexPath}`);
101
- console.error('The build directory may be incomplete or corrupted.');
102
- console.error('Please run "pnpm build" to rebuild the simulator.');
103
- process.exit(1);
104
- }
105
- /**
106
- * Launch the app simulator with full options object.
107
- */
108
- export async function launchSimulatorWithOptions(options) {
109
- const { appUrl, port, distPath } = options;
110
- // Fetch manifest to get actual resource paths.
111
- const manifest = await fetchManifest(appUrl);
112
- const playerPath = manifest?.resources?.player?.find((p) => p.endsWith('.js')) || '/player.js';
113
- const configPath = manifest?.resources?.config?.find((p) => p.endsWith('.js')) || '/config.js';
114
- ensureValidDistDir(distPath);
115
- // Create a temporary directory for the modified dist.
116
- const tempDir = join(tmpdir(), `juuno-simulator-${Date.now()}`);
117
- mkdirSync(tempDir, { recursive: true });
118
- copyDir(distPath, tempDir);
119
- const indexPath = join(tempDir, 'index.html');
120
- ensureIndexHtmlPresent(indexPath);
121
- const indexHtmlSource = readFileSync(indexPath, 'utf-8');
122
- const withImportMap = applyImportMap(indexHtmlSource, appUrl, playerPath, configPath);
123
- // Inject the app URL as a global variable. JSON.stringify for safe escaping.
124
- const appUrlScript = `
125
- <script>
126
- window.__JUUNO_EXTERNAL_APP_URL__ = ${JSON.stringify(appUrl)};
127
- </script>
128
- `;
129
- const indexHtml = withImportMap.replace('</head>', `${appUrlScript}</head>`);
130
- writeFileSync(indexPath, indexHtml);
131
- console.log(`
132
- šŸš€ Juuno App Simulator
133
-
134
- External App: ${appUrl}
135
- Simulator: http://localhost:${port}
136
-
137
- Press Ctrl+C to stop
138
- `);
139
- // Serve the modified temp directory.
140
- // Use npx serve with --single for SPA routing.
141
- const server = spawn('npx', ['-y', 'serve', tempDir, '-l', port, '--cors', '--single'], {
142
- stdio: 'inherit',
143
- shell: false,
144
- });
145
- server.on('error', (err) => {
146
- console.error('Failed to start server:', err);
147
- process.exit(1);
148
- });
149
- server.on('exit', (code) => {
150
- if (code !== 0) {
151
- console.error(`Server exited with code ${code}`);
152
- }
153
- process.exit(code ?? 0);
154
- });
155
- // Cleanup function.
156
- function cleanup() {
157
- // Remove temp directory.
158
- try {
159
- rmSync(tempDir, { recursive: true, force: true });
160
- }
161
- catch {
162
- // Ignore cleanup errors.
163
- }
164
- }
165
- // Handle termination.
166
- process.on('SIGINT', () => {
167
- console.log('\n\nšŸ‘‹ Stopping simulator...');
168
- server.kill();
169
- cleanup();
170
- process.exit(0);
171
- });
172
- process.on('SIGTERM', () => {
173
- server.kill();
174
- cleanup();
175
- process.exit(0);
176
- });
177
- }
package/dist/index.html DELETED
@@ -1,35 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Juuno App Simulator</title>
7
- <script type="importmap">
8
- {
9
- "imports": {
10
- "vue": "",
11
- "@vueuse/core": "",
12
- "@tiptap/vue-3": "",
13
- "@tiptap/starter-kit": "",
14
- "@tiptap/extension-underline": "",
15
- "lodash-es": "",
16
- "date-fns": "",
17
- "url-parse": "",
18
- "@juuno-sdk/app-sdk": "",
19
- "@juuno-sdk/app-sdk/player": "",
20
- "@juuno-sdk/app-sdk/config": "",
21
- "@juuno-sdk/app-sdk/preview": "",
22
- "external-app/player": "",
23
- "external-app/config": ""
24
- }
25
- }
26
- </script>
27
- <script type="module" crossorigin src="/assets/main-TDlmPBlt.js"></script>
28
- <link rel="stylesheet" crossorigin href="/assets/main-GRbDNSIa.css">
29
-
30
-
31
- </head>
32
- <body>
33
- <div id="app"></div>
34
- </body>
35
- </html>