From 40b1b00116f0a70f375d8ce90176b8947077ffce Mon Sep 17 00:00:00 2001 From: supervoidcoder <88671013+supervoidcoder@users.noreply.github.com> Date: Thu, 20 Nov 2025 11:55:06 -0500 Subject: [PATCH 1/7] Revert "Revert "Extensions"" --- src/containers/blocks.jsx | 15 ++ src/containers/extension-library.jsx | 183 +++++++++++++----- .../extensions/gallery/obgallery.svg | 7 + src/lib/libraries/extensions/index.jsx | 110 +++++++++-- .../extensions/javascript/javascript.png | Bin 0 -> 45201 bytes 5 files changed, 255 insertions(+), 60 deletions(-) create mode 100644 src/lib/libraries/extensions/gallery/obgallery.svg create mode 100644 src/lib/libraries/extensions/javascript/javascript.png diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx index 1a9370850..36e0c4c71 100644 --- a/src/containers/blocks.jsx +++ b/src/containers/blocks.jsx @@ -144,6 +144,21 @@ class Blocks extends React.Component { this.ScratchBlocks.Procedures.externalProcedureDefCallback = this.props.onActivateCustomProcedures; this.ScratchBlocks.ScratchMsgs.setLocale(this.props.locale); + // Bridge FieldCustom from Closure-compiled Blockly → ScratchBlocks for the JS extension. + // The develop-builds bundle defines window.Blockly.FieldCustom, while extensions call + // ScratchBlocks.FieldCustom.registerInput(...). This assignment wires them together. + if (window.Blockly && window.Blockly.FieldCustom) { + this.ScratchBlocks.FieldCustom = window.Blockly.FieldCustom; + } + // Temporary guard: keep UI stable even if bundles momentarily lack FieldCustom. + if (this.ScratchBlocks && !this.ScratchBlocks.FieldCustom) { + this.ScratchBlocks.FieldCustom = { + registerInput: () => {}, + unregisterInput: () => {}, + getRegisteredInputs: () => { try { return new Map(); } catch (e) { return {}; } } + }; + } + const Msg = this.ScratchBlocks.Msg; Msg.PROCEDURES_RETURN = this.props.intl.formatMessage(messages.PROCEDURES_RETURN, { v: '%1' diff --git a/src/containers/extension-library.jsx b/src/containers/extension-library.jsx index 4f986767b..10a91043f 100644 --- a/src/containers/extension-library.jsx +++ b/src/containers/extension-library.jsx @@ -8,7 +8,10 @@ import log from '../lib/log'; import extensionLibraryContent, { galleryError, galleryLoading, - galleryMore + galleryMore, + galleryLoadingOB, + galleryMoreOB, + galleryErrorOB } from '../lib/libraries/extensions/index.jsx'; import extensionTags from '../lib/libraries/tw-extension-tags'; @@ -39,9 +42,57 @@ const translateGalleryItem = (extension, locale) => ({ description: extension.descriptionTranslations[locale] || extension.description }); -let cachedGallery = null; +// Timeout constant for gallery loading +const GALLERY_TIMEOUT_MS = 750; -const fetchLibrary = async () => { +// Common gallery fetcher function to reduce code duplication +let cachedGalleryTW = null; +let cachedGalleryOB = null; + +const fetchLibraryTW = async () => { + const res = await fetch('https://extensions.turbowarp.org/generated-metadata/extensions-v0.json'); + if (!res.ok) { + throw new Error(`HTTP status ${res.status}`); + } + const data = await res.json(); + return data.extensions.map(extension => ({ + name: extension.name, + nameTranslations: extension.nameTranslations || {}, + description: extension.description, + descriptionTranslations: extension.descriptionTranslations || {}, + extensionId: extension.id, + extensionURL: `https://extensions.turbowarp.org/${extension.slug}.js`, + iconURL: `https://extensions.turbowarp.org/${extension.image || 'images/unknown.svg'}`, + tags: ['tw'], + credits: [ + ...(extension.original || []), + ...(extension.by || []) + ].map(credit => { + if (credit.link) { + return ( + + {credit.name} + + ); + } + return credit.name; + }), + docsURI: extension.docs ? `https://extensions.turbowarp.org/${extension.slug}` : null, + samples: extension.samples ? extension.samples.map(sample => ({ + href: `${process.env.ROOT}editor?project_url=https://extensions.turbowarp.org/samples/${encodeURIComponent(sample)}.sb3`, + text: sample + })) : null, + incompatibleWithScratch: !extension.scratchCompatible, + featured: true + })); +}; + +const fetchLibraryOB = async () => { const res = await fetch('https://omniblocks.github.io/extensions/generated-metadata/extensions-v0.json'); if (!res.ok) { throw new Error(`HTTP status ${res.status}`); @@ -55,7 +106,7 @@ const fetchLibrary = async () => { extensionId: extension.id, extensionURL: `https://omniblocks.github.io/extensions/${extension.slug}.js`, iconURL: `https://omniblocks.github.io/extensions/${extension.image || 'images/unknown.svg'}`, - tags: ['tw'], + tags: ['ob'], credits: [ ...(extension.original || []), ...(extension.by || []) @@ -84,6 +135,24 @@ const fetchLibrary = async () => { })); }; +// Helper function to handle gallery loading with timeout +const loadGalleryWithTimeout = (fetchFunction, timeoutCallback, successCallback, errorCallback) => { + const timeout = setTimeout(() => { + timeoutCallback(); + }, GALLERY_TIMEOUT_MS); + + fetchFunction() + .then(gallery => { + successCallback(gallery); + clearTimeout(timeout); + }) + .catch(error => { + log.error(error); + errorCallback(error); + clearTimeout(timeout); + }); +}; + class ExtensionLibrary extends React.PureComponent { constructor (props) { super(props); @@ -91,34 +160,39 @@ class ExtensionLibrary extends React.PureComponent { 'handleItemSelect' ]); this.state = { - gallery: cachedGallery, - galleryError: null, - galleryTimedOut: false + galleryTW: cachedGalleryTW, + galleryOB: cachedGalleryOB, + galleryTWError: null, + galleryOBError: null, + galleryTWTimedOut: false, + galleryOBTimedOut: false }; } componentDidMount () { - if (!this.state.gallery) { - const timeout = setTimeout(() => { - this.setState({ - galleryTimedOut: true - }); - }, 750); - - fetchLibrary() - .then(gallery => { - cachedGallery = gallery; - this.setState({ - gallery - }); - clearTimeout(timeout); - }) - .catch(error => { - log.error(error); - this.setState({ - galleryError: error - }); - clearTimeout(timeout); - }); + // Fetch TurboWarp gallery if not cached + if (!this.state.galleryTW) { + loadGalleryWithTimeout( + fetchLibraryTW, + () => this.setState({ galleryTWTimedOut: true }), + gallery => { + cachedGalleryTW = gallery; + this.setState({ galleryTW: gallery }); + }, + error => this.setState({ galleryTWError: error }) + ); + } + + // Fetch OmniBlocks gallery if not cached + if (!this.state.galleryOB) { + loadGalleryWithTimeout( + fetchLibraryOB, + () => this.setState({ galleryOBTimedOut: true }), + gallery => { + cachedGalleryOB = gallery; + this.setState({ galleryOB: gallery }); + }, + error => this.setState({ galleryOBError: error }) + ); } } handleItemSelect (item) { @@ -157,23 +231,40 @@ class ExtensionLibrary extends React.PureComponent { } } render () { - let library = null; - if (this.state.gallery || this.state.galleryError || this.state.galleryTimedOut) { - library = extensionLibraryContent.map(toLibraryItem); - library.push('---'); - if (this.state.gallery) { - library.push(toLibraryItem(galleryMore)); - const locale = this.props.intl.locale; - library.push( - ...this.state.gallery - .map(i => translateGalleryItem(i, locale)) - .map(toLibraryItem) - ); - } else if (this.state.galleryError) { - library.push(toLibraryItem(galleryError)); - } else { - library.push(toLibraryItem(galleryLoading)); - } + let library = extensionLibraryContent.map(toLibraryItem); + library.push('---'); + + const locale = this.props.intl.locale; + + // Add TurboWarp gallery items + if (this.state.galleryTW) { + library.push(toLibraryItem(galleryMore)); + library.push( + ...this.state.galleryTW + .map(i => translateGalleryItem(i, locale)) + .map(toLibraryItem) + ); + } else if (this.state.galleryTWError) { + library.push(toLibraryItem(galleryError)); + } else if (this.state.galleryTWTimedOut) { + library.push(toLibraryItem(galleryLoading)); + } + + // Add separator between galleries + library.push('---'); + + // Add OmniBlocks gallery items + if (this.state.galleryOB) { + library.push(toLibraryItem(galleryMoreOB)); + library.push( + ...this.state.galleryOB + .map(i => translateGalleryItem(i, locale)) + .map(toLibraryItem) + ); + } else if (this.state.galleryOBError) { + library.push(toLibraryItem(galleryErrorOB)); + } else if (this.state.galleryOBTimedOut) { + library.push(toLibraryItem(galleryLoadingOB)); } return ( diff --git a/src/lib/libraries/extensions/gallery/obgallery.svg b/src/lib/libraries/extensions/gallery/obgallery.svg new file mode 100644 index 000000000..d0cb26a5d --- /dev/null +++ b/src/lib/libraries/extensions/gallery/obgallery.svg @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/src/lib/libraries/extensions/index.jsx b/src/lib/libraries/extensions/index.jsx index bbf273884..bc7cf9bdf 100644 --- a/src/lib/libraries/extensions/index.jsx +++ b/src/lib/libraries/extensions/index.jsx @@ -46,10 +46,13 @@ import gdxforInsetIconURL from './gdxfor/gdxfor-small.svg'; import gdxforConnectionIconURL from './gdxfor/gdxfor-illustration.svg'; import gdxforConnectionSmallIconURL from './gdxfor/gdxfor-small.svg'; +import jgJavascriptExtensionIcon from './javascript/javascript.png'; + import twIcon from './tw/tw.svg'; import customExtensionIcon from './custom/custom.svg'; import returnIcon from './custom/return.svg'; import galleryIcon from './gallery/gallery.svg'; +import obgalleryIcon from './gallery/obgallery.svg'; import {APP_NAME} from '../../brand'; export default [ @@ -360,11 +363,11 @@ export default [ { name: ( ), @@ -381,6 +384,27 @@ export default [ tags: ['tw'], featured: true }, + { + name: ( + + ), + extensionId: 'SPjavascriptV2', + iconURL: jgJavascriptExtensionIcon, + description: ( + + ), + incompatibleWithScratch: true, + tags: ['ob'], + featured: true + }, { name: ( ), href: 'https://extensions.turbowarp.org/', @@ -433,12 +454,9 @@ export const galleryLoading = { export const galleryMore = { name: ( ), href: 'https://extensions.turbowarp.org/', @@ -459,12 +477,9 @@ export const galleryMore = { export const galleryError = { name: ( ), href: 'https://extensions.turbowarp.org/', @@ -481,3 +496,70 @@ export const galleryError = { tags: ['tw'], featured: true }; + +// OmniBlocks Gallery +export const galleryLoadingOB = { + name: ( + + ), + href: 'https://omniblocks.github.io/extensions', + extensionId: 'galleryOB', + iconURL: obgalleryIcon, + description: ( + + ), + tags: ['ob'], + featured: true +}; + +export const galleryMoreOB = { + name: ( + + ), + href: 'https://omniblocks.github.io/extensions', + extensionId: 'galleryOB', + iconURL: obgalleryIcon, + description: ( + + ), + tags: ['ob'], + featured: true +}; + +export const galleryErrorOB = { + name: ( + + ), + href: 'https://omniblocks.github.io/extensions', + extensionId: 'galleryOB', + iconURL: obgalleryIcon, + description: ( + + ), + tags: ['ob'], + featured: true +}; diff --git a/src/lib/libraries/extensions/javascript/javascript.png b/src/lib/libraries/extensions/javascript/javascript.png new file mode 100644 index 0000000000000000000000000000000000000000..0983957df73fd7b10b0c66f6328f4c9a3346c265 GIT binary patch literal 45201 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%0D5>ya{{7svd00+Bg| z#ZxfvBFCcCQbSAe>@*++k_T#sWj^!kWpD){QIZ?aCZ_MbBx+Lq&sK|l#N9N=_HS7uJ|Z*k2!3PlJ(UPE zK^}HZqx$46PWaz{0S(3ZRe5_EFJ;pX5tM|Y*MY8yMkZ{FIAEFWW@F*9#P@0Dmn*0u zrwNb-D=P;OFk|7w=Py^DQT3Wh+AZEwLR>!ck*B-Ty_7Me$|lI)o-`S_Vu%Y%ixma) z6f6oWs)17FJ~v+Q)7$XcZF^ys@e~D|@FMCHatVPfa5(gig}mR+!kGAUcC| zTNT;H}2>BDzd`-_W9N3!QA69uU{bNI^D{FDnXKibj|yi#Gk=QdQ;0 z`+KT~)wNH&Ze1xDhS!cG#J-?)Q|t=twGaL1FGNh|G&n^Em%oxJTFbffd^KBk$Ug~{ zjrAGlNnE%du11>D8XIA^MVe-@2q&rjlV3nGt%<}>N60u$51dh(j}H!LPV_l)r}X4v zS>du%vhbz|n>912wE^Q64-9P22%TSl+$Gq@*Y#6`A>`$GF3dMU)3p2;7wv~2oEr*nx1s7E{;J*2jJxbbJ zsgQ(ee^jp?hCfW3?T5P7ySAL2))`#Ywnm80mdvo5HaNqgO1vl|5db*no@}Q_I@(RS zb5nT3ckaquQ6is>^iW3#oa$*kX9QC>iw0|5QcICP4Cb+NzrmT;(EO3I-@>7mZedunzzLKVxO8F*gO3LV)kypHv;Q|zD;my!a$PZ_EdRG_mG%e zF_VtC9GXT06C>cdw=?{Mxhpm19eSpv?(sy1>Nk9|b|z3X^Jdm_;};1#*JwoDPUuE;UA=!$i(3y$ z%@B&d?l9E7)qZeoA=w;dH9Y7A=qPnF%Hn+UP5JAM&IKl_3mI z6F%3N$X%2AKQK|yJ09&8?h*%^O`Axt$R9Nijp+1V!`DJws>0W-?GCz4NGrc^YXr~u;H@3ZG&867R*N3 z^E$|+4Ry@kr|c6a4mp55K7lj(Rrl1A^uri(4+@pLHS11f=WJag-5~D)8v5pImv!C+ zy7qkrX7PRA4&wJjZX*Z}OE+%ERCP1d8d$}aEA4vPpfv=KhR{wOPY-^l4);2vciOeG zGu`3~`(Ww1`yAwf?#6zR%WK%7x25fo$8`H){h4x;;xx<`%fLpu(}jCv8FZbozob+8 z3Tl9j?9i@_8nn2vlNx(!rB||zSHj@ZnkL0(98?JAck> z4&SE)_3y^II^*}54ujD8pN1{e!d->!+zAJ`!bh-ijeOc~u^153HaoZBw+FwL1 zt&s+T0!~LWBcY9U%R;vGUxOm;1d{<0FW<U34A$ZC&hDQlNnoK1oF%706RdYv;Q-`BbYeGN)E|_AfGD+mG%`Je74zc9&Cipjm;VmPHo*2K!UwlM~S%2jf&~mLV+nF9X0NhUWpE0 zK|zg4yis{j|LkO&znd!V8IT~~^O-llTf8(^eJ%247q`q^f=JOHx!a2Y^a8{C9%o3+ z_-Z$P!S0A@-_G68=1Ro1Md%d9#mz7@W7G~eSAx5V&#{0Yb@jzMmGh%0>4&v9n&X8h zUab>N@Vh>?eyXCPni+*(tH0@IV&))UD-?!K_ZQ^HprWc`%E^0JM~A_B4feTz55?|H zf=dk33w_q=k`~Ub;a|ybz=PqPL`lo%&KG7q(c|qWZB)2H(3_6@D_Yc=6y4{+ z%Z4~@R~>u%=pqHSsNIo%c}fZbs&j-#Z1bAQ(XR5!$DTX<0O);>@e(ZQm>U*UC>Ad$ z-fk)qZ#@;V9moj4C^ybYWpyu73*M4$iQjQQuvrLmG zJDN(UM5iOfYPBm@FyOXd@|aomdNrbU-4|csme764(o*U@2MGeqg3INQcWK4lk}t$y zM!yL4!f^GzL<;mo2ifO9IeOByXZ_&8e z5;9m%kkZe6^X;%mD=Z&>hGs*|{TxcxI!@Mj6@5?`)mj|xn>d`)n|!ITKmTnYe>ci4P|(o*RCeP%heTQTD0T$NI0$dt^Qt)(W52oQ4U z*d%maDpCzy^vz3$(m(}XeDPZy;E5%I#4pdkR?EfD=?rxB5HQ(&QGdp8Xk}pJ4B(o? z>Qw&~M}X)>=Sq5F2b%k4mix|MioT2tG(ty)_$jk6sR zX8;Tx`B5jDUc%RjT5d}y#$*sjP&PhR6oeK-R!oKUW_pGL44}FcGyQ*VpUAzp8s~YP zKdy*-cC)!%rwa+yPCwR^)kIetA9;0O9ttO!LdO@9f81tG#p9siP{F0gN))`~bRbq) zJz!;s=_y1|zQ7m#Nd1~&#tsyuC%2u%n zQ7Rc`n5>71x8_m`AK5cpmJwdC)!)f%?J#@s5uU$Q?(xhzTU=RCeDE$j%4j!*hN(Cg|M9 z5#m>KkDzdU>z5ID@oG(_`$xXY2Svu0`901NRcG)9kG#LI99r9Vpk!uidnxAj}4u2!iJ37v?~hlv)NJ7k8`_&l+~L6Liu)ycod zEpfM+qI$)pbxj~Bg{gzf?U-S96;<|?eWIg7g>l1Zh;mbY_>CbG7nSCD6w zz04;zdrSAU$0;Hz`8CWB?lRIshuL;dLD75boU&T`9}%!%h-YZ~|j(Gnfb2~1zaPF&S&*9>D>uRLwz#YXAXYrl{f&?22 zx~oHoYg#w2F!Raj`ZEHaHZg;H9LW=oK5PeyCQt2CJhM5*xHWi`mgoq*QzuC3{#+el zm~2Xj zRK_)gyolbbcqR=lNVFVG;e-6)gInYlC6+Rw&m^XNLq?BZdDRszo3_7yIkrH>UzLY} znSYxc{r))${-KvyGu8T@uoN5|){v~kyAO}2^M=E`IDaA|Fq5 zpTZ)C@_K46U(8`HHb3z-&Xg&;`GsXuK4_0dT_(F%SN7}>TJ(fW_t-DF*hbTru^LfT z5?dsC4Tmkv$Dg^)fISR{TvE^m_xSn}dpYBW52aT(K>W}bte(YX z4$E}S0aMBqi$b56R%De=SL5zsxPB!rGWpH(JA&q&v@Ca*fW-@v9u~sU#B-UJ8LVDu z>H0(8AmbB^j5BI}=?e%#(@jAiPWC+u+1zF6Hle$ci4ggG22;0yyYTL8j3uXswbb2~ z{->*HW=5No@&=q_&&(|tQxqOFf3Hh`Ul7(}?#CcPr+^|@;y7#^{IDm2XvlF(#msHe zB9j*y>-zcX7gC!4XHWqbkhP9^l+@@D?D%wMt6@sd8<+>j`%HH!fKHjdFy@NrH*e>{ zB%|x_Q~E708KTCm`EUDASt-1$iMc8ZC6D=&$WTj|0QBIu$Y{$o84+Yj#Vx3k)cIa& z8T}4*`(DEr?V1lspw!OoI{)xUFy(h_SECGjuRrf(QRc6xn%>oy2W3wP&DTGM)JkF! z2S=ASMAc~4n>qcrs56fIw}e*H$RlIrwrLnZRkWAxX(c`iK?zoeCU3{rrQWH!rWRH% zuVonsJ~lmEW4R8uLCddVqTxKp&684#-j`o~?+O<9jjQa_?<_A}`*m?J<@(*g+}S6S z5dqV#Fn31bV0BQ`R4~b%YX~jMHlD8J*Urj4-3_&pNhxq}RE&hSmHzF>J}`RMMSTaM zZo5t0Mlp}}GFt^d^0$J}Y!iXj%OivvOsiiC%g(?@SDTw+((o)~f$Z0A<|^L^&eSOj3GEGFSB2Mx{A~0>zqjzto%X)9{ zB>lcB=6zW-9Oa{x89V!E*f%$=UkTu~N_o{}-3SNsBdF=Xm1%kE)UnTH6VkK)YLBDQe`=tg4Gv9!8X=x+dc(b^O76VLVWjB>+wgvZvA@K}~C-~s8vXH%{vU&Yh z3AYPM$j}vCuybm+@&vuTtqn0MnKQx-+!cX-6f*X}I1{=QxFJh9rY6LVS8+*^G=JO0 zH4fuK{nw=VBZ~xN;^S8|CJG~IYivlmPVUT)TK!z@ya$XH4!gq;w8k54#53LxU-I%N zEZZ&Bukh*!L`DkufAb!loGgEfhL_GC(|>+B8F*ly_(&6w%so2 z^pUq=XAD=KohLL%Gy?*k+Od@0uwXx}zq%i&Hl=0Ap<}L6gM3iprl!!Gpg+iBhVImT zg77MijV4SC=T7lcljhy((@JJ!wF2F>hIEbJv?c`W-W*Q!l;oJZ8HiwFNwge9)2aB@ zyFtYoi^!3rbsLs$|HGL=Dv*T#71Sr^0-cE5?!`r5hR=lh$O&vPR2?5PbGEE?$G2i} z(&oI3(6iAO^`WgZT;#dKHW^+fVTLC$ef_u3(V)+YC zA9=o9#QY~y-VAZNJV!@qD7_>E*4P`;Aa#3eUhEJEyWe8OHF}O2T&{p#_%X3maI3u1c(mBJdsT#}$j4B#qL{ug|ox zVwvTX&f7%kO) z-X~b+J6bQiE$Ft(w}q%QmLDh=`06`rXbuw;ReJN)*me~j8>8&D^?jpNy19?pa>&#) z=w_6r6dv9u+lBejG?nNp+6R1|A+8@z_XHtWZn-Ds;U#0xBt08MR)eX1O-fSZ;^;q%-hu#$Ar-GS+8gfF?7$}i0L2r=8x z!Sqx1KIz6braIfa3^`8+m?Sz3RWG_oZ)tb!LAJx!=IPCVyxDNcrziIe`q4D=Ih{Rh zut^ua7573fl6;1w=n`rPQ%dxkm$8a0yaf-O4Jj5J>GbB~Upf6yTwCFaD~vHoe-6D| zJ9#0M?ee_Jo$x%0ZMzN1d&I88wX9{mj-8H~pw1OcHXV6p^U>Y)*R4bS0_s$osLCR| zw5f+)#flSzR3u9x@%lHZRTTB~7L0f!UoBy{N?TC2;Iu2;*G3)cWBqW~rjVsL#;@VS zW+iF&US|(ymVT8=VI0uV2n}c@IXI!}`py?o^)I#1G}j)`OXP&BZZ`05reIu{AgRP{ z?973(Z&yl$#$G}1uDDM30~FPzX!n>=c;3b=fhdn!g0d9P3t__y2=Feq{mxxSgbhA& ze8&`1Fq!&r$!UQxX?*HlKNBII-JG>tH%`}Z-WgAv*I&^DBR;ockktm+yrs%WDtJVl zW@ubZW4b8*G_CFUno;qhUsWXH#y@)BU=-RY)f*Z&g#1c;F}B*Q4(c1f=L(dSmAMHkj`6T zr%I+pMRAAm{Izm1G^PeVaA;c1rAowxdCK!%`y@KT8qfTfa0L}A_HQ-#iu~TshW;qM zpyFKfE%;amZW1KTYf~c459OCcW~RG%Ou32TkLf3)akpyvyBv6M(gp?0RnP5t)roP) z+F&BABnI;B|GH>bQy+}TH_}P}34U;q5TRBtWR|SjomB3%w*dk}t_@%J<~TE9N5Y9% zwN0$>vaQB(%0cAYk|Ev9dA$sLZ|%VdoQ7-q&3Wz}f!I7;XlC6AbFylfP-HsRAz z=iJDuXFI>9qV<7Ww-@F#$OH2UTby&7N zY65q}ewcw^hM@}1+z6$~74tKBO#;Kvf~1a_QT4U~1)|JcAh~4fEU1{Odnwn@89(q(2R0So|amgMouhM3V)WO*T;l$M|OBzm=1z;`&6x+199%N{zcbr z5F0#hB1QSw#bDUy)J3z^{Bb-T5}I;cB+?kKU}Ni6j8Vb)wkK30HpFzqCNL&vnQ_T* zQ>(h$Tl&?BvA4CQLmKRl_cN|9D!)Q-%c(V^n#cTC z3_=Mgv=uz?Vi5_ReEg3raG3+oa((TiJoQ^ZWinrOY2W%hJNHT{xfPK zYQ%*@;jb_tkC_Z?tpmyY+!z18z=#>CIW~|Qn0iJbiy)r=CmEz|ErMvG6zs7EWY~uU&ZxlkM$1fC@=JKd`bT; zZ2420qu>b;(7&wy3NMe@6qZ@hU@`myPVQy@syihLv?=jxFJRY%h9I1amiQShv3(N| zo4qHjNB+0z1>OT4Y#}OA$00$>4 MsVGq@W)$$h01t&2LjV8( literal 0 HcmV?d00001 From 7a60c4e5158746d64821687406e10c770969afb4 Mon Sep 17 00:00:00 2001 From: ampelectrecuted <197376797+ampelectrecuted@users.noreply.github.com> Date: Sun, 23 Nov 2025 17:06:01 +0000 Subject: [PATCH 2/7] Apply suggestion from @amazon-q-developer[bot] Co-authored-by: amazon-q-developer[bot] <208079219+amazon-q-developer[bot]@users.noreply.github.com> --- src/containers/blocks.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx index 36e0c4c71..a7f37cf4d 100644 --- a/src/containers/blocks.jsx +++ b/src/containers/blocks.jsx @@ -155,7 +155,7 @@ class Blocks extends React.Component { this.ScratchBlocks.FieldCustom = { registerInput: () => {}, unregisterInput: () => {}, - getRegisteredInputs: () => { try { return new Map(); } catch (e) { return {}; } } + getRegisteredInputs: () => new Map() }; } From 4959f155bff62d428db6b246d683351ce4a4915b Mon Sep 17 00:00:00 2001 From: ampelectrecuted <197376797+ampelectrecuted@users.noreply.github.com> Date: Sun, 23 Nov 2025 17:06:19 +0000 Subject: [PATCH 3/7] Apply suggestion from @amazon-q-developer[bot] Co-authored-by: amazon-q-developer[bot] <208079219+amazon-q-developer[bot]@users.noreply.github.com> --- src/containers/extension-library.jsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/containers/extension-library.jsx b/src/containers/extension-library.jsx index 10a91043f..94528af85 100644 --- a/src/containers/extension-library.jsx +++ b/src/containers/extension-library.jsx @@ -137,18 +137,24 @@ const fetchLibraryOB = async () => { // Helper function to handle gallery loading with timeout const loadGalleryWithTimeout = (fetchFunction, timeoutCallback, successCallback, errorCallback) => { + let timeoutFired = false; const timeout = setTimeout(() => { + timeoutFired = true; timeoutCallback(); }, GALLERY_TIMEOUT_MS); fetchFunction() .then(gallery => { - successCallback(gallery); + if (!timeoutFired) { + successCallback(gallery); + } clearTimeout(timeout); }) .catch(error => { - log.error(error); - errorCallback(error); + if (!timeoutFired) { + log.error(error); + errorCallback(error); + } clearTimeout(timeout); }); }; From 48819edc27504ac0a65b75700ae58284bb577781 Mon Sep 17 00:00:00 2001 From: supervoidcoder <88671013+supervoidcoder@users.noreply.github.com> Date: Mon, 1 Dec 2025 22:57:46 +0000 Subject: [PATCH 4/7] idk what this does but it was causing issues so reverting --- .github/workflows/changelog.yml | 478 ++++++++++----------- .github/workflows/initialize-changelog.yml | 378 ++++++++-------- 2 files changed, 428 insertions(+), 428 deletions(-) diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 67c24172f..b7cb74fc1 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -1,240 +1,240 @@ -name: Changelog Updater - -on: - push: - branches: - - master - - main - release: - types: [published] - -permissions: - contents: write - -jobs: - update-changelog: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Configure Git - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - - name: Update Changelog for Commit - if: github.event_name == 'push' - run: | - # Get the latest commit info - COMMIT_SHA=$(git rev-parse HEAD) - COMMIT_SHORT=$(git rev-parse --short HEAD) - COMMIT_MSG=$(git log -1 --pretty=%s) - COMMIT_BODY=$(git log -1 --pretty=%b) - COMMIT_AUTHOR=$(git log -1 --pretty="%an") - COMMIT_DATE=$(git log -1 --pretty=%ci) - - # Skip if this is the changelog update commit itself - if [[ "$COMMIT_MSG" == *"update CHANGELOG.md"* ]] || [[ "$COMMIT_MSG" == *"[skip ci]"* ]]; then - echo "Skipping changelog update for changelog commit" - exit 0 - fi - - # Ensure CHANGELOG.md exists with markers - if [ ! -f CHANGELOG.md ]; then - cat > CHANGELOG.md << 'HEADER' - # Changelog - - This file is automatically updated by the Changelog Updater bot. - - - - - HEADER - fi - - # Check if markers exist, add them if they don't - if ! grep -q "UNRELEASED_COMMITS_START" CHANGELOG.md; then - # Insert markers after the header - sed -i '3 i \n\n' CHANGELOG.md - fi - - # Create the new commit entry - NEW_ENTRY="### Commit [\`${COMMIT_SHORT}\`](https://github.com/${{ github.repository }}/commit/${COMMIT_SHA}) - ${COMMIT_DATE} - - **${COMMIT_MSG}** - " - - # Add body if it exists and is not empty - if [ -n "$COMMIT_BODY" ] && [ "$COMMIT_BODY" != "" ]; then - NEW_ENTRY="${NEW_ENTRY} - ${COMMIT_BODY} - " - fi - - NEW_ENTRY="${NEW_ENTRY} - *Author: ${COMMIT_AUTHOR}* - - --- - " - - # Insert the new entry after the START marker - awk -v entry="$NEW_ENTRY" ' - /UNRELEASED_COMMITS_START/ { print; print ""; print entry; next } - { print } - ' CHANGELOG.md > CHANGELOG.md.tmp - - mv CHANGELOG.md.tmp CHANGELOG.md - - - name: Update Changelog for Release - if: github.event_name == 'release' - run: | - # Get release info - RELEASE_TAG="${{ github.event.release.tag_name }}" - RELEASE_NAME="${{ github.event.release.name }}" - RELEASE_DATE=$(date -u +"%Y-%m-%d %H:%M:%S") - - # Get previous release tag - PREV_TAG=$(git tag --sort=-creatordate | grep -v "^${RELEASE_TAG}$" | head -n 1) - - # Determine if this is the initial release - if [ -z "$PREV_TAG" ]; then - RELEASE_SUBTITLE="### Initial release - All commits:" - else - RELEASE_SUBTITLE="### Changes since ${PREV_TAG}:" - fi - - # Extract unreleased commits and convert to bullet list with descriptions - UNRELEASED_COMMITS="" - if [ -f CHANGELOG.md ] && grep -q "UNRELEASED_COMMITS_START" CHANGELOG.md; then - # Extract content between markers, parse detailed entries - UNRELEASED_SECTION=$(sed -n '/UNRELEASED_COMMITS_START/,/UNRELEASED_COMMITS_END/p' CHANGELOG.md | sed '1d;$d') - - # Process the unreleased section to extract commits with bodies - echo "$UNRELEASED_SECTION" | awk ' - /^### Commit/ { - if (commit_line != "") { - print commit_line - if (body != "") print body - } - commit_line = $0 - gsub(/^### Commit /, "- ", commit_line) - gsub(/ - [0-9]{4}-[0-9]{2}-[0-9]{2}.*$/, "", commit_line) - subject = "" - body = "" - author = "" - next - } - /^\*\*.*\*\*$/ { - subject = $0 - gsub(/\*\*/, "**", subject) - commit_line = commit_line " " subject - next - } - /^\*Author:/ { - author = $0 - commit_line = commit_line " " author - next - } - /^---$/ { next } - /^[[:space:]]*$/ { next } - { - if (body == "") { - body = " > " $0 - } else { - body = body "\n > " $0 - } - } - END { - if (commit_line != "") { - print commit_line - if (body != "") print body - } - } - ' > /tmp/unreleased_commits.txt - - UNRELEASED_COMMITS=$(cat /tmp/unreleased_commits.txt) - fi - - # If no unreleased commits in markers, get from git with bodies - if [ -z "$UNRELEASED_COMMITS" ]; then - if [ -z "$PREV_TAG" ]; then - git log ${RELEASE_TAG} --pretty=format:"%H|||%h|||%s|||%an|||%b" | while IFS='|||' read -r hash short_hash subject author body; do - echo "- [\`${short_hash}\`](https://github.com/${{ github.repository }}/commit/${hash}) **${subject}** - *${author}*" - if [ -n "$body" ] && [ "$body" != "" ]; then - echo "$body" | sed 's/^/ > /' - fi - done > /tmp/unreleased_commits.txt - else - git log ${PREV_TAG}..${RELEASE_TAG} --pretty=format:"%H|||%h|||%s|||%an|||%b" | while IFS='|||' read -r hash short_hash subject author body; do - echo "- [\`${short_hash}\`](https://github.com/${{ github.repository }}/commit/${hash}) **${subject}** - *${author}*" - if [ -n "$body" ] && [ "$body" != "" ]; then - echo "$body" | sed 's/^/ > /' - fi - done > /tmp/unreleased_commits.txt - fi - UNRELEASED_COMMITS=$(cat /tmp/unreleased_commits.txt) - fi - - # Create new release section - RELEASE_SECTION="## Release [${RELEASE_TAG}](https://github.com/${{ github.repository }}/releases/tag/${RELEASE_TAG}) - ${RELEASE_DATE} - " - - if [ -n "${RELEASE_NAME}" ]; then - RELEASE_SECTION="${RELEASE_SECTION} - **${RELEASE_NAME}** - " - fi - - RELEASE_SECTION="${RELEASE_SECTION} - ${RELEASE_SUBTITLE} - - ${UNRELEASED_COMMITS} - - --- - " - - # Create new changelog - if [ -f CHANGELOG.md ]; then - # Extract everything after the UNRELEASED_COMMITS_END marker - EXISTING_RELEASES=$(sed -n '/UNRELEASED_COMMITS_END/,$p' CHANGELOG.md | tail -n +2) - - cat > CHANGELOG.md << 'HEADER' - # Changelog - - This file is automatically updated by the Changelog Updater bot. - - - - - HEADER - - echo "$RELEASE_SECTION" >> CHANGELOG.md - echo "$EXISTING_RELEASES" >> CHANGELOG.md - else - cat > CHANGELOG.md << 'HEADER' - # Changelog - - This file is automatically updated by the Changelog Updater bot. - - - - - HEADER - - echo "$RELEASE_SECTION" >> CHANGELOG.md - fi - - - name: Commit and Push Changes - run: | - git add CHANGELOG.md - if git diff --staged --quiet; then - echo "No changes to commit" - else - git commit -m "chore: update CHANGELOG.md [skip ci]" - git push +name: Changelog Updater + +on: + push: + branches: + - master + - main + release: + types: [published] + +permissions: + contents: write + +jobs: + update-changelog: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Update Changelog for Commit + if: github.event_name == 'push' + run: | + # Get the latest commit info + COMMIT_SHA=$(git rev-parse HEAD) + COMMIT_SHORT=$(git rev-parse --short HEAD) + COMMIT_MSG=$(git log -1 --pretty=%s) + COMMIT_BODY=$(git log -1 --pretty=%b) + COMMIT_AUTHOR=$(git log -1 --pretty="%an") + COMMIT_DATE=$(git log -1 --pretty=%ci) + + # Skip if this is the changelog update commit itself + if [[ "$COMMIT_MSG" == *"update CHANGELOG.md"* ]] || [[ "$COMMIT_MSG" == *"[skip ci]"* ]]; then + echo "Skipping changelog update for changelog commit" + exit 0 + fi + + # Ensure CHANGELOG.md exists with markers + if [ ! -f CHANGELOG.md ]; then + cat > CHANGELOG.md << 'HEADER' + # Changelog + + This file is automatically updated by the Changelog Updater bot. + + + + + HEADER + fi + + # Check if markers exist, add them if they don't + if ! grep -q "UNRELEASED_COMMITS_START" CHANGELOG.md; then + # Insert markers after the header + sed -i '3 i \n\n' CHANGELOG.md + fi + + # Create the new commit entry + NEW_ENTRY="### Commit [\`${COMMIT_SHORT}\`](https://github.com/${{ github.repository }}/commit/${COMMIT_SHA}) - ${COMMIT_DATE} + + **${COMMIT_MSG}** + " + + # Add body if it exists and is not empty + if [ -n "$COMMIT_BODY" ] && [ "$COMMIT_BODY" != "" ]; then + NEW_ENTRY="${NEW_ENTRY} + ${COMMIT_BODY} + " + fi + + NEW_ENTRY="${NEW_ENTRY} + *Author: ${COMMIT_AUTHOR}* + + --- + " + + # Insert the new entry after the START marker + awk -v entry="$NEW_ENTRY" ' + /UNRELEASED_COMMITS_START/ { print; print ""; print entry; next } + { print } + ' CHANGELOG.md > CHANGELOG.md.tmp + + mv CHANGELOG.md.tmp CHANGELOG.md + + - name: Update Changelog for Release + if: github.event_name == 'release' + run: | + # Get release info + RELEASE_TAG="${{ github.event.release.tag_name }}" + RELEASE_NAME="${{ github.event.release.name }}" + RELEASE_DATE=$(date -u +"%Y-%m-%d %H:%M:%S") + + # Get previous release tag + PREV_TAG=$(git tag --sort=-creatordate | grep -v "^${RELEASE_TAG}$" | head -n 1) + + # Determine if this is the initial release + if [ -z "$PREV_TAG" ]; then + RELEASE_SUBTITLE="### Initial release - All commits:" + else + RELEASE_SUBTITLE="### Changes since ${PREV_TAG}:" + fi + + # Extract unreleased commits and convert to bullet list with descriptions + UNRELEASED_COMMITS="" + if [ -f CHANGELOG.md ] && grep -q "UNRELEASED_COMMITS_START" CHANGELOG.md; then + # Extract content between markers, parse detailed entries + UNRELEASED_SECTION=$(sed -n '/UNRELEASED_COMMITS_START/,/UNRELEASED_COMMITS_END/p' CHANGELOG.md | sed '1d;$d') + + # Process the unreleased section to extract commits with bodies + echo "$UNRELEASED_SECTION" | awk ' + /^### Commit/ { + if (commit_line != "") { + print commit_line + if (body != "") print body + } + commit_line = $0 + gsub(/^### Commit /, "- ", commit_line) + gsub(/ - [0-9]{4}-[0-9]{2}-[0-9]{2}.*$/, "", commit_line) + subject = "" + body = "" + author = "" + next + } + /^\*\*.*\*\*$/ { + subject = $0 + gsub(/\*\*/, "**", subject) + commit_line = commit_line " " subject + next + } + /^\*Author:/ { + author = $0 + commit_line = commit_line " " author + next + } + /^---$/ { next } + /^[[:space:]]*$/ { next } + { + if (body == "") { + body = " > " $0 + } else { + body = body "\n > " $0 + } + } + END { + if (commit_line != "") { + print commit_line + if (body != "") print body + } + } + ' > /tmp/unreleased_commits.txt + + UNRELEASED_COMMITS=$(cat /tmp/unreleased_commits.txt) + fi + + # If no unreleased commits in markers, get from git with bodies + if [ -z "$UNRELEASED_COMMITS" ]; then + if [ -z "$PREV_TAG" ]; then + git log ${RELEASE_TAG} --pretty=format:"%H|||%h|||%s|||%an|||%b" | while IFS='|||' read -r hash short_hash subject author body; do + echo "- [\`${short_hash}\`](https://github.com/${{ github.repository }}/commit/${hash}) **${subject}** - *${author}*" + if [ -n "$body" ] && [ "$body" != "" ]; then + echo "$body" | sed 's/^/ > /' + fi + done > /tmp/unreleased_commits.txt + else + git log ${PREV_TAG}..${RELEASE_TAG} --pretty=format:"%H|||%h|||%s|||%an|||%b" | while IFS='|||' read -r hash short_hash subject author body; do + echo "- [\`${short_hash}\`](https://github.com/${{ github.repository }}/commit/${hash}) **${subject}** - *${author}*" + if [ -n "$body" ] && [ "$body" != "" ]; then + echo "$body" | sed 's/^/ > /' + fi + done > /tmp/unreleased_commits.txt + fi + UNRELEASED_COMMITS=$(cat /tmp/unreleased_commits.txt) + fi + + # Create new release section + RELEASE_SECTION="## Release [${RELEASE_TAG}](https://github.com/${{ github.repository }}/releases/tag/${RELEASE_TAG}) - ${RELEASE_DATE} + " + + if [ -n "${RELEASE_NAME}" ]; then + RELEASE_SECTION="${RELEASE_SECTION} + **${RELEASE_NAME}** + " + fi + + RELEASE_SECTION="${RELEASE_SECTION} + ${RELEASE_SUBTITLE} + + ${UNRELEASED_COMMITS} + + --- + " + + # Create new changelog + if [ -f CHANGELOG.md ]; then + # Extract everything after the UNRELEASED_COMMITS_END marker + EXISTING_RELEASES=$(sed -n '/UNRELEASED_COMMITS_END/,$p' CHANGELOG.md | tail -n +2) + + cat > CHANGELOG.md << 'HEADER' + # Changelog + + This file is automatically updated by the Changelog Updater bot. + + + + + HEADER + + echo "$RELEASE_SECTION" >> CHANGELOG.md + echo "$EXISTING_RELEASES" >> CHANGELOG.md + else + cat > CHANGELOG.md << 'HEADER' + # Changelog + + This file is automatically updated by the Changelog Updater bot. + + + + + HEADER + + echo "$RELEASE_SECTION" >> CHANGELOG.md + fi + + - name: Commit and Push Changes + run: | + git add CHANGELOG.md + if git diff --staged --quiet; then + echo "No changes to commit" + else + git commit -m "chore: update CHANGELOG.md [skip ci]" + git push fi \ No newline at end of file diff --git a/.github/workflows/initialize-changelog.yml b/.github/workflows/initialize-changelog.yml index db8a9d4d2..4601619db 100644 --- a/.github/workflows/initialize-changelog.yml +++ b/.github/workflows/initialize-changelog.yml @@ -1,190 +1,190 @@ -name: Initialize Changelog (One-time Setup) - -on: - workflow_dispatch: - -permissions: - contents: write - -jobs: - initialize-changelog: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.GITHUB_TOKEN }} - - - name: Configure Git - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - - name: Generate Complete Changelog - run: | - REPO_URL="https://github.com/${{ github.repository }}" - - echo "🚀 Initializing changelog..." - echo "📦 Repository: ${REPO_URL}" - - # Create changelog header with markers - cat > CHANGELOG.md << 'HEADER' - # Changelog - - This file is automatically updated by the Changelog Updater bot. - - - HEADER - - # Get all tags sorted by date (newest first) - mapfile -t TAGS < <(git tag --sort=-creatordate) - TAG_COUNT=${#TAGS[@]} - - echo "🏷️ Found ${TAG_COUNT} releases" - - # If we have releases, add unreleased commits since the latest release - if [ "$TAG_COUNT" -gt "0" ]; then - LATEST_TAG="${TAGS[0]}" - echo "📝 Adding unreleased commits since ${LATEST_TAG}..." - - # Get all commit hashes since the last release - mapfile -t COMMIT_HASHES < <(git log ${LATEST_TAG}..HEAD --pretty=format:"%H") - - for hash in "${COMMIT_HASHES[@]}"; do - # Get individual commit details - short_hash=$(git log -1 --format=%h "$hash") - subject=$(git log -1 --format=%s "$hash") - author=$(git log -1 --format=%an "$hash") - date=$(git log -1 --format=%ci "$hash") - body=$(git log -1 --format=%b "$hash") - - # Skip changelog bot commits and [skip ci] commits - if [[ "$subject" == *"update CHANGELOG.md"* ]] || [[ "$subject" == *"[skip ci]"* ]]; then - continue - fi - - echo "### Commit [\`${short_hash}\`](${REPO_URL}/commit/${hash}) - ${date}" >> CHANGELOG.md - echo "" >> CHANGELOG.md - echo "**${subject}**" >> CHANGELOG.md - echo "" >> CHANGELOG.md - - # Add body if it exists - if [ -n "$body" ]; then - echo "$body" >> CHANGELOG.md - echo "" >> CHANGELOG.md - fi - - echo "*Author: ${author}*" >> CHANGELOG.md - echo "" >> CHANGELOG.md - echo "---" >> CHANGELOG.md - echo "" >> CHANGELOG.md - done - fi - - # Close the unreleased section - echo "" >> CHANGELOG.md - echo "" >> CHANGELOG.md - - if [ "$TAG_COUNT" -eq "0" ]; then - echo "📝 No releases found, adding all commits..." - - echo "## All Commits" >> CHANGELOG.md - echo "" >> CHANGELOG.md - - mapfile -t ALL_HASHES < <(git log --pretty=format:"%H") - - for hash in "${ALL_HASHES[@]}"; do - short_hash=$(git log -1 --format=%h "$hash") - subject=$(git log -1 --format=%s "$hash") - author=$(git log -1 --format=%an "$hash") - date=$(git log -1 --format=%ci "$hash") - body=$(git log -1 --format=%b "$hash") - - echo "### Commit [\`${short_hash}\`](${REPO_URL}/commit/${hash}) - ${date}" >> CHANGELOG.md - echo "" >> CHANGELOG.md - echo "**${subject}**" >> CHANGELOG.md - echo "" >> CHANGELOG.md - - if [ -n "$body" ]; then - echo "$body" >> CHANGELOG.md - echo "" >> CHANGELOG.md - fi - - echo "*Author: ${author}*" >> CHANGELOG.md - echo "" >> CHANGELOG.md - echo "---" >> CHANGELOG.md - echo "" >> CHANGELOG.md - done - else - # Process each release - for i in "${!TAGS[@]}"; do - CURRENT_TAG="${TAGS[$i]}" - PREV_TAG="" - - # Get previous tag - if [ $((i + 1)) -lt ${TAG_COUNT} ]; then - PREV_TAG="${TAGS[$((i + 1))]}" - fi - - TAG_DATE=$(git log -1 --format=%ci "${CURRENT_TAG}") - - echo "📋 Processing release ${CURRENT_TAG}..." - - # Determine if this is the initial release - if [ -z "$PREV_TAG" ]; then - RELEASE_SUBTITLE="### Initial release - All commits:" - mapfile -t RELEASE_HASHES < <(git log "${CURRENT_TAG}" --pretty=format:"%H") - else - RELEASE_SUBTITLE="### Changes since ${PREV_TAG}:" - mapfile -t RELEASE_HASHES < <(git log "${PREV_TAG}..${CURRENT_TAG}" --pretty=format:"%H") - fi - - { - echo "## Release [${CURRENT_TAG}](${REPO_URL}/releases/tag/${CURRENT_TAG}) - ${TAG_DATE}" - echo "" - echo "${RELEASE_SUBTITLE}" - echo "" - } >> CHANGELOG.md - - # Process commits for this release - for hash in "${RELEASE_HASHES[@]}"; do - short_hash=$(git log -1 --format=%h "$hash") - subject=$(git log -1 --format=%s "$hash") - author=$(git log -1 --format=%an "$hash") - body=$(git log -1 --format=%b "$hash") - - echo "- [\`${short_hash}\`](${REPO_URL}/commit/${hash}) **${subject}** - *${author}*" >> CHANGELOG.md - - if [ -n "$body" ]; then - echo "$body" | sed 's/^/ > /' >> CHANGELOG.md - fi - done - - echo "" >> CHANGELOG.md - echo "---" >> CHANGELOG.md - echo "" >> CHANGELOG.md - done - fi - - echo "✅ Changelog initialized successfully!" - - - name: Commit and Push Changelog - run: | - git add CHANGELOG.md - if git diff --staged --quiet; then - echo "No changes to commit" - else - git commit -m "chore: fix and reinitialize CHANGELOG.md [skip ci]" - git push - echo "📄 CHANGELOG.md has been fixed and pushed!" - fi - - - name: Summary - run: | - if [ -f CHANGELOG.md ]; then - LINE_COUNT=$(wc -l < CHANGELOG.md) - echo "✅ Changelog fixed with ${LINE_COUNT} lines" - echo "🎉 The changelog now works correctly!" +name: Initialize Changelog (One-time Setup) + +on: + workflow_dispatch: + +permissions: + contents: write + +jobs: + initialize-changelog: + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Configure Git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Generate Complete Changelog + run: | + REPO_URL="https://github.com/${{ github.repository }}" + + echo "🚀 Initializing changelog..." + echo "📦 Repository: ${REPO_URL}" + + # Create changelog header with markers + cat > CHANGELOG.md << 'HEADER' + # Changelog + + This file is automatically updated by the Changelog Updater bot. + + + HEADER + + # Get all tags sorted by date (newest first) + mapfile -t TAGS < <(git tag --sort=-creatordate) + TAG_COUNT=${#TAGS[@]} + + echo "🏷️ Found ${TAG_COUNT} releases" + + # If we have releases, add unreleased commits since the latest release + if [ "$TAG_COUNT" -gt "0" ]; then + LATEST_TAG="${TAGS[0]}" + echo "📝 Adding unreleased commits since ${LATEST_TAG}..." + + # Get all commit hashes since the last release + mapfile -t COMMIT_HASHES < <(git log ${LATEST_TAG}..HEAD --pretty=format:"%H") + + for hash in "${COMMIT_HASHES[@]}"; do + # Get individual commit details + short_hash=$(git log -1 --format=%h "$hash") + subject=$(git log -1 --format=%s "$hash") + author=$(git log -1 --format=%an "$hash") + date=$(git log -1 --format=%ci "$hash") + body=$(git log -1 --format=%b "$hash") + + # Skip changelog bot commits and [skip ci] commits + if [[ "$subject" == *"update CHANGELOG.md"* ]] || [[ "$subject" == *"[skip ci]"* ]]; then + continue + fi + + echo "### Commit [\`${short_hash}\`](${REPO_URL}/commit/${hash}) - ${date}" >> CHANGELOG.md + echo "" >> CHANGELOG.md + echo "**${subject}**" >> CHANGELOG.md + echo "" >> CHANGELOG.md + + # Add body if it exists + if [ -n "$body" ]; then + echo "$body" >> CHANGELOG.md + echo "" >> CHANGELOG.md + fi + + echo "*Author: ${author}*" >> CHANGELOG.md + echo "" >> CHANGELOG.md + echo "---" >> CHANGELOG.md + echo "" >> CHANGELOG.md + done + fi + + # Close the unreleased section + echo "" >> CHANGELOG.md + echo "" >> CHANGELOG.md + + if [ "$TAG_COUNT" -eq "0" ]; then + echo "📝 No releases found, adding all commits..." + + echo "## All Commits" >> CHANGELOG.md + echo "" >> CHANGELOG.md + + mapfile -t ALL_HASHES < <(git log --pretty=format:"%H") + + for hash in "${ALL_HASHES[@]}"; do + short_hash=$(git log -1 --format=%h "$hash") + subject=$(git log -1 --format=%s "$hash") + author=$(git log -1 --format=%an "$hash") + date=$(git log -1 --format=%ci "$hash") + body=$(git log -1 --format=%b "$hash") + + echo "### Commit [\`${short_hash}\`](${REPO_URL}/commit/${hash}) - ${date}" >> CHANGELOG.md + echo "" >> CHANGELOG.md + echo "**${subject}**" >> CHANGELOG.md + echo "" >> CHANGELOG.md + + if [ -n "$body" ]; then + echo "$body" >> CHANGELOG.md + echo "" >> CHANGELOG.md + fi + + echo "*Author: ${author}*" >> CHANGELOG.md + echo "" >> CHANGELOG.md + echo "---" >> CHANGELOG.md + echo "" >> CHANGELOG.md + done + else + # Process each release + for i in "${!TAGS[@]}"; do + CURRENT_TAG="${TAGS[$i]}" + PREV_TAG="" + + # Get previous tag + if [ $((i + 1)) -lt ${TAG_COUNT} ]; then + PREV_TAG="${TAGS[$((i + 1))]}" + fi + + TAG_DATE=$(git log -1 --format=%ci "${CURRENT_TAG}") + + echo "📋 Processing release ${CURRENT_TAG}..." + + # Determine if this is the initial release + if [ -z "$PREV_TAG" ]; then + RELEASE_SUBTITLE="### Initial release - All commits:" + mapfile -t RELEASE_HASHES < <(git log "${CURRENT_TAG}" --pretty=format:"%H") + else + RELEASE_SUBTITLE="### Changes since ${PREV_TAG}:" + mapfile -t RELEASE_HASHES < <(git log "${PREV_TAG}..${CURRENT_TAG}" --pretty=format:"%H") + fi + + { + echo "## Release [${CURRENT_TAG}](${REPO_URL}/releases/tag/${CURRENT_TAG}) - ${TAG_DATE}" + echo "" + echo "${RELEASE_SUBTITLE}" + echo "" + } >> CHANGELOG.md + + # Process commits for this release + for hash in "${RELEASE_HASHES[@]}"; do + short_hash=$(git log -1 --format=%h "$hash") + subject=$(git log -1 --format=%s "$hash") + author=$(git log -1 --format=%an "$hash") + body=$(git log -1 --format=%b "$hash") + + echo "- [\`${short_hash}\`](${REPO_URL}/commit/${hash}) **${subject}** - *${author}*" >> CHANGELOG.md + + if [ -n "$body" ]; then + echo "$body" | sed 's/^/ > /' >> CHANGELOG.md + fi + done + + echo "" >> CHANGELOG.md + echo "---" >> CHANGELOG.md + echo "" >> CHANGELOG.md + done + fi + + echo "✅ Changelog initialized successfully!" + + - name: Commit and Push Changelog + run: | + git add CHANGELOG.md + if git diff --staged --quiet; then + echo "No changes to commit" + else + git commit -m "chore: fix and reinitialize CHANGELOG.md [skip ci]" + git push + echo "📄 CHANGELOG.md has been fixed and pushed!" + fi + + - name: Summary + run: | + if [ -f CHANGELOG.md ]; then + LINE_COUNT=$(wc -l < CHANGELOG.md) + echo "✅ Changelog fixed with ${LINE_COUNT} lines" + echo "🎉 The changelog now works correctly!" fi \ No newline at end of file From 70854c1e963f5c92bbf608f762df75c121096810 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 18 Dec 2025 19:47:47 +0000 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=8E=A8=20Auto-fix=20ESLint=20issues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Triggered by @supervoidcoder Co-authored-by: supervoidcoder <88671013+supervoidcoder@users.noreply.github.com> --- src/containers/extension-library.jsx | 14 +++++++------- src/lib/libraries/extensions/index.jsx | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/containers/extension-library.jsx b/src/containers/extension-library.jsx index 94528af85..6cc7cd641 100644 --- a/src/containers/extension-library.jsx +++ b/src/containers/extension-library.jsx @@ -179,12 +179,12 @@ class ExtensionLibrary extends React.PureComponent { if (!this.state.galleryTW) { loadGalleryWithTimeout( fetchLibraryTW, - () => this.setState({ galleryTWTimedOut: true }), + () => this.setState({galleryTWTimedOut: true}), gallery => { cachedGalleryTW = gallery; - this.setState({ galleryTW: gallery }); + this.setState({galleryTW: gallery}); }, - error => this.setState({ galleryTWError: error }) + error => this.setState({galleryTWError: error}) ); } @@ -192,12 +192,12 @@ class ExtensionLibrary extends React.PureComponent { if (!this.state.galleryOB) { loadGalleryWithTimeout( fetchLibraryOB, - () => this.setState({ galleryOBTimedOut: true }), + () => this.setState({galleryOBTimedOut: true}), gallery => { cachedGalleryOB = gallery; - this.setState({ galleryOB: gallery }); + this.setState({galleryOB: gallery}); }, - error => this.setState({ galleryOBError: error }) + error => this.setState({galleryOBError: error}) ); } } @@ -237,7 +237,7 @@ class ExtensionLibrary extends React.PureComponent { } } render () { - let library = extensionLibraryContent.map(toLibraryItem); + const library = extensionLibraryContent.map(toLibraryItem); library.push('---'); const locale = this.props.intl.locale; diff --git a/src/lib/libraries/extensions/index.jsx b/src/lib/libraries/extensions/index.jsx index bc7cf9bdf..04cb0ee20 100644 --- a/src/lib/libraries/extensions/index.jsx +++ b/src/lib/libraries/extensions/index.jsx @@ -367,7 +367,7 @@ export default [ description="Name of the strange 'TurboWarp Blocks' extension" id="tw.twExtension.name" values={{ - APP_NAME: "TurboWarp", + APP_NAME: 'TurboWarp' }} /> ), From a2ab8fe2b6356f991e169ce1c50dddeab758e03a Mon Sep 17 00:00:00 2001 From: 8to16 <197376797+8to16@users.noreply.github.com> Date: Fri, 16 Jan 2026 20:27:43 +0000 Subject: [PATCH 6/7] Update src/containers/blocks.jsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/containers/blocks.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/containers/blocks.jsx b/src/containers/blocks.jsx index a7f37cf4d..0302f8c17 100644 --- a/src/containers/blocks.jsx +++ b/src/containers/blocks.jsx @@ -157,6 +157,8 @@ class Blocks extends React.Component { unregisterInput: () => {}, getRegisteredInputs: () => new Map() }; + log.warn('ScratchBlocks.FieldCustom is not available; using stub implementation. ' + + 'This usually means the Blockly → ScratchBlocks FieldCustom bridge is not configured correctly.'); } const Msg = this.ScratchBlocks.Msg; From 15814872a9f2594db71940695e89b124e4984e8b Mon Sep 17 00:00:00 2001 From: supervoidcoder <88671013+supervoidcoder@users.noreply.github.com> Date: Fri, 16 Jan 2026 22:17:12 +0000 Subject: [PATCH 7/7] fix: ADD can unsandboex (stolen from trash flightles bird mod --- .../security-manager-modal.jsx | 3 ++ .../tw-security-manager-modal/unsandbox.jsx | 38 +++++++++++++++++++ src/containers/tw-security-manager.jsx | 12 ++++++ src/lib/tw-security-manager-constants.js | 1 + 4 files changed, 54 insertions(+) create mode 100644 src/components/tw-security-manager-modal/unsandbox.jsx diff --git a/src/components/tw-security-manager-modal/security-manager-modal.jsx b/src/components/tw-security-manager-modal/security-manager-modal.jsx index 761298b54..71a610dbb 100644 --- a/src/components/tw-security-manager-modal/security-manager-modal.jsx +++ b/src/components/tw-security-manager-modal/security-manager-modal.jsx @@ -5,6 +5,7 @@ import Box from '../box/box.jsx'; import Modal from '../../containers/modal.jsx'; import SecurityModals from '../../lib/tw-security-manager-constants'; import LoadExtensionModal from './load-extension.jsx'; +import UnsandboxModal from './unsandbox.jsx'; import FetchModal from './fetch.jsx'; import OpenWindowModal from './open-window.jsx'; import RedirectModal from './redirect.jsx'; @@ -39,6 +40,8 @@ const SecurityManagerModalComponent = props => ( {props.type === SecurityModals.LoadExtension ? ( + ) : props.type === SecurityModals.Unsandbox ? ( + ) : props.type === SecurityModals.Fetch ? ( ) : props.type === SecurityModals.OpenWindow ? ( diff --git a/src/components/tw-security-manager-modal/unsandbox.jsx b/src/components/tw-security-manager-modal/unsandbox.jsx new file mode 100644 index 000000000..aff227b63 --- /dev/null +++ b/src/components/tw-security-manager-modal/unsandbox.jsx @@ -0,0 +1,38 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {FormattedMessage} from 'react-intl'; +import {APP_NAME} from '../../lib/brand'; + +const UnsandboxModal = props => ( +
+

+ +

+

+ +

+
+); + +UnsandboxModal.propTypes = { + extensionName: PropTypes.string.isRequired +}; + +export default UnsandboxModal; diff --git a/src/containers/tw-security-manager.jsx b/src/containers/tw-security-manager.jsx index 19a2fe340..c0d21ed5e 100644 --- a/src/containers/tw-security-manager.jsx +++ b/src/containers/tw-security-manager.jsx @@ -134,6 +134,7 @@ let allowedGeolocation = false; const SECURITY_MANAGER_METHODS = [ 'getSandboxMode', 'canLoadExtensionFromProject', + 'canUnsandbox', 'canFetch', 'canOpenWindow', 'canRedirect', @@ -283,6 +284,17 @@ class TWSecurityManagerComponent extends React.Component { }); } + /** + * @param {string} extensionName The extension's display name + * @returns {Promise} True if the extension can run without sandbox + */ + async canUnsandbox (extensionName) { + const {showModal} = await this.acquireModalLock(); + return showModal(SecurityModals.Unsandbox, { + extensionName + }); + } + /** * @param {string} url The resource to fetch * @returns {Promise} True if the resource is allowed to be fetched diff --git a/src/lib/tw-security-manager-constants.js b/src/lib/tw-security-manager-constants.js index d4b349b4c..ff614c6d7 100644 --- a/src/lib/tw-security-manager-constants.js +++ b/src/lib/tw-security-manager-constants.js @@ -1,5 +1,6 @@ const SecurityModals = { LoadExtension: 'LoadExtension', + Unsandbox: 'Unsandbox', Fetch: 'Fetch', OpenWindow: 'OpenWindow', Redirect: 'Redirect',