@ttmg/cli 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/dist/index.js +126 -18
  2. package/dist/index.js.map +1 -1
  3. package/dist/public/assets/Detail-DNF1PK-R.js +1 -0
  4. package/dist/public/assets/Detail-DNF1PK-R.js.br +0 -0
  5. package/dist/public/assets/MonetizationMode-C59U6Ari.js +1 -0
  6. package/dist/public/assets/MonetizationModeSummary-BR2LJFsU.js +1 -0
  7. package/dist/public/assets/SectionHeader-CCjfBKcF.js +1 -0
  8. package/dist/public/assets/Tag-DON36Of0.js +1 -0
  9. package/dist/public/assets/baseForm-B3oZF8Vp.js +10 -0
  10. package/dist/public/assets/baseForm-B3oZF8Vp.js.br +0 -0
  11. package/dist/public/assets/baseForm-BAvk37jF.css +1 -0
  12. package/dist/public/assets/baseForm-BAvk37jF.css.br +0 -0
  13. package/dist/public/assets/{boxes-C7RoIbId.js → boxes-EbMNfQ6U.js} +1 -1
  14. package/dist/public/assets/{chevron-right-B1WBZrrD.js → chevron-right-CsmjM7t0.js} +1 -1
  15. package/dist/public/assets/col-BXewy14r.css +1 -0
  16. package/dist/public/assets/col-BXewy14r.css.br +0 -0
  17. package/dist/public/assets/col-DWGIx7fj.js +1 -0
  18. package/dist/public/assets/{compass-VJNhHwqW.js → compass-bMD5nDM8.js} +1 -1
  19. package/dist/public/assets/index-B4j49Ts7.js +1 -0
  20. package/dist/public/assets/index-B4j49Ts7.js.br +0 -0
  21. package/dist/public/assets/{index-a-rMUS2V.js → index-B5JnznvI.js} +1 -1
  22. package/dist/public/assets/index-BCV7VBUL.js +1 -0
  23. package/dist/public/assets/index-BCV7VBUL.js.br +0 -0
  24. package/dist/public/assets/index-BKOO94x-.css +1 -0
  25. package/dist/public/assets/index-BPqSXmk6.js +1 -0
  26. package/dist/public/assets/index-Bn_Zugqv.js +1 -0
  27. package/dist/public/assets/index-Bn_Zugqv.js.br +0 -0
  28. package/dist/public/assets/index-BtO50Nho.js +1 -0
  29. package/dist/public/assets/index-BtO50Nho.js.br +0 -0
  30. package/dist/public/assets/{index-C_npSvtQ.js → index-BzEQAUVA.js} +1 -1
  31. package/dist/public/assets/{index-Dn5HHf-0.js → index-CAoj7RMM.js} +1 -1
  32. package/dist/public/assets/index-CKUVWl_O.css +1 -0
  33. package/dist/public/assets/index-CMwzPrAh.css +1 -0
  34. package/dist/public/assets/index-CMwzPrAh.css.br +0 -0
  35. package/dist/public/assets/index-CYnulX57.css +1 -0
  36. package/dist/public/assets/index-CYnulX57.css.br +0 -0
  37. package/dist/public/assets/{index-BH099q_U.js → index-Ca-cQ1Oq.js} +1 -1
  38. package/dist/public/assets/{index-s_30KWiA.js → index-CdJUBcSF.js} +1 -1
  39. package/dist/public/assets/{index-bQUQ2Wmz.js → index-Cf8aH-xI.js} +1 -1
  40. package/dist/public/assets/{index-DMsjcIN8.js → index-D4lGRib1.js} +1 -1
  41. package/dist/public/assets/index-D5-FwYf-.js +1 -0
  42. package/dist/public/assets/index-D5-FwYf-.js.br +0 -0
  43. package/dist/public/assets/{index-BO5MBsdm.js → index-DH7hBDqZ.js} +1 -1
  44. package/dist/public/assets/index-DKxf26_O.css +1 -0
  45. package/dist/public/assets/index-DOMxPC_v.css +1 -0
  46. package/dist/public/assets/index-DOMxPC_v.css.br +0 -0
  47. package/dist/public/assets/index-DRr-Punk.js +1 -0
  48. package/dist/public/assets/index-DV0N-QmJ.js +1 -0
  49. package/dist/public/assets/index-DV0N-QmJ.js.br +0 -0
  50. package/dist/public/assets/index-DXEfgIqc.js +1 -0
  51. package/dist/public/assets/{index-BBR3CPkN.js → index-DaOr2P0A.js} +1 -1
  52. package/dist/public/assets/index-DaOr2P0A.js.br +0 -0
  53. package/dist/public/assets/index-Dv0bhjYp.js +1 -0
  54. package/dist/public/assets/{index-qqnHwFjO.js → index-Dx-boSFk.js} +1 -1
  55. package/dist/public/assets/{index-C1C34gtn.js → index-FYgsnJbm.js} +1 -1
  56. package/dist/public/assets/index-HqjZ5P-l.css +1 -0
  57. package/dist/public/assets/{index-BJZ4gOGZ.css → index-V1Go68Vw.css} +1 -1
  58. package/dist/public/assets/index-V1Go68Vw.css.br +0 -0
  59. package/dist/public/assets/index-VvBtuIHK.css +1 -0
  60. package/dist/public/assets/index-VvBtuIHK.css.br +0 -0
  61. package/dist/public/assets/index-VvUpVuij.js +21 -0
  62. package/dist/public/assets/index-VvUpVuij.js.br +0 -0
  63. package/dist/public/assets/index-VzIFpCiL.js +1 -0
  64. package/dist/public/assets/{index-DbxY65Om.js → index-bASdAMHs.js} +1 -1
  65. package/dist/public/assets/index-q7fKJFmZ.js +1 -0
  66. package/dist/public/assets/index-vlN_-PRJ.css +1 -0
  67. package/dist/public/assets/{layers-Biv61qtE.js → layers-C8HCSIXn.js} +1 -1
  68. package/dist/public/assets/list-checks-B6t2uu5m.js +1 -0
  69. package/dist/public/assets/{sparkles-Bv2GxrFr.js → sparkles-pLu1TMVV.js} +1 -1
  70. package/dist/public/assets/triangle-alert-Dm4_sEwV.js +1 -0
  71. package/dist/public/assets/{zap-CjkC_qWZ.js → zap-tefJh46p.js} +1 -1
  72. package/dist/public/index.html +2 -2
  73. package/dist/scripts/docs/README.md +80 -0
  74. package/dist/scripts/docs/capture.js +84 -6
  75. package/dist/scripts/docs/check.js +184 -0
  76. package/dist/scripts/docs/render.js +283 -6
  77. package/dist/scripts/docs/scenarios/devtool-guide.mjs +802 -0
  78. package/package.json +1 -1
  79. package/CHANGELOG.md +0 -267
  80. package/dist/public/assets/Card-ASGGlJ8S.js +0 -1
  81. package/dist/public/assets/Detail-DY__Zw0r.js +0 -1
  82. package/dist/public/assets/Detail-DY__Zw0r.js.br +0 -0
  83. package/dist/public/assets/MonetizationMode-sHN_4ybQ.js +0 -1
  84. package/dist/public/assets/MonetizationModeSummary-DLvyriLM.js +0 -1
  85. package/dist/public/assets/SectionHeader-B7dueBCf.js +0 -1
  86. package/dist/public/assets/Tag-BsX0d6UL.js +0 -1
  87. package/dist/public/assets/arrow-left-uixm7Sd1.js +0 -1
  88. package/dist/public/assets/baseForm-C8XfTFyG.js +0 -10
  89. package/dist/public/assets/baseForm-C8XfTFyG.js.br +0 -0
  90. package/dist/public/assets/baseForm-Dl4zA6hU.css +0 -1
  91. package/dist/public/assets/baseForm-Dl4zA6hU.css.br +0 -0
  92. package/dist/public/assets/circle-check-wUxXeINd.js +0 -1
  93. package/dist/public/assets/index-BAud2cRu.css +0 -1
  94. package/dist/public/assets/index-BBR3CPkN.js.br +0 -0
  95. package/dist/public/assets/index-BJZ4gOGZ.css.br +0 -0
  96. package/dist/public/assets/index-BNc8JtET.js +0 -1
  97. package/dist/public/assets/index-Bd-f8ERX.js +0 -1
  98. package/dist/public/assets/index-ByX5RuFA.css +0 -1
  99. package/dist/public/assets/index-C7Tmy9g3.js +0 -1
  100. package/dist/public/assets/index-CCKb9Y-j.js +0 -14
  101. package/dist/public/assets/index-CCKb9Y-j.js.br +0 -0
  102. package/dist/public/assets/index-CFd8iglC.css +0 -1
  103. package/dist/public/assets/index-CFd8iglC.css.br +0 -0
  104. package/dist/public/assets/index-CKhfKyA3.js +0 -1
  105. package/dist/public/assets/index-CKhfKyA3.js.br +0 -0
  106. package/dist/public/assets/index-Cc1ilXmc.css +0 -1
  107. package/dist/public/assets/index-DHXlsTU6.css +0 -1
  108. package/dist/public/assets/index-DHXlsTU6.css.br +0 -0
  109. package/dist/public/assets/index-DNd8ZTok.js +0 -1
  110. package/dist/public/assets/index-DNd8ZTok.js.br +0 -0
  111. package/dist/public/assets/index-DpkktYot.css +0 -1
  112. package/dist/public/assets/index-DtoDrber.js +0 -1
  113. package/dist/public/assets/index-DtoDrber.js.br +0 -0
  114. package/dist/public/assets/index-ZHPtAdJM.js +0 -1
  115. package/dist/public/assets/index-ZHPtAdJM.js.br +0 -0
  116. package/dist/public/assets/index-ZtlCPCV9.js +0 -1
  117. package/dist/public/assets/index-_6n0s04V.css +0 -1
  118. package/dist/public/assets/index-_6n0s04V.css.br +0 -0
  119. package/dist/public/assets/index-r3aSeNL1.js +0 -1
  120. package/dist/public/assets/index-t4JeqZ4a.js +0 -1
@@ -0,0 +1 @@
1
+ import{u as U,h as k,r as o,j as e,p as D,i as a,z as y,a9 as F,d as A,T as m,b as E,as as I}from"./index-VvUpVuij.js";import{I as x,F as g}from"./baseForm-B3oZF8Vp.js";import"./col-DWGIx7fj.js";import"./index-DaOr2P0A.js";const B=()=>{const{state:{user:h}}=U(),r=k(h),[f,i]=o.useState(!1),[j,l]=o.useState(!1),[v,c]=o.useState(""),[d,n]=o.useState(""),p=o.useRef(null),u=()=>{l(!1),n(""),c("")},C=()=>{i(!1),u(),p.current?.formApi?.reset?.()},S=()=>{r&&i(!0)},N=s=>{const t=s;if(t?.code===0)I.success({title:a("uploadPlatformSuccess"),content:e.jsxs(e.Fragment,{children:[e.jsx("div",{children:a("uploadPlatformDesc")}),e.jsx("div",{style:{marginTop:12,display:"flex",justifyContent:"flex-end"},children:e.jsx(m.Text,{link:!0,onClick:()=>{window.open(t?.data?.detail,"_blank")},children:a("viewOnPlatform")})})]}),duration:10}),i(!1),u();else{const P=t?.error?.StatusMessage||t?.detail||a("uploadPlatformFailed");n(P),l(!1)}},T=s=>{const t=s?.error?.StatusMessage||s?.response?.detail||s?.message||a("uploadPlatformFailed");n(t),l(!1)},b=()=>{l(!0),E({method:"POST",url:"/game/upload",headers:{"ttmg-game-desc":encodeURIComponent(v||"--")}}).then(s=>{N(s)}).catch(s=>{T(s)})};return e.jsxs(e.Fragment,{children:[e.jsx(D,{content:()=>r?null:e.jsx("div",{className:"uploader-login-tip",children:a("loginToUpload")}),children:e.jsxs("div",{className:y("resource-uploader",{"resource-uploader-disabled":!r}),onClick:S,children:[e.jsx(x,{size:"default"}),e.jsx("div",{className:"uploader-text",children:a("upload")})]})}),e.jsx(F,{footer:null,visible:f,onCancel:C,title:a("uploadToPlatform"),children:e.jsxs("div",{className:"uploader-modal-content",children:[j&&e.jsx("div",{className:"upload-loading-container",children:e.jsx("div",{className:"loading-spin",children:e.jsx(A,{size:"large"})})}),d&&e.jsx(m.Text,{style:{color:"var(--tt-error-text)",marginBottom:12,marginTop:12},children:d}),e.jsxs(g,{ref:p,onChange:({values:s})=>{c(s?.gameDescription||"--"),n("")},children:[e.jsx(g.TextArea,{label:a("gameAssetDescription"),placeholder:a("gameAssetDescription"),field:"gameDescription"}),e.jsxs("div",{className:"uploader-button-container",onClick:b,children:[e.jsx(x,{className:"uploader-button-icon"}),e.jsx("div",{className:"uploader-button-text",children:a("uploadGamePackageTip")})]})]})]})})]})};export{B as GameUploader};
@@ -1 +1 @@
1
- import{w as v,z as E,R as r,A as O,T as N}from"./index-CCKb9Y-j.js";const x={PREFIX:`${v}-empty`};var M=function(i,a){var s={};for(var e in i)Object.prototype.hasOwnProperty.call(i,e)&&a.indexOf(e)<0&&(s[e]=i[e]);if(i!=null&&typeof Object.getOwnPropertySymbols=="function")for(var t=0,e=Object.getOwnPropertySymbols(i);t<e.length;t++)a.indexOf(e[t])<0&&Object.prototype.propertyIsEnumerable.call(i,e[t])&&(s[e[t]]=i[e[t]]);return s};const o=x.PREFIX;class S extends E{constructor(a){super(a),this.observe=s=>{for(const e of s)e.type==="attributes"&&e.attributeName==="theme-mode"&&this.updateMode()},this.updateMode=()=>{const s=this.body.getAttribute("theme-mode");s!==this.state.mode&&this.setState({mode:s})},this.state={mode:null}}componentDidMount(){if(this.props.darkModeImage){this.body=window.document.body,this.updateMode();const a={attributes:!0,childList:!1,subtree:!1};this.observer=new MutationObserver(this.observe),this.observer.observe(this.body,a)}}componentWillUnmount(){this.observer&&this.observer.disconnect()}render(){const a=this.props,{className:s,image:e,description:t,style:u,title:c,imageStyle:h,children:d,layout:m,darkModeImage:p}=a,y=M(a,["className","image","description","style","title","imageStyle","children","layout","darkModeImage"]),f=typeof t=="string"?t:"empty",n=this.state.mode==="dark"&&p?p:e;let l=null;typeof n=="string"?l=r.createElement("img",{alt:f,src:n}):n&&"id"in n?l=r.createElement("svg",{"aria-hidden":"true"},r.createElement("use",{xlinkHref:`#${n.id}`})):l=n;const g=O(s,o,{[`${o}-${m}`]:m}),b=l?{heading:4}:{heading:6,style:{fontWeight:400}};return r.createElement("div",Object.assign({className:g,style:u},this.getDataAttr(y)),r.createElement("div",{className:`${o}-image`,style:h,"x-semi-prop":"image,darkModeImage"},l),r.createElement("div",{className:`${o}-content`},c?r.createElement(N.Title,Object.assign({},b,{className:`${o}-title`,"x-semi-prop":"title"}),c):null,t?r.createElement("div",{className:`${o}-description`,"x-semi-prop":"description"},t):null,d?r.createElement("div",{className:`${o}-footer`,"x-semi-prop":"children"},d):null))}}S.defaultProps={layout:"vertical"};export{S as E};
1
+ import{t as v,y as E,R as r,z as O,T as N}from"./index-VvUpVuij.js";const x={PREFIX:`${v}-empty`};var M=function(i,a){var s={};for(var e in i)Object.prototype.hasOwnProperty.call(i,e)&&a.indexOf(e)<0&&(s[e]=i[e]);if(i!=null&&typeof Object.getOwnPropertySymbols=="function")for(var t=0,e=Object.getOwnPropertySymbols(i);t<e.length;t++)a.indexOf(e[t])<0&&Object.prototype.propertyIsEnumerable.call(i,e[t])&&(s[e[t]]=i[e[t]]);return s};const o=x.PREFIX;class S extends E{constructor(a){super(a),this.observe=s=>{for(const e of s)e.type==="attributes"&&e.attributeName==="theme-mode"&&this.updateMode()},this.updateMode=()=>{const s=this.body.getAttribute("theme-mode");s!==this.state.mode&&this.setState({mode:s})},this.state={mode:null}}componentDidMount(){if(this.props.darkModeImage){this.body=window.document.body,this.updateMode();const a={attributes:!0,childList:!1,subtree:!1};this.observer=new MutationObserver(this.observe),this.observer.observe(this.body,a)}}componentWillUnmount(){this.observer&&this.observer.disconnect()}render(){const a=this.props,{className:s,image:e,description:t,style:u,title:c,imageStyle:h,children:d,layout:m,darkModeImage:p}=a,y=M(a,["className","image","description","style","title","imageStyle","children","layout","darkModeImage"]),f=typeof t=="string"?t:"empty",n=this.state.mode==="dark"&&p?p:e;let l=null;typeof n=="string"?l=r.createElement("img",{alt:f,src:n}):n&&"id"in n?l=r.createElement("svg",{"aria-hidden":"true"},r.createElement("use",{xlinkHref:`#${n.id}`})):l=n;const g=O(s,o,{[`${o}-${m}`]:m}),b=l?{heading:4}:{heading:6,style:{fontWeight:400}};return r.createElement("div",Object.assign({className:g,style:u},this.getDataAttr(y)),r.createElement("div",{className:`${o}-image`,style:h,"x-semi-prop":"image,darkModeImage"},l),r.createElement("div",{className:`${o}-content`},c?r.createElement(N.Title,Object.assign({},b,{className:`${o}-title`,"x-semi-prop":"title"}),c):null,t?r.createElement("div",{className:`${o}-description`,"x-semi-prop":"description"},t):null,d?r.createElement("div",{className:`${o}-footer`,"x-semi-prop":"children"},d):null))}}S.defaultProps={layout:"vertical"};export{S as E};
@@ -0,0 +1 @@
1
+ import{c as h,b as N,u as M,r as s,s as v,j as e,d as b,P as L,i as o,m as S,T as z,o as C}from"./index-VvUpVuij.js";import{S as E}from"./SectionHeader-CCjfBKcF.js";import{C as x,T as f}from"./Tag-DON36Of0.js";import{S as I}from"./sparkles-pLu1TMVV.js";const T=[["path",{d:"M12 20v-9",key:"1qisl0"}],["path",{d:"M14 7a4 4 0 0 1 4 4v3a6 6 0 0 1-12 0v-3a4 4 0 0 1 4-4z",key:"uouzyp"}],["path",{d:"M14.12 3.88 16 2",key:"qol33r"}],["path",{d:"M21 21a4 4 0 0 0-3.81-4",key:"1b0z45"}],["path",{d:"M21 5a4 4 0 0 1-3.55 3.97",key:"5cxbf6"}],["path",{d:"M22 13h-4",key:"1jl80f"}],["path",{d:"M3 21a4 4 0 0 1 3.81-4",key:"1fjd4g"}],["path",{d:"M3 5a4 4 0 0 0 3.55 3.97",key:"1d7oge"}],["path",{d:"M6 13H2",key:"82j7cp"}],["path",{d:"m8 2 1.88 1.88",key:"fmnt4t"}],["path",{d:"M9 7.13V6a3 3 0 1 1 6 0v1.13",key:"1vgav8"}]],_=h("bug",T);const q=[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]],w=h("external-link",q);const H=[["path",{d:"M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z",key:"1ngwbx"}]],V=h("wrench",H);function A(){return N({url:"/game/changelog",method:"GET"})}const B=[{key:"feature",icon:I,titleI18n:"changelogFeature",tone:"success"},{key:"optimize",icon:V,titleI18n:"changelogOptimize",tone:"warn"},{key:"bugfix",icon:_,titleI18n:"changelogBugfix",tone:"error"}],$=()=>{const{state:{devToolVersion:r}}=M(),d=C,[p,u]=s.useState([]),[j,l]=s.useState(!0),[c,m]=s.useState("");return s.useEffect(()=>{r&&v(r)},[r]),s.useEffect(()=>{let a=!1;return(async()=>{l(!0),m("");const n=await A();if(!a){if(n?.error||n?.code!==0||!n?.data){u([]),m(typeof n?.error=="object"&&n.error?.errorMsg||"Failed to load changelog."),l(!1);return}u(n.data.items||[]),l(!1)}})(),()=>{a=!0}},[]),j?e.jsx("div",{className:"change-log-container change-log-loading",children:e.jsx(b,{size:"large"})}):e.jsxs("div",{className:"change-log-container",children:[e.jsx(L,{title:o("changelog"),subtitle:o("changelogTip")}),c?e.jsx(S,{fullMode:!1,bordered:!1,closeIcon:null,type:"danger",description:c}):null,!c&&p.length===0?e.jsx(x,{padding:"lg",children:e.jsx(z.Paragraph,{className:"change-log-empty",children:"No changelog entries yet."})}):null,p.map(a=>e.jsxs(x,{padding:"md",className:"change-log-item",children:[e.jsxs("div",{className:"change-log-item-head",children:[e.jsx("h2",{className:"change-log-version",children:a.title}),e.jsxs(f,{tone:"neutral",size:"sm",children:["iOS ",a.target.iOS," · Android ",a.target.Android]})]}),B.map(({key:i,icon:n,titleI18n:k,tone:y})=>{const g=a.changes?.[i]||[];return g.length?e.jsxs("section",{className:"change-log-section",children:[e.jsx(E,{icon:n,title:e.jsxs("span",{className:"change-log-section-title",children:[e.jsx("span",{children:o(k)}),e.jsx(f,{tone:y,size:"sm",children:g.length})]})}),e.jsx("ul",{className:"change-log-changes",children:g.map(t=>e.jsxs("li",{className:"change-log-change",children:[e.jsx("span",{className:"change-log-change-bullet","aria-hidden":!0}),e.jsxs("span",{className:"change-log-change-body",children:[t.desc[d],t?.link?e.jsxs("a",{className:"change-log-change-link",href:t.link,target:"_blank",rel:"noopener noreferrer",children:[o("changelogLink"),e.jsx(w,{size:12,strokeWidth:2})]}):null]})]},t.desc[d]))})]},i):null})]},a.title))]})};export{$ as ChangeLog};
@@ -0,0 +1 @@
1
+ .semi-toast{pointer-events:none}.semi-toast-wrapper{position:fixed;height:0;top:0;width:100%;display:flex;justify-content:center;z-index:1010}.semi-toast-wrapper .semi-toast-innerWrapper{width:fit-content;height:fit-content;text-align:center}.semi-toast-wrapper .semi-toast-innerWrapper-hover .semi-toast-zero-height-wrapper{perspective:unset;perspective-origin:center center}.semi-toast-zero-height-wrapper{transition:all .3s cubic-bezier(.22,.57,.02,1.2);perspective-origin:center 280px;perspective:280px;height:0;overflow:visible}.semi-toast-content{pointer-events:all;box-shadow:var(--semi-shadow-elevated);font-size:14px;line-height:20px;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif;background-color:var(--semi-color-bg-3);border-radius:var(--semi-border-radius-medium);padding:12px 8px;display:inline-flex;align-items:flex-start;justify-content:center;margin:12px;font-weight:600;color:var(--semi-color-text-0)}.semi-toast-content .semi-toast-close-button{margin-top:-2px;height:20px}.semi-toast-content .semi-toast-content-text{margin-left:12px;margin-right:12px;text-align:left;word-wrap:break-word;overflow-wrap:break-word}.semi-toast-light.semi-toast-warning .semi-toast-content{background-color:var(--semi-color-warning-light-default);border:1px solid var(--semi-color-warning)}.semi-toast-light.semi-toast-warning .semi-toast-icon-warning{color:var(--semi-color-warning)}.semi-toast-light.semi-toast-success .semi-toast-content{background-color:var(--semi-color-success-light-default);border:1px solid var(--semi-color-success)}.semi-toast-light.semi-toast-success .semi-toast-icon-success{color:var(--semi-color-success)}.semi-toast-light.semi-toast-info .semi-toast-content{background-color:var(--semi-color-info-light-default);border:1px solid var(--semi-color-info)}.semi-toast-light.semi-toast-info .semi-toast-icon-info{color:var(--semi-color-info)}.semi-toast-light.semi-toast-error .semi-toast-content{background-color:var(--semi-color-danger-light-default);border:1px solid var(--semi-color-danger)}.semi-toast-light.semi-toast-error .semi-toast-icon-error{color:var(--semi-color-danger)}.semi-toast .semi-toast-icon-warning{color:var(--semi-color-warning)}.semi-toast .semi-toast-icon-success{color:var(--semi-color-success)}.semi-toast .semi-toast-icon-info{color:var(--semi-color-info)}.semi-toast .semi-toast-icon-error{color:var(--semi-color-danger)}.semi-toast-animation-show{animation:.3s semi-toast-keyframe-toast-show cubic-bezier(.22,.57,.02,1.2) 0s;animation-fill-mode:forwards}.semi-toast-animation-hide{animation:.3s semi-toast-keyframe-toast-hide cubic-bezier(.22,.57,.02,1.2) 0s;animation-fill-mode:forwards}@keyframes semi-toast-keyframe-toast-show{0%{opacity:0;transform:translateY(-100%)}to{opacity:1}}@keyframes semi-toast-keyframe-toast-hide{0%{opacity:1}to{opacity:0;transform:translateY(-100%)}}.semi-toast-rtl{direction:rtl}.semi-toast-rtl .semi-toast-content .semi-toast-content-text{text-align:right;margin-left:12px;margin-right:12px}.tt-wasm-split-page{padding:0 0 32px}.tt-wasm-split-hero-strip{margin:-24px -32px 8px;padding:12px 28px 0;background:var(--tt-bg-panel)}.tt-wasm-split-hero-strip .tt-page-hero{margin:0;padding:12px 0 8px;background:transparent}.tt-wasm-split-back{display:inline-flex;align-items:center;gap:6px;padding:4px 8px 4px 6px;margin-left:-8px;font-size:var(--tt-text-meta);font-weight:500;color:var(--tt-text-dim);background:transparent;border:1px solid transparent;border-radius:var(--tt-radius-md);cursor:pointer;transition:var(--tt-hover-transition)}.tt-wasm-split-back:hover{color:var(--tt-text);background:var(--tt-overlay-soft);border-color:var(--tt-border)}.tt-wasm-split-back:focus-visible{outline:none;box-shadow:0 0 0 3px var(--tt-accent-soft)}.tt-wasm-split-section{margin-top:24px}.tt-wasm-split-empty{margin:0;font-size:var(--tt-text-body);color:var(--tt-text-dim);line-height:1.6}.tt-wasm-split-empty-card{display:flex;flex-direction:column;align-items:center;text-align:center;gap:12px;padding:40px 32px}.tt-wasm-split-empty-icon{display:inline-flex;align-items:center;justify-content:center;width:48px;height:48px;border-radius:50%;background:var(--tt-warn-bg);color:var(--tt-warn-text);border:1px solid var(--tt-warn-border);margin-bottom:4px}.tt-wasm-split-empty-title{margin:0;font-size:var(--tt-text-h2);font-weight:600;color:var(--tt-text)}.tt-wasm-split-empty-card .tt-wasm-split-empty{max-width:520px}.tt-wasm-split-empty-actions{margin-top:8px}.tt-wasm-split-empty-cta{appearance:none;font:inherit;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;height:36px;padding:0 20px;border-radius:var(--tt-radius-pill);background:var(--tt-accent);border:1px solid var(--tt-accent);color:var(--tt-accent-contrast);font-size:var(--tt-text-body);font-weight:600;letter-spacing:.3px;transition:var(--tt-hover-transition)}.tt-wasm-split-empty-cta:hover{background:var(--tt-accent-hover);border-color:var(--tt-accent-hover)}.tt-wasm-split-empty-cta:focus-visible{outline:2px solid var(--tt-accent);outline-offset:2px}.tt-wasm-requirements-notice{display:flex;align-items:flex-start;gap:12px;margin-top:16px;padding:12px 14px;border:1px solid var(--tt-warn-border);border-radius:var(--tt-radius-md);background:var(--tt-warn-bg);color:var(--tt-warn-text)}.tt-wasm-requirements-notice-icon{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;flex:0 0 auto;border-radius:var(--tt-radius-sm);background:var(--tt-bg-panel)}.tt-wasm-requirements-notice-copy{display:flex;min-width:0;flex-direction:column;gap:6px}.tt-wasm-requirements-notice-title{font-size:var(--tt-text-body);font-weight:600;color:var(--tt-text)}.tt-wasm-requirements-notice-list{display:flex;flex-direction:column;gap:4px;margin:0;padding:0;list-style:none}.tt-wasm-requirements-notice-list li{position:relative;padding-left:14px;font-size:var(--tt-text-meta);line-height:1.55;color:var(--tt-text-dim)}.tt-wasm-requirements-notice-list li:before{content:"";position:absolute;left:0;top:9px;width:5px;height:5px;border-radius:50%;background:var(--tt-warn-text)}.tt-wasm-requirements-notice a{color:var(--tt-warn-text);font-weight:600;text-decoration:none}.tt-wasm-requirements-notice a:hover{text-decoration:underline}.tt-wasm-mode-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin-top:12px}.tt-wasm-split-section .tt-section-header-has-icon+.tt-wasm-mode-grid{padding-left:0}.tt-wasm-mode-card{position:relative;display:flex;align-items:flex-start;gap:14px;padding:16px 18px;background:var(--tt-bg-panel);border:1px solid var(--tt-border);border-radius:var(--tt-radius-lg);text-align:left;cursor:pointer;color:var(--tt-text);transition:var(--tt-hover-transition);font:inherit}.tt-wasm-mode-card:hover{border-color:var(--tt-accent-strong);background:var(--tt-overlay-soft)}.tt-wasm-mode-card:focus-visible{outline:none;box-shadow:0 0 0 3px var(--tt-accent-soft)}.tt-wasm-mode-card-active,.tt-wasm-mode-card-active:hover{border-color:var(--tt-accent);background:var(--tt-accent-soft)}.tt-wasm-mode-card-icon{display:inline-flex;align-items:center;justify-content:center;width:36px;height:36px;flex-shrink:0;border-radius:var(--tt-radius-md);background:var(--tt-overlay-soft);color:var(--tt-accent-text)}.tt-wasm-mode-card-active .tt-wasm-mode-card-icon{background:var(--tt-bg-panel);color:var(--tt-accent-text)}.tt-wasm-mode-card-body{display:flex;flex-direction:column;gap:4px;min-width:0}.tt-wasm-mode-card-title-row{display:inline-flex;align-items:center;flex-wrap:wrap;gap:8px}.tt-wasm-mode-card-title{font-size:var(--tt-text-body);font-weight:600;color:var(--tt-text);letter-spacing:.2px}.tt-wasm-mode-card-badge{display:inline-flex;align-items:center;height:18px;padding:0 8px;font-size:var(--tt-text-caption);font-weight:600;letter-spacing:.4px;color:var(--tt-emphasis-contrast);background:var(--tt-emphasis);border-radius:var(--tt-radius-pill)}.tt-wasm-mode-card-desc{font-size:var(--tt-text-meta);color:var(--tt-text-dim);line-height:1.5}.tt-wasm-mode-card-check{position:absolute;top:14px;right:14px;display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border-radius:50%;background:var(--tt-accent);color:var(--tt-accent-contrast)}.tt-wasm-split-grid{display:flex;flex-direction:column;gap:16px;margin-top:20px}.tt-wasm-split-workflow-card{min-width:0}.tt-wasm-split-steps-wrap{padding:4px 0 0}.tt-wasm-split-stage{margin-top:12px}.tt-wasm-status-row-inset{margin-top:0;margin-bottom:20px;padding:12px 14px;background:var(--tt-bg-sunken);border-radius:var(--tt-radius-md)}.tt-wasm-split-helper-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:20px;align-items:stretch}.tt-wasm-split-helper-main{min-width:0}.tt-wasm-split-helper-main .tt-section-header{align-items:center}.tt-wasm-split-helper-list{list-style:none;margin:12px 0 0;padding:0;display:flex;flex-direction:column;gap:8px}.tt-wasm-split-helper-list li{position:relative;padding-left:14px;font-size:var(--tt-text-meta);line-height:1.6;color:var(--tt-text-dim)}.tt-wasm-split-helper-list li:before{content:"";position:absolute;left:0;top:9px;width:5px;height:5px;border-radius:50%;background:var(--tt-accent);opacity:.7}
@@ -1 +1 @@
1
- import{c as a}from"./index-CCKb9Y-j.js";const e=[["path",{d:"M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z",key:"zw3jo"}],["path",{d:"M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12",key:"1wduqc"}],["path",{d:"M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17",key:"kqbvx6"}]],c=a("layers",e);export{c as L};
1
+ import{c as a}from"./index-VvUpVuij.js";const e=[["path",{d:"M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z",key:"zw3jo"}],["path",{d:"M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12",key:"1wduqc"}],["path",{d:"M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17",key:"kqbvx6"}]],c=a("layers",e);export{c as L};
@@ -0,0 +1 @@
1
+ import{c}from"./index-VvUpVuij.js";const e=[["path",{d:"M13 5h8",key:"a7qcls"}],["path",{d:"M13 12h8",key:"h98zly"}],["path",{d:"M13 19h8",key:"c3s6r1"}],["path",{d:"m3 17 2 2 4-4",key:"1jhpwq"}],["path",{d:"m3 7 2 2 4-4",key:"1obspn"}]],t=c("list-checks",e);export{t as L};
@@ -1 +1 @@
1
- import{c as a}from"./index-CCKb9Y-j.js";const e=[["path",{d:"M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z",key:"1s2grr"}],["path",{d:"M20 2v4",key:"1rf3ol"}],["path",{d:"M22 4h-4",key:"gwowj6"}],["circle",{cx:"4",cy:"20",r:"2",key:"6kqj1y"}]],l=a("sparkles",e);export{l as S};
1
+ import{c as a}from"./index-VvUpVuij.js";const e=[["path",{d:"M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z",key:"1s2grr"}],["path",{d:"M20 2v4",key:"1rf3ol"}],["path",{d:"M22 4h-4",key:"gwowj6"}],["circle",{cx:"4",cy:"20",r:"2",key:"6kqj1y"}]],l=a("sparkles",e);export{l as S};
@@ -0,0 +1 @@
1
+ import{c as e}from"./index-VvUpVuij.js";const a=[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",key:"wmoenq"}],["path",{d:"M12 9v4",key:"juzpu7"}],["path",{d:"M12 17h.01",key:"p32p05"}]],o=e("triangle-alert",a);export{o as T};
@@ -1 +1 @@
1
- import{c as a}from"./index-CCKb9Y-j.js";const o=[["path",{d:"M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z",key:"1xq2db"}]],e=a("zap",o);export{e as Z};
1
+ import{c as a}from"./index-VvUpVuij.js";const o=[["path",{d:"M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z",key:"1xq2db"}]],e=a("zap",o);export{e as Z};
@@ -12,8 +12,8 @@
12
12
  rel="stylesheet"
13
13
  />
14
14
  <title>TikTok Mini Games DevTool</title>
15
- <script type="module" crossorigin src="/assets/index-CCKb9Y-j.js"></script>
16
- <link rel="stylesheet" crossorigin href="/assets/index-BJZ4gOGZ.css">
15
+ <script type="module" crossorigin src="/assets/index-VvUpVuij.js"></script>
16
+ <link rel="stylesheet" crossorigin href="/assets/index-V1Go68Vw.css">
17
17
  </head>
18
18
  <!--
19
19
  Theme attributes (`theme-mode` for Semi UI + `data-theme` for our
@@ -0,0 +1,80 @@
1
+ # DevTool documentation screenshots
2
+
3
+ This directory keeps the local pipeline for DevTool usage-guide screenshots and generated guide drafts.
4
+
5
+ ## Main scenario file
6
+
7
+ `scenarios/devtool-guide.mjs` is the first stable scenario manifest. Each recipe describes:
8
+
9
+ - the current Lark source document for the usage guide
10
+ - the DevTool route to open
11
+ - the page state or fixture expected by the screenshot
12
+ - the capture selector and viewport
13
+ - the usage-guide section that consumes the screenshot
14
+ - bilingual notes used by `render.js`
15
+ - acceptance checks that should stay true when the page changes
16
+ - the documentation contract, screenshot inventory, and release checks used for guide review
17
+
18
+ ## Commands
19
+
20
+ Render the guide draft and indexes without opening the browser:
21
+
22
+ ```bash
23
+ npm run docs:devtool:render
24
+ ```
25
+
26
+ Capture screenshots from a running local DevTool page:
27
+
28
+ ```bash
29
+ npm run docs:devtool:capture
30
+ ```
31
+
32
+ Check that screenshots are fresh enough for publishing:
33
+
34
+ ```bash
35
+ npm run docs:devtool:check
36
+ ```
37
+
38
+ `docs:devtool:capture` assumes the IDE is available at `http://localhost:5173`. Start or change the local DevTool URL before running it if your port is different.
39
+
40
+ ## Doc mode fixture
41
+
42
+ Screenshot routes append `docMode=1&docScenario=<scenario id>`. In this mode the IDE uses stable fixture data for platform and project requests, so usage-guide screenshots do not depend on the current login account, local Node connection, platform availability, or a connected device.
43
+
44
+ Doc mode is only enabled by the URL parameter above or `localStorage.TTMG_DOCS_CAPTURE=1`. Normal DevTool usage still goes through the real local service and platform APIs.
45
+
46
+ Pages that enter the guide should expose stable screenshot anchors such as:
47
+
48
+ ```tsx
49
+ <main data-doc-shot="start-debug-qr-idle">
50
+ ```
51
+
52
+ The scenario manifest should target these anchors through `capture.selector` instead of relying on layout CSS classes.
53
+
54
+ ## Outputs
55
+
56
+ The default output directory is `artifacts/devtool-guide/`.
57
+
58
+ - `manifest.json`: scenario snapshot used for this run
59
+ - `screenshot-index.json`: screenshot-to-route/section mapping for later doc updates
60
+ - `doc-version.json`: version snapshot for the guide source, scenario fingerprint, CLI version, ttmg-pack version, and generated artifacts
61
+ - `quality-gate.json`: review contract, release checks, and screenshot inventory for the current guide refresh
62
+ - `capture-report.json`: screenshot freshness evidence from the latest capture run
63
+ - `index.md`: bilingual usage-guide draft referencing screenshots in `assets/`
64
+
65
+ `manifest.json`, `screenshot-index.json`, `doc-version.json`, and `quality-gate.json` include `guideSource` so each generated draft can be traced back to the current Lark guide instead of relying on a one-off screenshot note.
66
+
67
+ The render step is safe to run without screenshots. The capture step should be run only when the local DevTool page is already available.
68
+
69
+ ## Freshness contract
70
+
71
+ DevTool screenshots are considered publishable only when:
72
+
73
+ - `capture-report.json` exists in the output directory
74
+ - its scenario fingerprint matches the current `scenarios/devtool-guide.mjs`
75
+ - every expected `recipe + locale` screenshot exists under `assets/`
76
+ - screenshot files are newer than the scenario manifest
77
+ - `capture-report.json` and screenshot files are newer than the latest `manifest.json` / `screenshot-index.json` / `doc-version.json` / `quality-gate.json`
78
+ - the capture report is within the freshness window used by `docs:devtool:check`
79
+
80
+ If any of these checks fail, rerun `npm run docs:devtool:capture` from the current DevTool build before updating the usage guide.
@@ -1,5 +1,6 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
+ const crypto = require('crypto');
3
4
  const { pathToFileURL } = require('url');
4
5
  const chromeLauncher = require('chrome-launcher');
5
6
  const CDP = require('chrome-remote-interface');
@@ -42,6 +43,27 @@ function localizeValue(value, lang) {
42
43
  );
43
44
  }
44
45
 
46
+ function stableStringify(value) {
47
+ if (Array.isArray(value)) {
48
+ return `[${value.map(stableStringify).join(',')}]`;
49
+ }
50
+ if (!value || typeof value !== 'object') {
51
+ return JSON.stringify(value);
52
+ }
53
+ return `{${Object.keys(value)
54
+ .sort()
55
+ .map(key => `${JSON.stringify(key)}:${stableStringify(value[key])}`)
56
+ .join(',')}}`;
57
+ }
58
+
59
+ function createScenarioFingerprint({ schemaVersion, locales, guideTitle, guideSource, recipes }) {
60
+ return crypto
61
+ .createHash('sha256')
62
+ .update(stableStringify({ schemaVersion, locales, guideTitle, guideSource, recipes }))
63
+ .digest('hex')
64
+ .slice(0, 16);
65
+ }
66
+
45
67
  function escapeForEval(value) {
46
68
  return JSON.stringify(value).replace(/<\//g, '<\\/');
47
69
  }
@@ -229,17 +251,29 @@ async function captureFullPage(Page, Runtime, Emulation, outputPath, viewport) {
229
251
  async function loadRecipes(recipesPath) {
230
252
  const module = await import(pathToFileURL(recipesPath).href);
231
253
  return {
254
+ schemaVersion: module.schemaVersion || 1,
232
255
  recipes: module.recipes || module.default || [],
233
256
  locales: module.locales || ['zh-CN', 'en-US'],
257
+ guideTitle: module.guideTitle || 'TikTok 小游戏调试工具使用指南',
258
+ guideSource: module.guideSource || null,
234
259
  defaultLocalStorage: module.defaultLocalStorage || {},
235
260
  };
236
261
  }
237
262
 
263
+ function getRecipeViewport(recipe) {
264
+ return recipe.viewport || recipe.capture?.viewport || { width: 1440, height: 1400 };
265
+ }
266
+
267
+ function getRecipeWaitFor(recipe) {
268
+ return recipe.waitFor || {
269
+ selector: recipe.capture?.selector || 'body',
270
+ timeoutMs: 20000,
271
+ };
272
+ }
273
+
238
274
  async function main() {
239
275
  const args = parseArgs(process.argv.slice(2));
240
276
  const baseUrl = args['base-url'];
241
- const recipesPath = path.resolve(args.recipes);
242
- const outputDir = path.resolve(args['output-dir']);
243
277
 
244
278
  if (!baseUrl) {
245
279
  throw new Error('Missing required argument: --base-url');
@@ -251,9 +285,34 @@ async function main() {
251
285
  throw new Error('Missing required argument: --output-dir');
252
286
  }
253
287
 
254
- const { recipes, locales, defaultLocalStorage } = await loadRecipes(recipesPath);
288
+ const recipesPath = path.resolve(args.recipes);
289
+ const outputDir = path.resolve(args['output-dir']);
290
+ const { schemaVersion, recipes, locales, guideTitle, guideSource, defaultLocalStorage } = await loadRecipes(recipesPath);
291
+ const normalizedLocales = locales.map(normalizeLang);
292
+ const scenarioFingerprint = createScenarioFingerprint({
293
+ schemaVersion,
294
+ recipes,
295
+ locales: normalizedLocales,
296
+ guideTitle,
297
+ guideSource,
298
+ });
255
299
  const assetsDir = path.join(outputDir, 'assets');
256
300
  fs.mkdirSync(assetsDir, { recursive: true });
301
+ const reportPath = path.join(outputDir, 'capture-report.json');
302
+ if (fs.existsSync(reportPath)) {
303
+ fs.rmSync(reportPath);
304
+ }
305
+ const captureReport = {
306
+ schemaVersion: 1,
307
+ title: guideTitle,
308
+ guideSource,
309
+ scenarioFingerprint,
310
+ baseUrl,
311
+ recipesPath,
312
+ generatedAt: new Date().toISOString(),
313
+ outputDir,
314
+ screenshots: [],
315
+ };
257
316
 
258
317
  const chrome = await chromeLauncher.launch({
259
318
  chromeFlags: [
@@ -280,7 +339,7 @@ async function main() {
280
339
  }),
281
340
  ]);
282
341
 
283
- for (const locale of locales.map(normalizeLang)) {
342
+ for (const locale of normalizedLocales) {
284
343
  const baseHeaders = {
285
344
  'Accept-Language': locale,
286
345
  'X-TTMG-Docs-Lang': locale,
@@ -296,7 +355,8 @@ async function main() {
296
355
  await seedLocalStorage(Runtime, localizedStorage);
297
356
 
298
357
  for (const recipe of recipes) {
299
- const viewport = recipe.viewport || { width: 1440, height: 1400 };
358
+ const viewport = getRecipeViewport(recipe);
359
+ const waitFor = getRecipeWaitFor(recipe);
300
360
  const requestHeaders = {
301
361
  ...baseHeaders,
302
362
  ...(recipe.requestHeaders || {}),
@@ -306,16 +366,34 @@ async function main() {
306
366
  });
307
367
  await setViewport(Emulation, viewport.width, viewport.height);
308
368
  await navigateAndWait(client, Page, `${baseUrl}${recipe.route}`);
309
- await waitForSelector(Runtime, recipe.waitFor.selector, recipe.waitFor.timeoutMs || 20000);
369
+ await waitForSelector(Runtime, waitFor.selector, waitFor.timeoutMs || 20000);
310
370
  await stabilizePage(Runtime);
311
371
  await wait(500);
312
372
 
313
373
  const fileName = `${recipe.id}.${locale}.png`;
314
374
  const outputPath = path.join(assetsDir, fileName);
315
375
  await captureFullPage(Page, Runtime, Emulation, outputPath, viewport);
376
+ const stats = fs.statSync(outputPath);
377
+ captureReport.screenshots.push({
378
+ id: `${recipe.id}.${locale}`,
379
+ recipeId: recipe.id,
380
+ locale,
381
+ route: recipe.route,
382
+ selector: recipe.capture?.selector || recipe.selector || 'body',
383
+ viewport,
384
+ path: `assets/${fileName}`,
385
+ bytes: stats.size,
386
+ capturedAt: new Date().toISOString(),
387
+ });
316
388
  console.log(`[ttmg-docs] captured ${fileName}`);
317
389
  }
318
390
  }
391
+ fs.writeFileSync(
392
+ reportPath,
393
+ `${JSON.stringify(captureReport, null, 2)}\n`,
394
+ 'utf8',
395
+ );
396
+ console.log(`[ttmg-docs] wrote capture report into ${outputDir}`);
319
397
  } finally {
320
398
  if (client) {
321
399
  await client.close();
@@ -0,0 +1,184 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const crypto = require('crypto');
4
+ const { pathToFileURL } = require('url');
5
+
6
+ function parseArgs(argv) {
7
+ return argv.reduce((acc, arg) => {
8
+ if (!arg.startsWith('--')) {
9
+ return acc;
10
+ }
11
+ const [key, value] = arg.slice(2).split('=');
12
+ acc[key] = value === undefined ? true : value;
13
+ return acc;
14
+ }, {});
15
+ }
16
+
17
+ function normalizeLang(value) {
18
+ return String(value || '').toLowerCase().includes('en') ? 'en-US' : 'zh-CN';
19
+ }
20
+
21
+ function stableStringify(value) {
22
+ if (Array.isArray(value)) {
23
+ return `[${value.map(stableStringify).join(',')}]`;
24
+ }
25
+ if (!value || typeof value !== 'object') {
26
+ return JSON.stringify(value);
27
+ }
28
+ return `{${Object.keys(value)
29
+ .sort()
30
+ .map(key => `${JSON.stringify(key)}:${stableStringify(value[key])}`)
31
+ .join(',')}}`;
32
+ }
33
+
34
+ function createScenarioFingerprint({ schemaVersion, locales, guideTitle, guideSource, recipes }) {
35
+ return crypto
36
+ .createHash('sha256')
37
+ .update(stableStringify({ schemaVersion, locales, guideTitle, guideSource, recipes }))
38
+ .digest('hex')
39
+ .slice(0, 16);
40
+ }
41
+
42
+ async function loadRecipes(recipesPath) {
43
+ const module = await import(pathToFileURL(recipesPath).href);
44
+ return {
45
+ schemaVersion: module.schemaVersion || 1,
46
+ recipes: module.recipes || module.default || [],
47
+ locales: (module.locales || ['zh-CN', 'en-US']).map(normalizeLang),
48
+ guideTitle: module.guideTitle || 'TikTok 小游戏调试工具使用指南',
49
+ guideSource: module.guideSource || null,
50
+ };
51
+ }
52
+
53
+ function readJson(filePath) {
54
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
55
+ }
56
+
57
+ function fail(message) {
58
+ throw new Error(message);
59
+ }
60
+
61
+ function assertFreshEnough(report, maxAgeHours) {
62
+ if (!maxAgeHours) {
63
+ return;
64
+ }
65
+ const maxAgeMs = Number(maxAgeHours) * 60 * 60 * 1000;
66
+ const generatedAt = new Date(report.generatedAt).getTime();
67
+ if (!Number.isFinite(generatedAt)) {
68
+ fail('capture-report.json has invalid generatedAt');
69
+ }
70
+ const ageMs = Date.now() - generatedAt;
71
+ if (ageMs > maxAgeMs) {
72
+ fail(`DevTool screenshots are stale: capture-report is older than ${maxAgeHours} hours`);
73
+ }
74
+ }
75
+
76
+ function assertFileFreshAgainstRecipe(filePath, recipesPath) {
77
+ const screenshotStat = fs.statSync(filePath);
78
+ const recipeStat = fs.statSync(recipesPath);
79
+ if (screenshotStat.mtimeMs + 1000 < recipeStat.mtimeMs) {
80
+ fail(`Screenshot is older than scenario manifest: ${filePath}`);
81
+ }
82
+ }
83
+
84
+ function assertReportFreshAgainstArtifact(report, artifactPath) {
85
+ if (!fs.existsSync(artifactPath)) {
86
+ return;
87
+ }
88
+ const reportGeneratedAt = new Date(report.generatedAt).getTime();
89
+ const artifactStat = fs.statSync(artifactPath);
90
+ if (reportGeneratedAt + 1000 < artifactStat.mtimeMs) {
91
+ fail(`capture-report.json is older than ${path.basename(artifactPath)}. Re-run docs:devtool:capture.`);
92
+ }
93
+ }
94
+
95
+ function assertScreenshotFreshAgainstArtifact(filePath, artifactPath) {
96
+ if (!fs.existsSync(artifactPath)) {
97
+ return;
98
+ }
99
+ const screenshotStat = fs.statSync(filePath);
100
+ const artifactStat = fs.statSync(artifactPath);
101
+ if (screenshotStat.mtimeMs + 1000 < artifactStat.mtimeMs) {
102
+ fail(`Screenshot is older than ${path.basename(artifactPath)}: ${filePath}`);
103
+ }
104
+ }
105
+
106
+ async function main() {
107
+ const args = parseArgs(process.argv.slice(2));
108
+ if (!args.recipes) {
109
+ fail('Missing required argument: --recipes');
110
+ }
111
+ if (!args['output-dir']) {
112
+ fail('Missing required argument: --output-dir');
113
+ }
114
+
115
+ const recipesPath = path.resolve(args.recipes);
116
+ const outputDir = path.resolve(args['output-dir']);
117
+ const reportPath = path.join(outputDir, 'capture-report.json');
118
+ const manifestPath = path.join(outputDir, 'manifest.json');
119
+ const screenshotIndexPath = path.join(outputDir, 'screenshot-index.json');
120
+ const docVersionPath = path.join(outputDir, 'doc-version.json');
121
+ const qualityGatePath = path.join(outputDir, 'quality-gate.json');
122
+
123
+ if (!fs.existsSync(reportPath)) {
124
+ fail('Missing capture-report.json. Run docs:devtool:capture before publishing screenshots.');
125
+ }
126
+
127
+ const { schemaVersion, recipes, locales, guideTitle, guideSource } = await loadRecipes(recipesPath);
128
+ const expectedFingerprint = createScenarioFingerprint({ schemaVersion, recipes, locales, guideTitle, guideSource });
129
+ const report = readJson(reportPath);
130
+
131
+ if (report.scenarioFingerprint !== expectedFingerprint) {
132
+ fail('DevTool screenshots are stale: capture-report does not match the current scenario manifest.');
133
+ }
134
+
135
+ assertFreshEnough(report, args['max-age-hours']);
136
+ assertReportFreshAgainstArtifact(report, manifestPath);
137
+ assertReportFreshAgainstArtifact(report, screenshotIndexPath);
138
+ assertReportFreshAgainstArtifact(report, docVersionPath);
139
+ assertReportFreshAgainstArtifact(report, qualityGatePath);
140
+
141
+ for (const optionalJson of [manifestPath, screenshotIndexPath, docVersionPath, qualityGatePath]) {
142
+ if (!fs.existsSync(optionalJson)) continue;
143
+ const data = readJson(optionalJson);
144
+ if (data.scenarioFingerprint && data.scenarioFingerprint !== expectedFingerprint) {
145
+ fail(`${path.basename(optionalJson)} does not match the current scenario manifest.`);
146
+ }
147
+ }
148
+
149
+ const capturedById = new Map((report.screenshots || []).map(item => [item.id, item]));
150
+ const expectedIds = [];
151
+ for (const recipe of recipes) {
152
+ for (const locale of locales) {
153
+ expectedIds.push(`${recipe.id}.${locale}`);
154
+ }
155
+ }
156
+
157
+ for (const id of expectedIds) {
158
+ const item = capturedById.get(id);
159
+ if (!item) {
160
+ fail(`Missing screenshot capture record: ${id}`);
161
+ }
162
+ const screenshotPath = path.join(outputDir, item.path);
163
+ if (!fs.existsSync(screenshotPath)) {
164
+ fail(`Missing screenshot file: ${item.path}`);
165
+ }
166
+ const stats = fs.statSync(screenshotPath);
167
+ if (stats.size < 1024) {
168
+ fail(`Screenshot file looks invalid or empty: ${item.path}`);
169
+ }
170
+ assertFileFreshAgainstRecipe(screenshotPath, recipesPath);
171
+ assertScreenshotFreshAgainstArtifact(screenshotPath, manifestPath);
172
+ assertScreenshotFreshAgainstArtifact(screenshotPath, screenshotIndexPath);
173
+ assertScreenshotFreshAgainstArtifact(screenshotPath, docVersionPath);
174
+ assertScreenshotFreshAgainstArtifact(screenshotPath, qualityGatePath);
175
+ }
176
+
177
+ console.log(`[ttmg-docs] screenshot freshness check passed: ${expectedIds.length} screenshots`);
178
+ }
179
+
180
+ main().catch(error => {
181
+ console.error('[ttmg-docs] screenshot freshness check failed');
182
+ console.error(error);
183
+ process.exit(1);
184
+ });