pixifact-cli 0.5.0 → 0.6.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/README.md +27 -2
- package/editor/assets/browserAll-DCAvtrKG.js +1 -0
- package/editor/assets/index-D8cbY_GQ.js +1130 -0
- package/editor/assets/{index-CVrKIfXK.css → index-gCKoe0w8.css} +1 -1
- package/editor/assets/init-ClFt_GA_.js +1 -0
- package/editor/assets/webworkerAll-Do_n5w1y.js +1 -0
- package/editor/index.html +2 -2
- package/package.json +2 -2
- package/src/automation.ts +2 -3
- package/src/editorSession.ts +211 -4
- package/src/pixifact-cli.ts +143 -1
- package/src/runtimeSession.ts +133 -0
- package/editor/assets/browserAll-CdyZj0BN.js +0 -1
- package/editor/assets/index-CAe9Mkmr.js +0 -1130
- package/editor/assets/init-C32NAhsC.js +0 -1
- package/editor/assets/webworkerAll-BEmxAAYf.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
:root{color:#d9dee7;font-synthesis:none;text-rendering:optimizelegibility;--bg:#111318;--panel:#171a20;--panel-raised:#1d2129;--canvas:#0d0f13;--line:#2a2f39;--line-strong:#39404d;--text:#d9dee7;--muted:#818997;--faint:#596171;--focus:#4c8dff;--focus-soft:#213a63;--success:#51b785;--warning:#d4a64f;--danger:#e36f6f;--control:#11141a;background:#111318;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}*{box-sizing:border-box}html,body,#app{width:100%;height:100%;margin:0;overflow:hidden}button,input,select{color:inherit;font:inherit;letter-spacing:0}button{border:0}.editor-shell{background:var(--bg);grid-template-rows:42px minmax(0,1fr);width:100%;height:100%;display:grid}.editor-session-standby{background:var(--bg);place-items:center;width:100%;height:100%;display:grid}.editor-session-standby>section{gap:18px;width:min(420px,100vw - 40px);display:grid}.standby-product{color:var(--faint);font-size:10px;font-weight:750}.editor-session-standby h1{color:var(--text);letter-spacing:0;margin:0;font-size:18px}.standby-context{border-top:1px solid var(--line);border-bottom:1px solid var(--line);grid-template-columns:76px minmax(0,1fr);gap:8px 12px;padding:14px 0;display:grid}.standby-context span{color:var(--muted);font-size:12px}.standby-context strong{min-width:0;color:var(--text);text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:500;overflow:hidden}.takeover-button{background:var(--focus);color:#fff;cursor:pointer;border:1px solid #659cff;border-radius:4px;justify-self:start;min-width:104px;height:32px;padding:0 14px;font-size:12px;font-weight:650}.takeover-button:hover:not(:disabled){background:#5a96ff}.standby-note,.standby-error{margin:-6px 0 0;font-size:11px;line-height:1.6}.standby-note{color:var(--muted)}.standby-error{color:var(--danger)}.topbar{border-bottom:1px solid var(--line);background:#14171c;grid-template-columns:232px minmax(0,1fr) auto 112px;align-items:center;min-width:0;display:grid}.brand{border-right:1px solid var(--line);color:#f1f4f8;align-items:center;height:100%;padding:0 14px;font-size:11px;font-weight:750;display:flex}.scene-identity{align-items:baseline;gap:10px;min-width:0;padding:0 14px;display:flex}.scene-identity strong{flex:none;font-size:13px}.scene-identity span{min-width:0;color:var(--muted);text-overflow:ellipsis;white-space:nowrap;font-size:11px;overflow:hidden}.history-tools{align-items:center;gap:2px;padding-right:10px;display:flex}.history-tools button,.reset-button{width:28px;height:28px;color:var(--muted);cursor:pointer;background:0 0;border-radius:4px;place-items:center;padding:0;display:inline-grid}.history-tools button:hover:not(:disabled),.reset-button:hover:not(:disabled){background:var(--panel-raised);color:var(--text)}button:disabled{opacity:.35;cursor:default}.sync-state{border-left:1px solid var(--line);height:100%;color:var(--muted);align-items:center;gap:7px;padding:0 13px;font-size:11px;display:flex}.sync-state span{background:var(--success);border-radius:50%;width:7px;height:7px}.sync-state[data-state=saving] span{background:var(--warning)}.sync-state[data-state=conflict] span,.sync-state[data-state=error] span{background:var(--danger)}.workspace{grid-template-columns:minmax(190px,232px) minmax(360px,1fr) minmax(250px,296px);min-width:0;min-height:0;display:grid}.left-panel,.right-panel,.canvas-panel{min-width:0;min-height:0}.left-panel,.right-panel{background:var(--panel)}.left-panel{border-right:1px solid var(--line)}.right-panel{border-left:1px solid var(--line);overflow:auto}.left-tabs{grid-template-rows:34px minmax(0,1fr);height:100%;display:grid}.tab-list{border-bottom:1px solid var(--line);grid-template-columns:1fr 1fr;display:grid}.tab-trigger{color:var(--muted);cursor:pointer;background:0 0;font-size:11px;position:relative}.tab-trigger[data-state=active]{color:var(--text)}.tab-trigger[data-state=active]:after{background:var(--focus);content:"";height:2px;position:absolute;bottom:-1px;left:12px;right:12px}.tab-content{outline:none;min-height:0;overflow:auto}.panel-title,.inspector-section-title,.asset-group-label{color:var(--muted);font-size:10px;font-weight:700}.panel-title{border-bottom:1px solid var(--line);align-items:center;height:34px;padding:0 12px;display:flex}.tree-list{margin:0;padding:0;list-style:none}.hierarchy-panel{min-height:100%;padding:0 0 6px}.hierarchy-toolbar{z-index:2;border-bottom:1px solid var(--line);background:var(--panel);grid-template-columns:minmax(0,1fr) 27px 27px 27px;align-items:center;gap:2px;height:35px;padding:4px 6px;display:grid;position:sticky;top:0}.hierarchy-toolbar select{border:1px solid var(--line-strong);background:var(--control);width:100%;min-width:0;height:25px;color:var(--text);border-radius:3px;outline:none;padding:0 5px;font-size:10px}.hierarchy-toolbar select:focus{border-color:var(--focus)}.hierarchy-toolbar button{width:27px;height:27px;color:var(--muted);cursor:pointer;background:0 0;border-radius:3px;place-items:center;padding:0;display:grid}.hierarchy-toolbar button:hover:not(:disabled){background:var(--panel-raised);color:var(--text)}.tree-row{width:100%;height:27px;color:var(--text);text-align:left;cursor:default;background:0 0;grid-template-columns:15px 16px minmax(0,1fr) auto;align-items:center;gap:4px;padding-top:0;padding-bottom:0;padding-right:9px;display:grid;position:relative}.hierarchy-panel.is-dragging .tree-row{cursor:grabbing}.hierarchy-panel.is-asset-dragging .tree-row{cursor:copy}.tree-row.drop-inside{box-shadow:inset 0 0 0 1px var(--focus);background:#26364d}.tree-row.drop-before:before,.tree-row.drop-after:after{z-index:1;background:var(--focus);content:"";pointer-events:none;height:2px;position:absolute;left:5px;right:5px}.tree-row.drop-before:before{top:-1px}.tree-row.drop-after:after{bottom:-1px}.tree-row:hover{background:#20242c}.tree-row.selected{background:var(--focus-soft);color:#eef5ff}.tree-disclosure{width:15px;height:20px;color:var(--muted);place-items:center;display:grid}.tree-disclosure.empty{visibility:hidden}.tree-icon{color:#8da4c7}.tree-label{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:12px;overflow:hidden}.tree-row small{color:var(--faint);font-size:9px}.scene-root{cursor:default;padding-left:8px;font-weight:650}.scene-mark{color:#a9c8f8;border:1px solid #637a9d;border-radius:3px;place-items:center;width:14px;height:14px;font-size:8px;display:grid}.asset-list{padding:7px 0 14px}.asset-group-label{padding:10px 12px 5px}.asset-row{width:100%;height:29px;color:var(--text);text-align:left;background:0 0;grid-template-columns:18px minmax(0,1fr);align-items:center;gap:5px;padding:0 12px;display:grid}button.asset-row{cursor:pointer}.asset-row[data-asset-draggable=true]{cursor:grab;-webkit-user-select:none;user-select:none}.asset-row:hover,.asset-row.selected{background:#20242c}.asset-row.selected{color:#a9ccff}.asset-row span{text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.asset-row.asset-draggable{color:var(--muted)}.asset-empty,.panel-empty{color:var(--faint);padding:14px 12px;font-size:12px}.panel-empty.compact{padding-top:18px}.canvas-panel{background:var(--canvas);position:relative}.scene-canvas-host{-webkit-user-select:none;user-select:none;position:absolute;inset:0;overflow:hidden}.scene-canvas-host.is-asset-drop-target:after{z-index:2;border:1px solid var(--focus);content:"";pointer-events:none;position:absolute;inset:3px}.canvas-grid{pointer-events:none;background-image:linear-gradient(#20242b 1px,#0000 1px),linear-gradient(90deg,#20242b 1px,#0000 1px);background-size:24px 24px;position:absolute;inset:0}.scene-canvas{display:block;position:absolute;inset:0}.scene-canvas-host.is-pan-ready .scene-canvas{cursor:grab!important}.scene-canvas-host.is-panning .scene-canvas{cursor:grabbing!important}.canvas-tools{z-index:3;position:absolute;top:10px;right:10px}.canvas-tools button{border:1px solid var(--line);background:var(--panel);width:28px;height:28px;color:var(--muted);cursor:pointer;border-radius:4px;place-items:center;padding:0;display:inline-grid}.canvas-tools button:hover:not(:disabled){border-color:var(--line-strong);background:var(--panel-raised);color:var(--text)}.canvas-status{border:1px solid var(--line);max-width:min(520px,100% - 48px);color:var(--muted);background:#15181e;border-radius:4px;padding:7px 10px;font-size:12px;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.inspector-panel{min-height:0}.inspector-heading{border-bottom:1px solid var(--line);justify-content:space-between;align-items:center;min-height:54px;padding:8px 12px;display:flex}.inspector-heading div{gap:3px;min-width:0;display:grid}.inspector-heading strong{text-overflow:ellipsis;white-space:nowrap;font-size:13px;overflow:hidden}.inspector-heading small,.node-id{color:var(--muted);font-size:10px}.node-id{text-overflow:ellipsis;white-space:nowrap;max-width:90px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;overflow:hidden}.inspector-section-title{border-bottom:1px solid var(--line);height:28px;padding:9px 12px 0}.property-row{border-bottom:1px solid #21252d;grid-template-columns:minmax(70px,.8fr) minmax(120px,1.4fr);align-items:center;min-height:34px;padding:3px 8px 3px 12px;display:grid}.property-row>span{color:var(--text);text-overflow:ellipsis;white-space:nowrap;font-size:11px;overflow:hidden}.property-row>span.inherited{color:var(--muted)}.property-control{grid-template-columns:minmax(0,1fr) 24px;align-items:start;gap:3px;display:grid}.property-value{gap:3px;min-width:0;display:grid}.property-control input[type=text],.property-control input[type=number],.property-control select{border:1px solid var(--line-strong);background:var(--control);width:100%;min-width:0;height:25px;color:var(--text);border-radius:3px;outline:none;padding:0 7px;font-size:11px}.property-control input:focus,.property-control select:focus{border-color:var(--focus)}.property-control input:disabled,.property-control select:disabled{color:#9ca6b5;cursor:default;opacity:1;background:#1b1f26;border-color:#303640}.property-control input[type=checkbox]{width:15px;height:15px;accent-color:var(--focus)}.property-control input[type=color]{border:1px solid var(--line-strong);background:var(--control);border-radius:3px;width:34px;height:24px;padding:2px}.reset-button{width:24px;height:24px}.binding-source{color:#8eb8e8;text-overflow:ellipsis;white-space:nowrap;align-items:center;gap:4px;min-width:0;font-size:10px;display:flex;overflow:hidden}.inline-error,.global-error{color:#ffd1d1;margin:0;font-size:11px}.inline-error{background:#2d1b20;border-bottom:1px solid #4f292f;padding:9px 12px}.global-error{background:#321e23;border:1px solid #66343c;border-radius:4px;max-width:min(520px,100vw - 24px);padding:9px 11px;position:fixed;bottom:12px;right:12px}@media (width<=860px){.workspace{grid-template-columns:190px minmax(320px,1fr) 250px}.topbar{grid-template-columns:190px minmax(0,1fr) auto 100px}}
|
|
1
|
+
:root{color:#d9dee7;font-synthesis:none;text-rendering:optimizelegibility;--bg:#111318;--panel:#171a20;--panel-raised:#1d2129;--canvas:#0d0f13;--line:#2a2f39;--line-strong:#39404d;--text:#d9dee7;--muted:#818997;--faint:#596171;--focus:#4c8dff;--focus-soft:#213a63;--success:#51b785;--warning:#d4a64f;--danger:#e36f6f;--control:#11141a;background:#111318;font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif}*{box-sizing:border-box}html,body,#app{width:100%;height:100%;margin:0;overflow:hidden}button,input,select{color:inherit;font:inherit;letter-spacing:0}button{border:0}.editor-shell{background:var(--bg);grid-template-rows:42px minmax(0,1fr);width:100%;height:100%;display:grid}.editor-session-standby{background:var(--bg);place-items:center;width:100%;height:100%;display:grid}.editor-session-standby>section{gap:18px;width:min(420px,100vw - 40px);display:grid}.standby-product{color:var(--faint);font-size:10px;font-weight:750}.editor-session-standby h1{color:var(--text);letter-spacing:0;margin:0;font-size:18px}.standby-context{border-top:1px solid var(--line);border-bottom:1px solid var(--line);grid-template-columns:76px minmax(0,1fr);gap:8px 12px;padding:14px 0;display:grid}.standby-context span{color:var(--muted);font-size:12px}.standby-context strong{min-width:0;color:var(--text);text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:500;overflow:hidden}.takeover-button{background:var(--focus);color:#fff;cursor:pointer;border:1px solid #659cff;border-radius:4px;justify-self:start;min-width:104px;height:32px;padding:0 14px;font-size:12px;font-weight:650}.takeover-button:hover:not(:disabled){background:#5a96ff}.standby-note,.standby-error{margin:-6px 0 0;font-size:11px;line-height:1.6}.standby-note{color:var(--muted)}.standby-error{color:var(--danger)}.topbar{border-bottom:1px solid var(--line);background:#14171c;grid-template-columns:232px minmax(0,1fr) auto 112px;align-items:center;min-width:0;display:grid}.brand{border-right:1px solid var(--line);color:#f1f4f8;align-items:center;height:100%;padding:0 14px;font-size:11px;font-weight:750;display:flex}.scene-identity{align-items:baseline;gap:10px;min-width:0;padding:0 14px;display:flex}.scene-identity strong{flex:none;font-size:13px}.scene-identity span{min-width:0;color:var(--muted);text-overflow:ellipsis;white-space:nowrap;font-size:11px;overflow:hidden}.history-tools{align-items:center;gap:2px;padding-right:10px;display:flex}.history-tools button,.reset-button{width:28px;height:28px;color:var(--muted);cursor:pointer;background:0 0;border-radius:4px;place-items:center;padding:0;display:inline-grid}.history-tools button:hover:not(:disabled),.reset-button:hover:not(:disabled){background:var(--panel-raised);color:var(--text)}button:disabled{opacity:.35;cursor:default}.sync-state{border-left:1px solid var(--line);height:100%;color:var(--muted);align-items:center;gap:7px;padding:0 13px;font-size:11px;display:flex}.sync-state span{background:var(--success);border-radius:50%;width:7px;height:7px}.sync-state[data-state=saving] span{background:var(--warning)}.sync-state[data-state=conflict] span,.sync-state[data-state=error] span{background:var(--danger)}.workspace{grid-template-columns:minmax(190px,232px) minmax(360px,1fr) minmax(250px,296px);min-width:0;min-height:0;display:grid}.left-panel,.right-panel,.canvas-panel{min-width:0;min-height:0}.left-panel,.right-panel{background:var(--panel)}.left-panel{border-right:1px solid var(--line)}.right-panel{border-left:1px solid var(--line);overflow:auto}.left-tabs{grid-template-rows:34px minmax(0,1fr);height:100%;display:grid}.tab-list{border-bottom:1px solid var(--line);grid-template-columns:1fr 1fr;display:grid}.tab-trigger{color:var(--muted);cursor:pointer;background:0 0;font-size:11px;position:relative}.tab-trigger[data-state=active]{color:var(--text)}.tab-trigger[data-state=active]:after{background:var(--focus);content:"";height:2px;position:absolute;bottom:-1px;left:12px;right:12px}.tab-content{outline:none;min-height:0;overflow:auto}.panel-title,.inspector-section-title,.asset-group-label{color:var(--muted);font-size:10px;font-weight:700}.panel-title{border-bottom:1px solid var(--line);align-items:center;height:34px;padding:0 12px;display:flex}.tree-list{margin:0;padding:0;list-style:none}.hierarchy-panel{min-height:100%;padding:0 0 6px}.hierarchy-toolbar{z-index:2;border-bottom:1px solid var(--line);background:var(--panel);grid-template-columns:minmax(0,1fr) 27px 27px 27px;align-items:center;gap:2px;height:35px;padding:4px 6px;display:grid;position:sticky;top:0}.hierarchy-toolbar select{border:1px solid var(--line-strong);background:var(--control);width:100%;min-width:0;height:25px;color:var(--text);border-radius:3px;outline:none;padding:0 5px;font-size:10px}.hierarchy-toolbar select:focus{border-color:var(--focus)}.hierarchy-toolbar button{width:27px;height:27px;color:var(--muted);cursor:pointer;background:0 0;border-radius:3px;place-items:center;padding:0;display:grid}.hierarchy-toolbar button:hover:not(:disabled){background:var(--panel-raised);color:var(--text)}.tree-row{width:100%;height:27px;color:var(--text);text-align:left;cursor:default;background:0 0;grid-template-columns:15px 16px minmax(0,1fr) auto;align-items:center;gap:4px;padding-top:0;padding-bottom:0;padding-right:9px;display:grid;position:relative}.hierarchy-panel.is-dragging .tree-row{cursor:grabbing}.hierarchy-panel.is-asset-dragging .tree-row{cursor:copy}.tree-row.drop-inside{box-shadow:inset 0 0 0 1px var(--focus);background:#26364d}.tree-row.drop-before:before,.tree-row.drop-after:after{z-index:1;background:var(--focus);content:"";pointer-events:none;height:2px;position:absolute;left:5px;right:5px}.tree-row.drop-before:before{top:-1px}.tree-row.drop-after:after{bottom:-1px}.tree-row:hover{background:#20242c}.tree-row.selected{background:var(--focus-soft);color:#eef5ff}.tree-disclosure{width:15px;height:20px;color:var(--muted);place-items:center;display:grid}.tree-disclosure.empty{visibility:hidden}.tree-icon{color:#8da4c7}.tree-label{text-overflow:ellipsis;white-space:nowrap;min-width:0;font-size:12px;overflow:hidden}.tree-row small{color:var(--faint);font-size:9px}.scene-root{cursor:default;padding-left:8px;font-weight:650}.scene-mark{color:#a9c8f8;border:1px solid #637a9d;border-radius:3px;place-items:center;width:14px;height:14px;font-size:8px;display:grid}.asset-list{padding:7px 0 14px}.asset-row{width:100%;height:29px;color:var(--text);text-align:left;background:0 0;grid-template-columns:14px 18px minmax(0,1fr);align-items:center;gap:5px;padding:0 12px;display:grid}button.asset-row{cursor:pointer}.asset-directory{color:var(--muted)}.asset-directory svg:nth-of-type(2){color:#a9c8f8}.asset-row[data-asset-draggable=true]{cursor:grab;-webkit-user-select:none;user-select:none}.asset-row:hover,.asset-row.selected{background:#20242c}.asset-row.selected{color:#a9ccff}.asset-row span{text-overflow:ellipsis;white-space:nowrap;font-size:12px;overflow:hidden}.asset-row .asset-row-spacer{width:14px}.asset-thumbnail{object-fit:contain;pointer-events:none;border-radius:2px;width:18px;height:18px}.asset-row.asset-draggable{color:var(--muted)}.asset-empty,.panel-empty{color:var(--faint);padding:14px 12px;font-size:12px}.panel-empty.compact{padding-top:18px}.canvas-panel{background:var(--canvas);position:relative}.scene-canvas-host{-webkit-user-select:none;user-select:none;position:absolute;inset:0;overflow:hidden}.scene-canvas-host.is-asset-drop-target:after{z-index:2;border:1px solid var(--focus);content:"";pointer-events:none;position:absolute;inset:3px}.canvas-grid{pointer-events:none;background-image:linear-gradient(#20242b 1px,#0000 1px),linear-gradient(90deg,#20242b 1px,#0000 1px);background-size:24px 24px;position:absolute;inset:0}.scene-canvas{display:block;position:absolute;inset:0}.scene-canvas-host.is-pan-ready .scene-canvas{cursor:grab!important}.scene-canvas-host.is-panning .scene-canvas{cursor:grabbing!important}.canvas-tools{z-index:3;position:absolute;top:10px;right:10px}.canvas-tools button{border:1px solid var(--line);background:var(--panel);width:28px;height:28px;color:var(--muted);cursor:pointer;border-radius:4px;place-items:center;padding:0;display:inline-grid}.canvas-tools button:hover:not(:disabled){border-color:var(--line-strong);background:var(--panel-raised);color:var(--text)}.canvas-status{border:1px solid var(--line);max-width:min(520px,100% - 48px);color:var(--muted);background:#15181e;border-radius:4px;padding:7px 10px;font-size:12px;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.inspector-panel{min-height:0}.inspector-heading{border-bottom:1px solid var(--line);justify-content:space-between;align-items:center;min-height:54px;padding:8px 12px;display:flex}.inspector-heading div{gap:3px;min-width:0;display:grid}.inspector-heading strong{text-overflow:ellipsis;white-space:nowrap;font-size:13px;overflow:hidden}.inspector-heading small,.node-id{color:var(--muted);font-size:10px}.node-id{text-overflow:ellipsis;white-space:nowrap;max-width:90px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;overflow:hidden}.inspector-section-title{border-bottom:1px solid var(--line);height:28px;padding:9px 12px 0}.property-row{border-bottom:1px solid #21252d;grid-template-columns:minmax(70px,.8fr) minmax(120px,1.4fr);align-items:center;min-height:34px;padding:3px 8px 3px 12px;display:grid}.property-row>span{color:var(--text);text-overflow:ellipsis;white-space:nowrap;font-size:11px;overflow:hidden}.property-row>span.inherited{color:var(--muted)}.property-row.is-image-drop-candidate{box-shadow:inset 2px 0 var(--success);cursor:copy;background:#162b24}.property-row.is-image-drop-candidate:hover{background:#1b372d}.property-row.is-image-drop-candidate .property-control input[type=text]{border-color:var(--success)}.property-control{grid-template-columns:minmax(0,1fr) 24px;align-items:start;gap:3px;display:grid}.property-value{gap:3px;min-width:0;display:grid}.property-control input[type=text],.property-control input[type=number],.property-control select{border:1px solid var(--line-strong);background:var(--control);width:100%;min-width:0;height:25px;color:var(--text);border-radius:3px;outline:none;padding:0 7px;font-size:11px}.property-control input:focus,.property-control select:focus{border-color:var(--focus)}.property-control input:disabled,.property-control select:disabled{color:#9ca6b5;cursor:default;opacity:1;background:#1b1f26;border-color:#303640}.property-control input[type=checkbox]{width:15px;height:15px;accent-color:var(--focus)}.property-control input[type=color]{border:1px solid var(--line-strong);background:var(--control);border-radius:3px;width:34px;height:24px;padding:2px}.reset-button{width:24px;height:24px}.binding-source{color:#8eb8e8;text-overflow:ellipsis;white-space:nowrap;align-items:center;gap:4px;min-width:0;font-size:10px;display:flex;overflow:hidden}.inline-error,.global-error{color:#ffd1d1;margin:0;font-size:11px}.inline-error{background:#2d1b20;border-bottom:1px solid #4f292f;padding:9px 12px}.global-error{background:#321e23;border:1px solid #66343c;border-radius:4px;max-width:min(520px,100vw - 24px);padding:9px 11px;position:fixed;bottom:12px;right:12px}@media (width<=860px){.workspace{grid-template-columns:190px minmax(320px,1fr) 250px}.topbar{grid-template-columns:190px minmax(0,1fr) auto 100px}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{n as e,r as t,t as n,u as r}from"./index-D8cbY_GQ.js";r.add(n,t),r.add(e);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./index-D8cbY_GQ.js";import"./init-ClFt_GA_.js";
|
package/editor/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Pixifact Editor</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-D8cbY_GQ.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-gCKoe0w8.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="app"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixifact-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Pixifact Scene 自动化 CLI。",
|
|
5
5
|
"packageManager": "bun@1.3.13",
|
|
6
6
|
"type": "module",
|
|
@@ -33,6 +33,6 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"esbuild": "^0.27.2",
|
|
36
|
-
"pixifact": "^0.
|
|
36
|
+
"pixifact": "^0.6.0"
|
|
37
37
|
}
|
|
38
38
|
}
|
package/src/automation.ts
CHANGED
|
@@ -432,7 +432,7 @@ function validateAllCompilerScenes(root: string) {
|
|
|
432
432
|
const failures = results.filter((result): result is CompilerSceneValidationFailure => !result.ok);
|
|
433
433
|
const scenes = results
|
|
434
434
|
.filter((result): result is CompilerSceneValidationSuccess => result.ok)
|
|
435
|
-
.map(({ scenePath, revision
|
|
435
|
+
.map(({ scenePath, revision }) => ({ scenePath, revision }));
|
|
436
436
|
if (failures.length > 0) {
|
|
437
437
|
return {
|
|
438
438
|
ok: false as const,
|
|
@@ -447,7 +447,6 @@ function validateAllCompilerScenes(root: string) {
|
|
|
447
447
|
ok: true as const,
|
|
448
448
|
projectRoot: root,
|
|
449
449
|
sceneCount: results.length,
|
|
450
|
-
scenes,
|
|
451
450
|
};
|
|
452
451
|
}
|
|
453
452
|
|
|
@@ -637,7 +636,7 @@ export function createPixifactAutomation() {
|
|
|
637
636
|
const nodeId = assertString(args.node, 'node');
|
|
638
637
|
const node = collectDetailedCompilerNodes(template.children).find((item) => item.locator === nodeId);
|
|
639
638
|
if (!node) {
|
|
640
|
-
throw new Error(`Node "${nodeId}" was not found.`);
|
|
639
|
+
throw new Error(`Node locator "${nodeId}" was not found.`);
|
|
641
640
|
}
|
|
642
641
|
return node;
|
|
643
642
|
},
|
package/src/editorSession.ts
CHANGED
|
@@ -4,9 +4,10 @@ import os from 'node:os';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { parseSceneTemplate } from 'pixifact/compiler';
|
|
6
6
|
|
|
7
|
-
export const editorSessionProtocolVersion =
|
|
7
|
+
export const editorSessionProtocolVersion = 3;
|
|
8
8
|
|
|
9
9
|
export type EditorContextSyncState = 'synced' | 'saving' | 'conflict' | 'error';
|
|
10
|
+
export type EditorPreviewState = 'loading' | 'ready' | 'error';
|
|
10
11
|
|
|
11
12
|
export type EditorNodeContext =
|
|
12
13
|
| {
|
|
@@ -41,6 +42,7 @@ export type EditorSelectionContext =
|
|
|
41
42
|
export interface EditorBrowserContext {
|
|
42
43
|
scene: {
|
|
43
44
|
path: string;
|
|
45
|
+
previewState: EditorPreviewState;
|
|
44
46
|
revision: string;
|
|
45
47
|
syncState: EditorContextSyncState;
|
|
46
48
|
};
|
|
@@ -75,6 +77,7 @@ interface EditorBrowserSocket {
|
|
|
75
77
|
|
|
76
78
|
interface EditorHostSessionOptions {
|
|
77
79
|
projectRoot: string;
|
|
80
|
+
screenshotTimeoutMs?: number;
|
|
78
81
|
token: string;
|
|
79
82
|
now?: () => Date;
|
|
80
83
|
}
|
|
@@ -89,6 +92,25 @@ interface ClaimEditorSessionOptions extends EditorSessionLookupOptions {
|
|
|
89
92
|
descriptor: EditorSessionDescriptor;
|
|
90
93
|
}
|
|
91
94
|
|
|
95
|
+
interface PendingEditorScreenshot {
|
|
96
|
+
browser: EditorBrowserSocket;
|
|
97
|
+
resolve(response: Response): void;
|
|
98
|
+
scene: {
|
|
99
|
+
path: string;
|
|
100
|
+
revision: string;
|
|
101
|
+
};
|
|
102
|
+
timeout: ReturnType<typeof setTimeout>;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface EditorScreenshotResult {
|
|
106
|
+
ok: true;
|
|
107
|
+
scene: string;
|
|
108
|
+
revision: string;
|
|
109
|
+
width: number;
|
|
110
|
+
height: number;
|
|
111
|
+
data: Uint8Array;
|
|
112
|
+
}
|
|
113
|
+
|
|
92
114
|
function jsonResponse(value: unknown, status = 200) {
|
|
93
115
|
return Response.json(value, { status });
|
|
94
116
|
}
|
|
@@ -138,6 +160,7 @@ function isEditorBrowserContext(value: unknown): value is EditorBrowserContext {
|
|
|
138
160
|
typeof value.scene.path !== 'string'
|
|
139
161
|
|| typeof value.scene.revision !== 'string'
|
|
140
162
|
|| !['synced', 'saving', 'conflict', 'error'].includes(String(syncState))
|
|
163
|
+
|| !['loading', 'ready', 'error'].includes(String(value.scene.previewState))
|
|
141
164
|
) {
|
|
142
165
|
return false;
|
|
143
166
|
}
|
|
@@ -152,10 +175,29 @@ function isEditorBrowserContext(value: unknown): value is EditorBrowserContext {
|
|
|
152
175
|
export function createEditorHostSession(options: EditorHostSessionOptions) {
|
|
153
176
|
const projectRoot = fs.realpathSync(options.projectRoot);
|
|
154
177
|
const now = options.now ?? (() => new Date());
|
|
178
|
+
const screenshotTimeoutMs = options.screenshotTimeoutMs ?? 10_000;
|
|
155
179
|
const browsers = new Set<EditorBrowserSocket>();
|
|
180
|
+
const pendingScreenshots = new Map<string, PendingEditorScreenshot>();
|
|
156
181
|
let activeBrowser: EditorBrowserSocket | undefined;
|
|
157
182
|
let context: EditorContext | undefined;
|
|
158
183
|
let resume: EditorSessionResumeState | undefined;
|
|
184
|
+
let screenshotSequence = 0;
|
|
185
|
+
|
|
186
|
+
function settleScreenshot(requestId: string, response: Response) {
|
|
187
|
+
const pending = pendingScreenshots.get(requestId);
|
|
188
|
+
if (!pending) return;
|
|
189
|
+
pendingScreenshots.delete(requestId);
|
|
190
|
+
clearTimeout(pending.timeout);
|
|
191
|
+
pending.resolve(response);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function rejectBrowserScreenshots(browser: EditorBrowserSocket, error: string) {
|
|
195
|
+
for (const [requestId, pending] of pendingScreenshots) {
|
|
196
|
+
if (pending.browser === browser) {
|
|
197
|
+
settleScreenshot(requestId, jsonResponse({ ok: false, error }, 409));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
159
201
|
|
|
160
202
|
function sendState(
|
|
161
203
|
socket: EditorBrowserSocket,
|
|
@@ -190,6 +232,7 @@ export function createEditorHostSession(options: EditorHostSessionOptions) {
|
|
|
190
232
|
function close(socket: EditorBrowserSocket) {
|
|
191
233
|
browsers.delete(socket);
|
|
192
234
|
if (socket === activeBrowser) {
|
|
235
|
+
rejectBrowserScreenshots(socket, 'Active Editor browser disconnected during screenshot capture.');
|
|
193
236
|
activeBrowser = undefined;
|
|
194
237
|
context = undefined;
|
|
195
238
|
}
|
|
@@ -209,6 +252,9 @@ export function createEditorHostSession(options: EditorHostSessionOptions) {
|
|
|
209
252
|
return;
|
|
210
253
|
}
|
|
211
254
|
const previous = activeBrowser;
|
|
255
|
+
if (previous) {
|
|
256
|
+
rejectBrowserScreenshots(previous, 'Active Editor browser changed during screenshot capture.');
|
|
257
|
+
}
|
|
212
258
|
activeBrowser = socket;
|
|
213
259
|
context = undefined;
|
|
214
260
|
for (const browser of browsers) {
|
|
@@ -222,6 +268,57 @@ export function createEditorHostSession(options: EditorHostSessionOptions) {
|
|
|
222
268
|
}
|
|
223
269
|
return;
|
|
224
270
|
}
|
|
271
|
+
if (
|
|
272
|
+
socket === activeBrowser
|
|
273
|
+
&& (parsed.type === 'editorScreenshotCompleted' || parsed.type === 'editorScreenshotFailed')
|
|
274
|
+
) {
|
|
275
|
+
const requestId = typeof parsed.requestId === 'string' ? parsed.requestId : '';
|
|
276
|
+
const pending = pendingScreenshots.get(requestId);
|
|
277
|
+
if (!pending || pending.browser !== socket) return;
|
|
278
|
+
if (parsed.type === 'editorScreenshotFailed') {
|
|
279
|
+
const error = typeof parsed.error === 'string'
|
|
280
|
+
? parsed.error
|
|
281
|
+
: 'Editor browser failed to capture the Scene screenshot.';
|
|
282
|
+
settleScreenshot(requestId, jsonResponse({ ok: false, error }, 500));
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (
|
|
286
|
+
!isRecord(parsed.scene)
|
|
287
|
+
|| parsed.scene.path !== pending.scene.path
|
|
288
|
+
|| parsed.scene.revision !== pending.scene.revision
|
|
289
|
+
|| !Number.isInteger(parsed.width)
|
|
290
|
+
|| Number(parsed.width) <= 0
|
|
291
|
+
|| !Number.isInteger(parsed.height)
|
|
292
|
+
|| Number(parsed.height) <= 0
|
|
293
|
+
|| typeof parsed.dataUrl !== 'string'
|
|
294
|
+
) {
|
|
295
|
+
settleScreenshot(requestId, jsonResponse({
|
|
296
|
+
ok: false,
|
|
297
|
+
error: 'Editor browser returned an invalid screenshot response.',
|
|
298
|
+
}, 502));
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const match = parsed.dataUrl.match(/^data:image\/png;base64,([A-Za-z0-9+/]+={0,2})$/);
|
|
302
|
+
const png = match ? Buffer.from(match[1], 'base64') : undefined;
|
|
303
|
+
if (!png || !png.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) {
|
|
304
|
+
settleScreenshot(requestId, jsonResponse({
|
|
305
|
+
ok: false,
|
|
306
|
+
error: 'Editor browser did not return PNG screenshot data.',
|
|
307
|
+
}, 502));
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
settleScreenshot(requestId, new Response(png, {
|
|
311
|
+
headers: {
|
|
312
|
+
'cache-control': 'no-store',
|
|
313
|
+
'content-type': 'image/png',
|
|
314
|
+
'x-pixifact-height': String(parsed.height),
|
|
315
|
+
'x-pixifact-revision': pending.scene.revision,
|
|
316
|
+
'x-pixifact-scene': encodeURIComponent(pending.scene.path),
|
|
317
|
+
'x-pixifact-width': String(parsed.width),
|
|
318
|
+
},
|
|
319
|
+
}));
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
225
322
|
if (socket !== activeBrowser || parsed.type !== 'editorContextChanged') return;
|
|
226
323
|
if (!isEditorBrowserContext(parsed.context)) {
|
|
227
324
|
throw new Error('Editor context message is invalid.');
|
|
@@ -244,7 +341,7 @@ export function createEditorHostSession(options: EditorHostSessionOptions) {
|
|
|
244
341
|
notifyStandbyBrowsers();
|
|
245
342
|
}
|
|
246
343
|
|
|
247
|
-
function
|
|
344
|
+
function synchronizedContext() {
|
|
248
345
|
if (!activeBrowser) {
|
|
249
346
|
return jsonResponse({
|
|
250
347
|
ok: false,
|
|
@@ -297,12 +394,54 @@ export function createEditorHostSession(options: EditorHostSessionOptions) {
|
|
|
297
394
|
},
|
|
298
395
|
}, 409);
|
|
299
396
|
}
|
|
300
|
-
return
|
|
397
|
+
return context;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function contextResponse() {
|
|
401
|
+
const result = synchronizedContext();
|
|
402
|
+
return result instanceof Response ? result : jsonResponse(result);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function screenshotResponse() {
|
|
406
|
+
const result = synchronizedContext();
|
|
407
|
+
if (result instanceof Response) return result;
|
|
408
|
+
if (result.scene.previewState !== 'ready') {
|
|
409
|
+
return jsonResponse({
|
|
410
|
+
ok: false,
|
|
411
|
+
error: 'Editor Scene preview is not ready.',
|
|
412
|
+
previewState: result.scene.previewState,
|
|
413
|
+
}, 409);
|
|
414
|
+
}
|
|
415
|
+
const browser = activeBrowser!;
|
|
416
|
+
const requestId = `screenshot-${++screenshotSequence}`;
|
|
417
|
+
const scene = {
|
|
418
|
+
path: result.scene.path,
|
|
419
|
+
revision: result.scene.revision,
|
|
420
|
+
};
|
|
421
|
+
return new Promise<Response>((resolve) => {
|
|
422
|
+
const timeout = setTimeout(() => {
|
|
423
|
+
settleScreenshot(requestId, jsonResponse({
|
|
424
|
+
ok: false,
|
|
425
|
+
error: 'Editor screenshot capture timed out.',
|
|
426
|
+
}, 504));
|
|
427
|
+
}, screenshotTimeoutMs);
|
|
428
|
+
pendingScreenshots.set(requestId, { browser, resolve, scene, timeout });
|
|
429
|
+
browser.send(JSON.stringify({
|
|
430
|
+
type: 'editorScreenshotRequested',
|
|
431
|
+
protocolVersion: editorSessionProtocolVersion,
|
|
432
|
+
requestId,
|
|
433
|
+
scene,
|
|
434
|
+
}));
|
|
435
|
+
});
|
|
301
436
|
}
|
|
302
437
|
|
|
303
438
|
function fetchRequest(request: Request) {
|
|
304
439
|
const pathname = new URL(request.url).pathname;
|
|
305
|
-
if (
|
|
440
|
+
if (
|
|
441
|
+
pathname !== '/api/editor-session/health'
|
|
442
|
+
&& pathname !== '/api/editor-context'
|
|
443
|
+
&& pathname !== '/api/editor-screenshot'
|
|
444
|
+
) {
|
|
306
445
|
return undefined;
|
|
307
446
|
}
|
|
308
447
|
if (!authorizationMatches(request, options.token)) {
|
|
@@ -314,6 +453,12 @@ export function createEditorHostSession(options: EditorHostSessionOptions) {
|
|
|
314
453
|
projectRoot,
|
|
315
454
|
});
|
|
316
455
|
}
|
|
456
|
+
if (pathname === '/api/editor-screenshot') {
|
|
457
|
+
if (request.method !== 'POST') {
|
|
458
|
+
return jsonResponse({ ok: false, error: 'Editor screenshot requires POST.' }, 405);
|
|
459
|
+
}
|
|
460
|
+
return screenshotResponse();
|
|
461
|
+
}
|
|
317
462
|
return contextResponse();
|
|
318
463
|
}
|
|
319
464
|
|
|
@@ -488,3 +633,65 @@ export async function queryEditorContext(options: EditorSessionLookupOptions) {
|
|
|
488
633
|
};
|
|
489
634
|
}
|
|
490
635
|
}
|
|
636
|
+
|
|
637
|
+
export async function captureEditorScreenshot(
|
|
638
|
+
options: EditorSessionLookupOptions,
|
|
639
|
+
): Promise<EditorScreenshotResult | { ok: false; error: string; [key: string]: unknown }> {
|
|
640
|
+
const projectRoot = fs.realpathSync(options.projectRoot);
|
|
641
|
+
const descriptor = readEditorSessionDescriptor(projectRoot, options.sessionsRoot);
|
|
642
|
+
if (!descriptor) {
|
|
643
|
+
return {
|
|
644
|
+
ok: false,
|
|
645
|
+
error: 'No Pixifact Editor Host is running for this project.',
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
if (descriptor.projectRoot !== projectRoot) {
|
|
649
|
+
return {
|
|
650
|
+
ok: false,
|
|
651
|
+
error: 'Pixifact Editor session belongs to a different project.',
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
const fetcher = options.fetch ?? fetch;
|
|
655
|
+
let response: Response;
|
|
656
|
+
try {
|
|
657
|
+
response = await fetcher(`${descriptor.origin}/api/editor-screenshot`, {
|
|
658
|
+
method: 'POST',
|
|
659
|
+
headers: { authorization: `Bearer ${descriptor.token}` },
|
|
660
|
+
});
|
|
661
|
+
} catch {
|
|
662
|
+
return {
|
|
663
|
+
ok: false,
|
|
664
|
+
error: 'Pixifact Editor Host is not reachable for this project.',
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
if (!response.ok) {
|
|
668
|
+
return await response.json() as { ok: false; error: string; [key: string]: unknown };
|
|
669
|
+
}
|
|
670
|
+
const sceneHeader = response.headers.get('x-pixifact-scene');
|
|
671
|
+
const revision = response.headers.get('x-pixifact-revision');
|
|
672
|
+
const width = Number(response.headers.get('x-pixifact-width'));
|
|
673
|
+
const height = Number(response.headers.get('x-pixifact-height'));
|
|
674
|
+
if (
|
|
675
|
+
response.headers.get('content-type') !== 'image/png'
|
|
676
|
+
|| !sceneHeader
|
|
677
|
+
|| !revision
|
|
678
|
+
|| !Number.isInteger(width)
|
|
679
|
+
|| width <= 0
|
|
680
|
+
|| !Number.isInteger(height)
|
|
681
|
+
|| height <= 0
|
|
682
|
+
) {
|
|
683
|
+
return { ok: false, error: 'Pixifact Editor Host returned invalid screenshot metadata.' };
|
|
684
|
+
}
|
|
685
|
+
const data = new Uint8Array(await response.arrayBuffer());
|
|
686
|
+
if (!Buffer.from(data.subarray(0, 8)).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10]))) {
|
|
687
|
+
return { ok: false, error: 'Pixifact Editor Host did not return PNG screenshot data.' };
|
|
688
|
+
}
|
|
689
|
+
return {
|
|
690
|
+
ok: true,
|
|
691
|
+
scene: decodeURIComponent(sceneHeader),
|
|
692
|
+
revision,
|
|
693
|
+
width,
|
|
694
|
+
height,
|
|
695
|
+
data,
|
|
696
|
+
};
|
|
697
|
+
}
|
package/src/pixifact-cli.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
2
4
|
import { createPixifactAutomation } from './automation';
|
|
3
5
|
import { hintForCommandError } from 'pixifact';
|
|
4
6
|
import { CompileSceneError, compileScenes } from 'pixifact/compiler-node';
|
|
5
7
|
import type { SceneValidationDiagnostic } from 'pixifact/compiler';
|
|
6
8
|
import { startEditorServer } from './editorServer';
|
|
7
|
-
import { queryEditorContext } from './editorSession';
|
|
9
|
+
import { captureEditorScreenshot, queryEditorContext } from './editorSession';
|
|
10
|
+
import { queryRuntime, queryRuntimeList } from './runtimeSession';
|
|
11
|
+
import type { RuntimeLogLevel, RuntimeRequest } from 'pixifact/runtime-dev';
|
|
8
12
|
import {
|
|
9
13
|
buildWechatTarget,
|
|
10
14
|
devWechatTarget,
|
|
@@ -17,8 +21,11 @@ type Automation = ReturnType<typeof createPixifactAutomation>;
|
|
|
17
21
|
|
|
18
22
|
interface CliOptions {
|
|
19
23
|
automation?: Automation;
|
|
24
|
+
captureEditorScreenshot?: typeof captureEditorScreenshot;
|
|
20
25
|
onWechatDevEvent?: (event: WechatDevEvent) => void;
|
|
21
26
|
readEditorContext?: typeof queryEditorContext;
|
|
27
|
+
listRuntimes?: typeof queryRuntimeList;
|
|
28
|
+
queryRuntime?: typeof queryRuntime;
|
|
22
29
|
startEditor?: typeof startEditorServer;
|
|
23
30
|
}
|
|
24
31
|
|
|
@@ -82,6 +89,23 @@ function projectRootFlag(flags: Record<string, string | true>) {
|
|
|
82
89
|
return requireFlag(flags, 'project-root');
|
|
83
90
|
}
|
|
84
91
|
|
|
92
|
+
function optionalFlag(flags: Record<string, string | true>, name: string) {
|
|
93
|
+
const value = flags[name];
|
|
94
|
+
return value === undefined ? undefined : requireFlag(flags, name);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function finiteNumberFlag(flags: Record<string, string | true>, name: string) {
|
|
98
|
+
const value = Number(requireFlag(flags, name));
|
|
99
|
+
if (!Number.isFinite(value)) throw new Error(`--${name} must be a finite number.`);
|
|
100
|
+
return value;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function nonNegativeInteger(value: string | undefined, error: string) {
|
|
104
|
+
const number = Number(value);
|
|
105
|
+
if (!Number.isInteger(number) || number < 0) throw new Error(error);
|
|
106
|
+
return number;
|
|
107
|
+
}
|
|
108
|
+
|
|
85
109
|
function jsonLine(value: unknown) {
|
|
86
110
|
return `${JSON.stringify(value, null, 2)}\n`;
|
|
87
111
|
}
|
|
@@ -92,6 +116,13 @@ function isFailedResult(value: unknown): value is CliJsonResult {
|
|
|
92
116
|
&& (value as CliJsonResult).ok === false;
|
|
93
117
|
}
|
|
94
118
|
|
|
119
|
+
function isRuntimeTreeResult(value: unknown): value is { runtimeId: string; root: unknown } {
|
|
120
|
+
return typeof value === 'object'
|
|
121
|
+
&& value !== null
|
|
122
|
+
&& typeof (value as { runtimeId?: unknown }).runtimeId === 'string'
|
|
123
|
+
&& 'root' in value;
|
|
124
|
+
}
|
|
125
|
+
|
|
95
126
|
function compileScenesFailure(error: unknown): CliJsonResult {
|
|
96
127
|
const message = error instanceof Error ? error.message : String(error);
|
|
97
128
|
if (error instanceof CompileSceneError) {
|
|
@@ -228,10 +259,88 @@ async function executeFileCommand(
|
|
|
228
259
|
onWechatDevEvent?: (event: WechatDevEvent) => void,
|
|
229
260
|
startEditor: typeof startEditorServer = startEditorServer,
|
|
230
261
|
readEditorContext: typeof queryEditorContext = queryEditorContext,
|
|
262
|
+
captureScreenshot: typeof captureEditorScreenshot = captureEditorScreenshot,
|
|
263
|
+
listRuntimes: typeof queryRuntimeList = queryRuntimeList,
|
|
264
|
+
sendRuntimeRequest: typeof queryRuntime = queryRuntime,
|
|
231
265
|
) {
|
|
232
266
|
const [area, action] = positionals;
|
|
233
267
|
const projectRoot = projectRootFlag(flags);
|
|
234
268
|
|
|
269
|
+
if (area === 'runtime' && action === 'list') {
|
|
270
|
+
return listRuntimes({ projectRoot });
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (area === 'runtime' && action !== undefined) {
|
|
274
|
+
const runtimeId = optionalFlag(flags, 'runtime');
|
|
275
|
+
let request: RuntimeRequest;
|
|
276
|
+
if (action === 'tree') {
|
|
277
|
+
request = { type: 'tree' };
|
|
278
|
+
} else if (action === 'node') {
|
|
279
|
+
request = {
|
|
280
|
+
type: 'node',
|
|
281
|
+
uid: nonNegativeInteger(
|
|
282
|
+
positionals[2],
|
|
283
|
+
'Runtime node uid must be a non-negative integer.',
|
|
284
|
+
),
|
|
285
|
+
};
|
|
286
|
+
} else if (action === 'state') {
|
|
287
|
+
request = { type: 'state' };
|
|
288
|
+
} else if (action === 'logs') {
|
|
289
|
+
const after = flags.after === undefined
|
|
290
|
+
? undefined
|
|
291
|
+
: nonNegativeInteger(requireFlag(flags, 'after'), '--after must be a non-negative integer.');
|
|
292
|
+
const level = optionalFlag(flags, 'level');
|
|
293
|
+
if (level && !['debug', 'log', 'info', 'warn', 'error'].includes(level)) {
|
|
294
|
+
throw new Error('--level must be debug, log, info, warn, or error.');
|
|
295
|
+
}
|
|
296
|
+
request = {
|
|
297
|
+
type: 'logs',
|
|
298
|
+
...(after === undefined ? {} : { after }),
|
|
299
|
+
...(level === undefined ? {} : { level: level as RuntimeLogLevel }),
|
|
300
|
+
};
|
|
301
|
+
} else if (action === 'input') {
|
|
302
|
+
const inputAction = positionals[2];
|
|
303
|
+
if (inputAction === 'click' || inputAction === 'move') {
|
|
304
|
+
request = {
|
|
305
|
+
type: 'input',
|
|
306
|
+
action: inputAction,
|
|
307
|
+
x: finiteNumberFlag(flags, 'x'),
|
|
308
|
+
y: finiteNumberFlag(flags, 'y'),
|
|
309
|
+
};
|
|
310
|
+
} else if (inputAction === 'key' || inputAction === 'keydown' || inputAction === 'keyup') {
|
|
311
|
+
const key = positionals[3];
|
|
312
|
+
if (!key) throw new Error(`runtime input ${inputAction} requires a key.`);
|
|
313
|
+
request = { type: 'input', action: inputAction, key };
|
|
314
|
+
} else {
|
|
315
|
+
throw new Error(`Unknown Pixifact Runtime input "${inputAction ?? ''}".`);
|
|
316
|
+
}
|
|
317
|
+
} else {
|
|
318
|
+
throw new Error(`Unknown Pixifact CLI command "${positionals.join(' ')}".`);
|
|
319
|
+
}
|
|
320
|
+
const result = await sendRuntimeRequest({ projectRoot, runtimeId, request });
|
|
321
|
+
if (action !== 'tree' || flags.output === undefined || isFailedResult(result)) {
|
|
322
|
+
return result;
|
|
323
|
+
}
|
|
324
|
+
if (!isRuntimeTreeResult(result)) {
|
|
325
|
+
throw new Error('Pixifact Runtime returned an invalid tree response.');
|
|
326
|
+
}
|
|
327
|
+
const output = path.resolve(requireFlag(flags, 'output'));
|
|
328
|
+
const capturedAt = new Date().toISOString();
|
|
329
|
+
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
330
|
+
fs.writeFileSync(output, jsonLine({
|
|
331
|
+
schemaVersion: 1,
|
|
332
|
+
capturedAt,
|
|
333
|
+
runtimeId: result.runtimeId,
|
|
334
|
+
root: result.root,
|
|
335
|
+
}), 'utf8');
|
|
336
|
+
return {
|
|
337
|
+
ok: true,
|
|
338
|
+
output,
|
|
339
|
+
runtimeId: result.runtimeId,
|
|
340
|
+
capturedAt,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
235
344
|
if (area === 'editor' && action === undefined) {
|
|
236
345
|
const session = await startEditor({ projectRoot });
|
|
237
346
|
return {
|
|
@@ -245,6 +354,22 @@ async function executeFileCommand(
|
|
|
245
354
|
return readEditorContext({ projectRoot });
|
|
246
355
|
}
|
|
247
356
|
|
|
357
|
+
if (area === 'editor' && action === 'screenshot') {
|
|
358
|
+
const output = path.resolve(requireFlag(flags, 'output'));
|
|
359
|
+
const screenshot = await captureScreenshot({ projectRoot });
|
|
360
|
+
if (!screenshot.ok) return screenshot;
|
|
361
|
+
fs.writeFileSync(output, screenshot.data);
|
|
362
|
+
return {
|
|
363
|
+
ok: true,
|
|
364
|
+
scene: screenshot.scene,
|
|
365
|
+
revision: screenshot.revision,
|
|
366
|
+
width: screenshot.width,
|
|
367
|
+
height: screenshot.height,
|
|
368
|
+
bytes: screenshot.data.byteLength,
|
|
369
|
+
output,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
|
|
248
373
|
if ((area === 'build' || area === 'dev' || area === 'validate') && action === undefined) {
|
|
249
374
|
const target = requireFlag(flags, 'target');
|
|
250
375
|
if (target !== 'wechat') {
|
|
@@ -392,12 +517,26 @@ export async function executePixifactCli(argv: string[], options: CliOptions = {
|
|
|
392
517
|
aiValidationAlternatives: [
|
|
393
518
|
'scene validate --all',
|
|
394
519
|
],
|
|
520
|
+
runtimeCommands: [
|
|
521
|
+
'runtime list',
|
|
522
|
+
'runtime tree [--output <json-path>] [--runtime <runtime-id>]',
|
|
523
|
+
'runtime node <pixi-uid> [--runtime <runtime-id>]',
|
|
524
|
+
'runtime state [--runtime <runtime-id>]',
|
|
525
|
+
'runtime logs [--after <seq>] [--level <level>] [--runtime <runtime-id>]',
|
|
526
|
+
'runtime input click --x <x> --y <y> [--runtime <runtime-id>]',
|
|
527
|
+
'runtime input move --x <x> --y <y> [--runtime <runtime-id>]',
|
|
528
|
+
'runtime input key <key> [--runtime <runtime-id>]',
|
|
529
|
+
'runtime input keydown <key> [--runtime <runtime-id>]',
|
|
530
|
+
'runtime input keyup <key> [--runtime <runtime-id>]',
|
|
531
|
+
],
|
|
395
532
|
auxiliaryCommands: [
|
|
396
533
|
'editor',
|
|
397
534
|
'editor context',
|
|
535
|
+
'editor screenshot --output <png-path>',
|
|
398
536
|
'scene create --scene <scene-path> --name <SceneName>',
|
|
399
537
|
'node inspect --scene <scene-path> --node <locator>',
|
|
400
538
|
],
|
|
539
|
+
nodeLocatorSource: 'scene inspect --scene <scene-path> returns node locator values',
|
|
401
540
|
defaults: {
|
|
402
541
|
projectRoot: 'current working directory',
|
|
403
542
|
},
|
|
@@ -414,6 +553,9 @@ export async function executePixifactCli(argv: string[], options: CliOptions = {
|
|
|
414
553
|
options.onWechatDevEvent,
|
|
415
554
|
options.startEditor,
|
|
416
555
|
options.readEditorContext,
|
|
556
|
+
options.captureEditorScreenshot,
|
|
557
|
+
options.listRuntimes,
|
|
558
|
+
options.queryRuntime,
|
|
417
559
|
);
|
|
418
560
|
if (isFailedResult(result)) {
|
|
419
561
|
return {
|