1"use strict";(self.webpackChunkcodeandweb=self.webpackChunkcodeandweb||[]).push([[21911],{1469:function(e,t,a){var n=a(87433);e.exports=function(e,t){return new(n(e))(0===t?0:t)}},2360:function(e,t,a){var n,l=a(28551),r=a(96801),o=a(88727),s=a(30421),c=a(20397),i=a(4055),d=a(66119),h="prototype",m="script",u=d("IE_PROTO"),p=function(){},f=function(e){return"<"+m+">"+e+"</"+m+">"},w=function(e){e.write(f("")),e.close();var t=e.parentWindow.Object;return e=null,t},v=function(){try{n=new ActiveXObject("htmlfile")}catch(r){}var e,t,a;v="undefined"!=typeof document?document.domain&&n?w(n):(t=i("iframe"),a="java"+m+":",t.style.display="none",c.appendChild(t),t.src=String(a),(e=t.contentWindow.document).open(),e.write(f("document.F=Object")),e.close(),e.F):w(n);for(var l=o.length;l--;)delete v[h][o[l]];return v()};s[u]=!0,e.exports=Object.create||function(e,t){var a;return null!==e?(p[h]=l(e),a=new p,p[h]=null,a[u]=e):a=v(),void 0===t?a:r.f(a,t)}},6469:function(e,t,a){var n=a(78227),l=a(2360),r=a(24913).f,o=n("unscopables"),s=Array.prototype;void 0===s[o]&&r(s,o,{configurable:!0,value:l(null)}),e.exports=function(e){s[o][e]=!0}},15142:function(e,t,a){a.d(t,{h:function(){return n}});const n={title:"CodeAndWeb",siteUrl:"https://www.codeandweb.com",image:"/img/logo.png",author:{name:"Andreas Löw"},organization:{name:"CodeAndWeb GmbH",url:"https://www.codeandweb.com",logo:"https://www.codeandweb.com/img/logo.png"},social:{twitter:"@codeandweb"}}},20397:function(e,t,a){var n=a(97751);e.exports=n("document","documentElement")},27476:function(e,t,a){var n=a(22195),l=a(79504);e.exports=function(e){if("Function"===n(e))return l(e)}},30237:function(e,t,a){a(6469)("flatMap")},33517:function(e,t,a){var n=a(79504),l=a(79039),r=a(94901),o=a(36955),s=a(97751),c=a(33706),i=function(){},d=s("Reflect","construct"),h=/^\s*(?:class|function)\b/,m=n(h.exec),u=!h.test(i),p=function(e){if(!r(e))return!1;try{return d(i,[],e),!0}catch(t){return!1}},f=function(e){if(!r(e))return!1;switch(o(e)){case"AsyncFunction":case"GeneratorFunction":case"AsyncGeneratorFunction":return!1}try{return u||!!m(h,c(e))}catch(t){return!0}};f.sham=!0,e.exports=!d||l((function(){var e;return p(p.call)||!p(Object)||!p((function(){e=!0}))||e}))?f:p},34376:function(e,t,a){var n=a(22195);e.exports=Array.isArray||function(e){return"Array"===n(e)}},45071:function(e,t,a){a.r(t),a.d(t,{Head:function(){return K},default:function(){return Z}});var n=a(28453),l=a(96540),r=a(24794);a(78350),a(30237);var o=a(22734),s=a(15142);const c=[{platform:"mac",title:"macOS"},{platform:"windows",title:"Windows"},{platform:"linux-deb",title:"Linux (.deb)"},{platform:"linux-flatpak",title:"Linux (Flatpak)"}],i=[{platform:"mac",aliases:["macos","osx","darwin"],fileExtension:".dmg"},{platform:"windows",aliases:["win"],fileExtension:".msi"},{platform:"linux-deb",aliases:["deb","ubuntu","linux"],fileExtension:".deb"},{platform:"linux-flatpak",aliases:["flatpak"],fileExtension:".flatpak"}],d=new Map(c.map((e=>[e.platform,e.title]))),h={deb:"linux-deb",flatpak:"linux-flatpak"},m=e=>{var t;return"mac"===e.osName?"mac":"win"===e.osName?"windows":null!==(t=h[e.variant])&&void 0!==t?t:null},u=(e,t)=>s.h.siteUrl+"/download/"+e+"/latest/"+t,p=e=>s.h.siteUrl+"/download/"+e+"/latest.json",f=["texturepacker","texturepacker3d","physicseditor","spriteilluminator","babeledit"],w=(e,t)=>{const a={};for(const n of t)for(const t of n.downloads){const n=m(t);null!==n&&void 0===a[n]&&(a[n]=u(e,n))}return a},v=e=>"texturepacker"===e?"/texturepacker/documentation/installation-and-licensing":o.R.baseUrlForProduct(e)+"/documentation",E=e=>o.R.baseUrlForProduct(e)+"/download",g=e=>c.flatMap((t=>{const a=e.latestUrlByPlatform[t.platform];return a?[{platform:t.platform,title:t.title,url:a}]:[]})),b=e=>f.map((t=>{var a;return((e,t)=>{var a,n;return{product:e,productName:o.R.displayNameForProduct(e),version:null!==(a=null===(n=t[0])||void 0===n?void 0:n.version)&&void 0!==a?a:"",docsUrl:v(e),releaseNotesUrl:E(e),manifestUrl:p(e),latestUrlByPlatform:w(e,t)}})(t,(e=>e.filter((e=>!e.isBeta)))(null!==(a=e[t])&&void 0!==a?a:[]))})),y="inline-flex min-h-11 items-center text-primary-700 hover:text-primary-900 dark:text-primary-300 dark:hover:text-primary-100 underline",k=e=>{let{productName:t,platformTitle:a,url:n}=e;return l.createElement("a",{href:n,"aria-label":"Download "+t+" for "+a+": "+n,className:y+" font-mono text-xs break-all"},new URL(n).pathname)},x=()=>l.createElement("span",{className:"mx-2 text-gray-400 sm:hidden","aria-hidden":"true"},"·"),S=e=>{let{row:t}=e;return l.createElement("p",{className:"flex flex-wrap items-center text-xs sm:flex-col sm:items-start"},l.createElement("a",{href:t.docsUrl,className:y,"aria-label":t.productName+" install help"},"Install help"),l.createElement(x,null),l.createElement("a",{href:t.releaseNotesUrl,className:y,"aria-label":t.productName+" release notes"},"Release notes"),l.createElement(x,null),l.createElement("a",{href:t.manifestUrl,target:"_blank",rel:"noopener noreferrer",className:y+" font-mono","aria-label":t.productName+" latest.json manifest (opens in a new tab)"},"latest.json"))},N=e=>{let{rows:t}=e;return l.createElement("div",{className:"space-y-6 sm:hidden"},t.map((e=>l.createElement("section",{key:e.product,"aria-label":e.productName+" downloads",className:"rounded-lg border border-gray-200 p-4 dark:border-slate-700"},l.createElement("h3",{className:"text-base font-semibold text-gray-900 dark:text-slate-100"},e.productName),l.createElement("p",{className:"text-sm text-gray-600 dark:text-slate-400"},"Version ",l.createElement("span",{className:"font-mono"},e.version)),l.createElement(S,{row:e}),l.createElement("dl",{className:"mt-3 space-y-3"},g(e).map((t=>l.createElement("div",{key:t.platform},l.createElement("dt",{className:"text-xs font-semibold text-gray-700 dark:text-slate-300"},t.title),l.createElement("dd",{className:"mt-1"},l.createElement(k,{productName:e.productName,platformTitle:t.title,url:t.url}))))))))))},T="border-b border-gray-300 px-3 py-2 text-left text-sm font-semibold text-gray-900 dark:border-slate-600 dar
1k:text-slate-100",H="border-b border-gray-200 px-3 align-top text-left dark:border-slate-700",L=e=>{let{row:t}=e;return l.createElement("dl",{className:"grid grid-cols-[7rem_minmax(0,1fr)] items-center gap-x-4"},g(t).map((e=>l.createElement(l.Fragment,{key:e.platform},l.createElement("dt",{className:"text-sm text-gray-700 dark:text-slate-300"},e.title),l.createElement("dd",null,l.createElement(k,{productName:t.productName,platformTitle:e.title,url:e.url}))))))},C=e=>{let{rows:t}=e;return l.createElement("table",{className:"hidden w-full border-collapse sm:table"},l.createElement("caption",{className:"mb-3 text-left text-sm text-gray-600 dark:text-slate-400"},"Current stable release of the five apps served by the /latest/ URLs, with the version-less download URL for each platform. The paths below all hang off"," ",l.createElement("span",{className:"font-mono"},"https://www.codeandweb.com"),"."),l.createElement("thead",null,l.createElement("tr",null,l.createElement("th",{scope:"col",className:T},"Product"),l.createElement("th",{scope:"col",className:T},"Platform"),l.createElement("th",{scope:"col",className:T},"Links"))),l.createElement("tbody",null,t.map((e=>l.createElement("tr",{key:e.product},l.createElement("th",{scope:"row",className:"border-b border-gray-200 px-3 py-3 align-top text-left text-sm font-semibold text-gray-900 dark:border-slate-700 dark:text-slate-100"},e.productName,l.createElement("span",{className:"mt-1 block font-mono text-xs font-normal text-gray-600 dark:text-slate-400"},e.version)),l.createElement("td",{className:H},l.createElement(L,{row:e})),l.createElement("td",{className:H},l.createElement(S,{row:e})))))))},U=()=>{const e=b((()=>{const e=(0,r.useStaticQuery)("3744102756");return{babeledit:e.babeledit.nodes,physicseditor:e.physicseditor.nodes,spriteilluminator:e.spriteilluminator.nodes,texturepacker:e.texturepacker.nodes,texturepacker3d:e.texturepacker3d.nodes}})()),t=(()=>{const{siteBuildMetadata:e}=(0,r.useStaticQuery)("503377001");return e.buildTime})();return l.createElement("div",{className:"not-prose my-8 overflow-x-auto"},l.createElement(N,{rows:e}),l.createElement(C,{rows:e}),l.createElement("p",{className:"mt-4 text-sm text-gray-600 dark:text-slate-400"},"These are the current stable releases. The page is rebuilt whenever a release ships, so the version numbers are as fresh as the last build of this site, on ",t,". The"," ",l.createElement("span",{className:"font-mono"},"/latest/")," URLs above are always live: they redirect to the newest installer at the moment you request them, even when this table is a day behind."))},A="border-b border-gray-300 px-3 py-2 text-left text-sm font-semibold text-gray-900 dark:border-slate-600 dark:text-slate-100",R="border-b border-gray-200 px-3 py-3 align-top text-left dark:border-slate-700",M=e=>{let{children:t}=e;return l.createElement("code",{className:"inline-block rounded bg-gray-100 px-1.5 py-0.5 font-mono text-sm whitespace-nowrap text-gray-800 dark:bg-slate-800 dark:text-slate-200"},t)},I=()=>l.createElement("div",{className:"not-prose my-8 w-full overflow-x-auto"},l.createElement("table",{className:"w-full border-collapse"},l.createElement("caption",{className:"mb-3 text-left text-sm text-gray-600 dark:text-slate-400"},"Every token the ",l.createElement("span",{className:"font-mono"},"/latest/")," URLs accept. An alias resolves to exactly the same installer as its canonical token."),l.createElement("thead",null,l.createElement("tr",null,l.createElement("th",{scope:"col",className:A},"Platform"),l.createElement("th",{scope:"col",className:A},"Token"),l.createElement("th",{scope:"col",className:A},"Also accepted"),l.createElement("th",{scope:"col",className:A},"Gets you"))),l.createElement("tbody",null,i.map((e=>{return l.createElement("tr",{key:e.platform},l.createElement("th",{scope:"row",className:R+" text-sm font-semibold whitespace-nowrap text-gray-900 dark:text-slate-100"},(t=e.platform,null!==(a=d.get(t))&&void 0!==a?a:t)),l.createElement("td",{className:R},l.createElement(M,null,e.platform)),l.createElement("td",{className:R},l.createElement("span",{className:"flex flex-wrap gap-1.5"},e.aliases.map((e=>l.createElement(M,{key:e},e))))),l.createElement("td",{className:R+" font-mono text-sm whitespace-nowrap"},e.fileExtension));var t,a})))));var V=a(24176);const _=(e,t)=>
1{const a=e.downloads.find((e=>m(e)===t));if(!a)throw new Error("Release "+e.version+" has no "+t+" download");return a},z=e=>{var t;const a=null===(t=e.signatures)||void 0===t?void 0:t.ed25519;if(!a)throw new Error(e.fileName+" has no ed25519 signature");return a},O=(e,t,a)=>{var n;const l=_(t,a),r=null!==(n=l.signatures)&&void 0!==n?n:{};return Object.assign({version:t.version,released:t.releaseDate,url:""+s.h.siteUrl+l.uri,latestUrl:u(e,a),fileName:l.fileName,size:l.fileSize},r.sha256?{sha256:r.sha256}:{},r.ed25519?{ed25519:r.ed25519}:{})},P=(e,t)=>{const a={schemaVersion:1,product:o.R.displayNameForProduct(e),slug:e,version:t.version,released:t.releaseDate,releaseNotes:s.h.siteUrl+"/"+e+"/download",downloads:{mac:O(e,t,"mac")}};return JSON.stringify(a,null,4)},j=()=>(0,r.useStaticQuery)("2436439545").texturepacker.nodes[0],B="jN2H7aeSZm8Y3vFbDpD80J5pXpnzfJNq5+Uqj5ryUAI=",D=()=>l.createElement(l.Fragment,null,j().version),F=()=>l.createElement(V.N,{className:"language-json",codeString:P("texturepacker",j())}),W=()=>{const e=_(j(),"windows").fileName;return l.createElement(V.N,{className:"language-bash",codeString:"(Get-AuthenticodeSignature .\\"+e+").Status\n(Get-AuthenticodeSignature .\\"+e+").SignerCertificate.Subject\n# Valid\n# CN=CodeAndWeb GmbH, O=CodeAndWeb GmbH, L=Neu-Ulm, S=Bavaria, C=DE"})},G=()=>l.createElement(V.N,{codeString:B}),q=()=>{const e=_(j(),"windows");return l.createElement(V.N,{className:"language-bash",codeString:"printf '\\x30\\x2a\\x30\\x05\\x06\\x03\\x2b\\x65\\x70\\x03\\x21\\x00' > key.der\necho \""+B+'" | base64 -d >> key.der\nopenssl pkey -pubin -inform DER -in key.der -out key.pem\necho "'+z(e)+'" | base64 -d > sig.bin\nopenssl pkeyutl -verify -pubin -inkey key.pem -rawin -in '+e.fileName+" -sigfile sig.bin\n# Signature Verified Successfully"})};function $(e){const t=Object.assign({h1:"h1",a:"a",span:"span",p:"p",code:"code",h2:"h2",pre:"pre",strong:"strong",h3:"h3",em:"em",table:"table",thead:"thead",tr:"tr",th:"th",tbody:"tbody",td:"td"},(0,n.R)(),e.components);return l.createElement(l.Fragment,null,l.createElement("div",{className:"h-8"}),"\n",l.createElement(t.h1,{id:"product-downloads",style:{position:"relative"}},"Product downloads",l.createElement(t.a,{href:"#product-downloads","aria-label":"product downloads permalink",className:"anchor after"},l.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}}))),"\n",l.createElement(t.p,null,"Every CodeAndWeb app has a download URL that never changes. Ask for\n",l.createElement(t.code,null,"https://www.codeandweb.com/download/texturepacker/latest/mac")," and you get the installer of the\nnewest stable release, whatever that happens to be today."),"\n",l.createElement(t.p,null,"This page is for the scripts rather than for the people: a Dockerfile, an install script, a Windows\nor Linux packager, an agent fetching a build. Anything that wants the current installer without\nparsing an XML feed that was designed for the macOS updater. If you are here to install one of our\napps on your own machine, the per-product download pages are the friendlier place to be, and they\nare linked from every row of the table below."),"\n",l.createElement(t.h2,{id:"current-downloads",style:{position:"relative"}},"Current downloads",l.createElement(t.a,{href:"#current-downloads","aria-label":"current downloads permalink",className:"anchor after"},l.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}}))),"\n",l.createElement(U),"\n",l.createElement(t.h2,{id:"the-url-pattern",style:{position:"relative"}}
1,"The URL pattern",l.createElement(t.a,{href:"#the-url-pattern","aria-label":"the url pattern permalink",className:"anchor after"},l.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}}))),"\n",l.createElement(t.pre,null,l.createElement(t.code,null,"https://www.codeandweb.com/download/<product>/latest/<platform>\n")),"\n",l.createElement(t.p,null,"The product is one of ",l.createElement(t.code,null,"texturepacker"),", ",l.createElement(t.code,null,"texturepacker3d"),", ",l.createElement(t.code,null,"physicseditor"),", ",l.createElement(t.code,null,"spriteilluminator")," or\n",l.createElement(t.code,null,"babeledit"),". The platform is one of the tokens below. Both are case-insensitive, so\n",l.createElement(t.code,null,"/download/TexturePacker/latest/Mac")," works exactly like the lowercase form."),"\n",l.createElement(t.p,null,"The URL answers with a ",l.createElement(t.code,null,"302")," to the versioned installer, so the file you end up with still carries\nits version in the name. The ",l.createElement(t.code,null,"302")," also carries an ",l.createElement(t.code,null,"X-Latest-Version")," header, which is the cheapest\nway to find out what the URL currently points at from a shell:"),"\n",l.createElement(t.pre,null,l.createElement(t.code,{className:"language-bash"},"curl -fsSI https://www.codeandweb.com/download/texturepacker/latest/mac | grep -i x-latest-version\n")),"\n",l.createElement(t.p,null,l.createElement(t.strong,null,"From a browser, read the version out of the manifest instead.")," A cross-origin ",l.createElement(t.code,null,"fetch()")," of this\nURL does not work at all: with the default ",l.createElement(t.code,null,'redirect: "follow"')," the browser re-runs the CORS check\nagainst the installer the ",l.createElement(t.code,null,"302")," points at, that file is served straight off disk without an\n",l.createElement(t.code,null,"Access-Control-Allow-Origin")," header, and the whole call rejects with a ",l.createElement(t.code,null,"TypeError")," rather than\nreturning anything you can read. With ",l.createElement(t.code,null,'redirect: "manual"')," you get an opaque response whose headers\nare unreadable by definition, so ",l.createElement(t.code,null,"X-Latest-Version")," is out of reach either way. The manifest below\nis not a redirect, so its ",l.createElement(t.code,null,"version")," is readable from a browser like any other JSON field."),"\n",l.createElement(t.p,null,"Both ",l.createElement(t.code,null,"/latest")," and ",l.createElement(t.code,null,"/latest/")," serve the manifest, and both will keep doing so. Prefer the form\nwithout the trailing slash: it is the one every example here uses, and picking one spelling keeps\nyou off a second cache entry for the same document."),"\n",l.createElement(t.h3,{id:"platform-tokens",style:{position:"relative"}},"Platform tokens",l.createElement(t.a,{href:"#platform-tokens","aria-label":"platform tokens permalink",className:"anchor after"},l.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}}))),"\n",l.createElement(I),"\n",l.createElement(t.p,null,"Not every app ships every platform. TexturePacker3D has no Linux build at all, and PhysicsEditor and\nSpriteIlluminator ship no Flatpak, so those three apps list fewer platforms in the downloads table at\nthe top of this page. Asking for a platform we do not build returns a ",l.createElement(t.code,null,"404")," that names the platforms\nthat app does have."),"\n",l.createElement(t.h2,{id:"the-manifests",style:{position:"relative"}}
1,"The manifests",l.createElement(t.a,{href:"#the-manifests","aria-label":"the manifests permalink",className:"anchor after"},l.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}}))),"\n",l.createElement(t.p,null,"If you would rather resolve the version yourself than follow a redirect, every app publishes a small\nJSON manifest:"),"\n",l.createElement(t.pre,null,l.createElement(t.code,null,"https://www.codeandweb.com/download/<product>/latest.json\n")),"\n",l.createElement(F),"\n",l.createElement(t.p,null,l.createElement(t.code,null,"url")," is the immutable, versioned installer. ",l.createElement(t.code,null,"latestUrl")," is the floating URL from the section above.\nA platform we do not build is left out of ",l.createElement(t.code,null,"downloads")," entirely rather than being present with a\n",l.createElement(t.code,null,"null"),", and ",l.createElement(t.code,null,"sha256")," or ",l.createElement(t.code,null,"ed25519")," can be missing for an older build, so read them defensively."),"\n",l.createElement(t.p,null,l.createElement(t.code,null,"ed25519")," is the same EdDSA signature the appcast carries. See\n",l.createElement(t.a,{href:"#checking-what-you-downloaded"},"checking what you downloaded")," for what to do with it."),"\n",l.createElement(t.p,null,"All five apps in one document:"),"\n",l.createElement(t.pre,null,l.createElement(t.code,null,"https://www.codeandweb.com/download/latest.json\n")),"\n",l.createElement(t.p,null,"That one wraps the same per-app manifests in a ",l.createElement(t.code,null,"products")," array. If one app's release data cannot be\nread, that app drops out of the array and the other four still answer."),"\n",l.createElement(t.p,null,"Manifests and redirects are both sent with ",l.createElement(t.code,null,"Cache-Control: max-age=60, public")," and\n",l.createElement(t.code,null,"Access-Control-Allow-Origin: *"),", so the manifests are readable from a browser as well as from a\nbuild script. The redirects are not: following one cross-origin puts the browser on the installer\nfile itself, which carries no such header, so ",l.createElement(t.code,null,"fetch()")," rejects. Use the manifests from a browser\nand the ",l.createElement(t.code,null,"/latest/<platform>")," URLs from anything that is not one."),"\n",l.createElement(t.h2,{id:"the-sparkle-appcast",style:{position:"relative"}},"The Sparkle appcast",l.createElement(t.a,{href:"#the-sparkle-appcast","aria-label":"the sparkle appcast permalink",className:"anchor after"},l.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}}))),"\n",l.createElement(t.p,null,"Every app publishes a ",l.createElement(t.a,{href:"https://sparkle-project.org/"},"Sparkle")," feed, and it covers all four\nplatforms, not only macOS:"),"\n",l.createElement(t.pre,null,l.createElement(t.code,null,"https://www.codeandweb.com/releases/<product>/updatecheck.php?o=mac\n")),"\n",l.createElement(t.p,null,l.createElement(t.code,null,"o")," takes ",l.createElement(t.code,null,"mac"),", ",l.createElement(t.code,null,"win")," or ",l.createElement(t.code,null,"linux")," and defaults to Windows. ",l.createElement(t.code,null,"ov=flatpak")," gets the Flatpak, ",l.createElement(t.code,null,"bit=32"),"\nthe last 32
1-bit build, ",l.createElement(t.code,null,"lang=de")," localised release-notes links. Stable releases only, newest first,\neach enclosure signed with ",l.createElement(t.code,null,"sparkle:edSignature")," and ",l.createElement(t.code,null,"sparkle:dsaSignature"),". This is the feed\nHomebrew reads. There is also ",l.createElement(t.code,null,"/releases/<product>/appcast-mac-release.xml"),", macOS only, no\nparameters."),"\n",l.createElement(t.p,null,"It differs from ",l.createElement(t.code,null,"latest.json")," in two ways that matter. It ",l.createElement(t.strong,null,"stops at a version we set by hand"),", so\na release aimed at new installs can go onto the site without prompting existing customers to update\nfor features they did not ask for. And it ",l.createElement(t.strong,null,"honours minimum OS versions"),": pass the running system\nas ",l.createElement(t.code,null,"ov=12.0.0")," and an old mac is offered the newest release it can actually run, which the ",l.createElement(t.code,null,"latest"),"\nURLs never do."),"\n",l.createElement(t.h2,{id:"every-build-is-scanned-before-we-upload-it",style:{position:"relative"}},"Every build is scanned before we upload it",l.createElement(t.a,{href:"#every-build-is-scanned-before-we-upload-it","aria-label":"every build is scanned before we upload it permalink",className:"anchor after"},l.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}}))),"\n",l.createElement(t.p,null,"Every installer goes through ",l.createElement(t.a,{href:"https://www.virustotal.com/"},"VirusTotal")," before it reaches our\nservers. VirusTotal addresses a file by its SHA-256, the same ",l.createElement(t.code,null,"sha256")," the manifest publishes, so\nthe report for any build is a URL you can assemble yourself:"),"\n",l.createElement(t.pre,null,l.createElement(t.code,null,"https://www.virustotal.com/gui/file/<sha256>\n")),"\n",l.createElement(t.p,null,"That scan describes the file when we published it. The ",l.createElement(t.code,null,"sha256")," is what proves the bytes you\ndownloaded are the ones we scanned, so verify it anyway."),"\n",l.createElement(t.h2,{id:"checking-what-you-downloaded",style:{position:"relative"}},"Checking what you downloaded",l.createElement(t.a,{href:"#checking-what-you-downloaded","aria-label":"checking what you downloaded permalink",className:"anchor after"},l.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}}))),"\n",l.createElement(t.p,null,"Three ways, in the order most people should reach for them."),"\n",l.createElement(t.p,null,l.createElement(t.strong,null,"The code signature.")," Every installer is signed, and your operating system already knows how to\ncheck it. No key from us, nothing to pin. On macOS the signature is a notarized Developer ID:"),"\n",l.createElement(t.pre,null,l.createElement(t.code,{className:"language-bash"},"spctl -a -vvv -t install /Applications/TexturePacker.app\n# source=Notarized Developer ID\n# origin=Developer ID Application: CodeAndWeb GmbH (8P8584JRFS)\n")),"\n",l.createElement(t.p,null,"On Windows the installer carries an extended-validation certificate issued to ",l.createElement(t.code,null,"CodeAndWeb GmbH"),":"),"\n",l.createElement(W),"\n",l.createElement(t.p,null,l.createElement(t.strong,null,"The ",l.createElement(t.code,null,"sha256"),".")," For a build machine, pin the versioned ",l.createElement(t.code,null,"url")," and its ",l.createElement(t.code,null,"sha256")," and compare after\ndownloading, as in ",l.createElement(t.a,{href:"#resolve-verify-pin"},"the CI recipe")," below. That catches a truncated or swapped\nfile."),"\n",l.createElement(t.p,null,l.createElement(t.strong,null,"The ",l.createElement(t.code,null,"ed25519")," signature.")," The checksum sits on the same server as the installer, so it cannot\ntell you that server was not compromised. The signing key is not on it, so the signature can. Our\npublic key, the same one Sparkle uses and the same one already inside every app you have installed:"),"\n",l.createElement(G),"\n",l.createElement(t.p,null,"Pin that value in y
1our build. Checking a signature against a key you fetched in the same breath\nproves nothing. Then, with OpenSSL 3:"),"\n",l.createElement(q),"\n",l.createElement(t.p,null,"Those twelve bytes are the DER header for an Ed25519 public key: Sparkle publishes the bare 32-byte\nkey and OpenSSL wants it wrapped. Note the macOS system ",l.createElement(t.code,null,"openssl")," is LibreSSL and cannot do this;\nuse OpenSSL 3 or any language binding."),"\n",l.createElement(t.h2,{id:"using-the-urls-in-a-script",style:{position:"relative"}},"Using the URLs in a script",l.createElement(t.a,{href:"#using-the-urls-in-a-script","aria-label":"using the urls in a script permalink",className:"anchor after"},l.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}}))),"\n",l.createElement(t.p,null,"Every example below uses ",l.createElement(t.code,null,"curl -fL"),", and the ",l.createElement(t.code,null,"-f")," is not decoration. Without it ",l.createElement(t.code,null,"curl")," treats an HTTP\nerror as success: ",l.createElement(t.code,null,"curl -sLO .../latest/mac")," against a 404 exits ",l.createElement(t.code,null,"0")," and writes the error page to\ndisk as a file called ",l.createElement(t.code,null,"mac"),". With ",l.createElement(t.code,null,"-f")," it exits n
1on-zero and writes nothing. Test for failure, not\nfor a particular exit code: it is 56 over HTTP/2 and 22 over HTTP/1.1."),"\n",l.createElement(t.p,null,l.createElement(t.code,null,"-O")," is the other trap. It names the file after the URL you typed, not the redirect target, so\n",l.createElement(t.code,null,"/latest/mac")," saves a file called ",l.createElement(t.code,null,"mac"),". Browsers and ",l.createElement(t.code,null,"wget")," keep the versioned name, ",l.createElement(t.code,null,"curl -O")," does\nnot. Use ",l.createElement(t.code,null,"-o"),". The one safe place for ",l.createElement(t.code,null,"-O")," is the pinned CI recipe below, where the URL already ends\nin the file name."),"\n",l.createElement(t.h3,{id:"resolve-verify-pin",style:{position:"relative"}},"In CI: resolve, verify, pin",l.createElement(t.a,{href:"#resolve-verify-pin","aria-label":"resolve verify pin permalink",className:"anchor after"},l.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}}))),"\n",l.createElement(t.p,null,"This is the workflow we would run in a build. Read the manifest once, commit the immutable ",l.createElement(t.code,null,"url")," and\nits ",l.createElement(t.code,null,"sha256"),", and let the build fetch that exact file:"),"\n",l.createElement(t.pre,null,l.createElement(t.code,{className:"language-bash"},'# once, by hand: read "url" and "sha256" out of the manifest\ncurl -fsSL https://www.codeandweb.com/download/texturepacker/latest.json\n\n# in the build, with the committed values:\ncurl -fLO "$TP_URL" && echo "$TP_SHA256 $(basename "$TP_URL")" | sha256sum -c\n')),"\n",l.createElement(t.p,null,"The build is now reproducible, a tampered file fails the checksum, and upgrading is a visible commit\nrather than something that happens to you on a Tuesday."),"\n",l.createElement(t.p,null,l.createElement(t.strong,null,"The ",l.createElement(t.code,null,"sha256")," belongs to ",l.createElement(t.code,null,"url"),", not to ",l.createElement(t.code,null,"latestUrl"),".")," Read the checksum from the manifest and then\nfetch ",l.createElement(t.code,null,"latestUrl"),", and a release landing between those two calls looks exactly like a corrupted\ndownload. If you want to follow ",l.createElement(t.code,null,"latest"),", skip the checksum. If you want the checksum, pin the ",l.createElement(t.code,null,"url"),"\nit came with."),"\n",l.createElement(t.h3,{id:"on-your-own-machine-follow-latest",style:{position:"relative"}},"On your own machine: follow latest",l.createElement(t.a,{href:"#on-your-own-machine-follow-latest","aria-label":"on your own machine follow latest permalink",className:"anchor after"},l.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}}))),"\n",l.createElement(t.p,null,"For an evaluation, a developer laptop or a throwaway container, following the floating URL is the\nwhole point of it:"),"\n",l.createElement(t.pre,null,l.createElement(t.code,{className:"language-bash"},"curl -fL -o TexturePacker.dmg https://www.codeandweb.com/download/texturepacker/latest/mac\n")),"\n",l.createElement(t.h3,{id:"powershell",style:{position:"relative"}},"PowerShell",l.createElement(t.a,{href:"#powershell","aria-label":"powershell permalink",className:"anchor after"},l.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}}))),"\n",l.createElement(t.p,null,l.createElement(t.code,null,"Invoke-WebRequest")," follows the redirect and fails on an HTTP error on its own, writing nothing to\ndisk, so it needs no equivalent of ",l.createElement(t.code,null,"-f"),". Do not expect the error to name the status though: against\nour 404s .
1NET cannot read the error body and reports\n",l.createElement(t.code,null,"WebException: The request was aborted: The connection was closed unexpectedly"),". Test for failure,\nnot for a message."),"\n",l.createElement(t.pre,null,l.createElement(t.code,{className:"language-bash"},"Invoke-WebRequest -Uri https://www.codeandweb.com/download/texturepacker/latest/windows -OutFile TexturePacker.msi\n")),"\n",l.createElement(t.p,null,"Resolve, verify and pin looks like this:"),"\n",l.createElement(t.pre,null,l.createElement(t.code,{className:"language-bash"},'# once, by hand: read the values out of the manifest\n$manifest = Invoke-RestMethod https://www.codeandweb.com/download/texturepacker/latest.json\n$manifest.downloads.windows.url\n$manifest.downloads.windows.sha256\n\n# in the build, with the committed values:\nInvoke-WebRequest -Uri $env:TP_URL -OutFile TexturePacker.msi\nif ((Get-FileHash TexturePacker.msi -Algorithm SHA256).Hash -ne $env:TP_SHA256) {\n throw "TexturePacker.msi does not match the published checksum"\n}\n')),"\n",l.createElement(t.p,null,l.createElement(t.code,null,"Get-FileHash")," returns upper-case hex and the manifest publishes lower case. ",l.createElement(t.code,null,"-ne")," compares\ncase-insensitively, so the line above is right as written. Change it to ",l.createElement(t.code,null,"-cne"),", or port it to a\nlanguage that compares bytes, and it fails on every build."),"\n",l.createElement(t.h2,{id:"things-to-watch-out-for",style:{position:"relative"}},"Things to watch out for",l.createElement(t.a,{href:"#things-to-watch-out-for","aria-label":"things to watch out for permalink",className:"anchor after"},l.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}}))),"\n",l.createElement(t.h3,{id:"docker-layer-caching",style:{position:"relative"}},"Docker caches the layer, not the answer",l.createElement(t.a,{href:"#docker-layer-caching","aria-label":"docker layer caching permalink",className:"anchor after"},l.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}}))),"\n",l.createElement(t.p,null,"Docker caches a ",l.createElement(t.code,null,"RUN")," line by its text alone, and the text of a version-less URL never changes. So a\n",l.createElement(t.code,null,"RUN curl .../latest/...")," layer keeps installing the build it downloaded the first time, months after\nthe redirect moved on. This is the single most common surprise with a floating URL. Prefer ",l.createElement(t.code,null,"ADD")," with\nthe URL, and when you need the certainty, build with ",l.createElement(t.code,null,"--no-cache")," or bump a ",l.createElement(t.code,null,"--build-arg")," that the\ndownload line depends on."),"\n",l.createElement(t.h3,{id:"bare-linux-means-debian-packaging",style:{position:"relative"}},"Bare ",l.createElement(t.code,null,"linux")," means Debian packaging",l.createElement(t.a,{href:"#bare-linux-means-debian-packaging","aria-label":"bare linux means debian packaging permalink",className:"anchor after"},l.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}}))),"\n",l.createElement(t.p,null,l.createElement(t.code,null,"linux")," is an alias for ",l.createElement(t.code,null,"linux-deb"),", so it gives you a ",l.createElement(t.code,null,".deb"),'. That is invisible to somebody on\nFedora or Arch who reasonably expected "the Linux build". Ask for ',l.createElement(t.code,null,"linux-flatpak")," if you want the\ndistro-independent package, and see the table above for which apps have one."),"\n",l.createElement(t.h3,{id:"a-platform-can-be-retired",style:{position:"relative"}}
1,"A platform can be retired",l.createElement(t.a,{href:"#a-platform-can-be-retired","aria-label":"a platform can be retired permalink",className:"anchor after"},l.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}}))),"\n",l.createElement(t.p,null,"A platform token resolves to the newest stable release that shipped ",l.createElement(t.em,null,"that platform"),", which is not\nalways the newest release. So if we stopped building a ",l.createElement(t.code,null,".deb"),", ",l.createElement(t.code,null,"/latest/linux-deb")," would keep handing\nyou the last one we built, indefinitely and without an error. It will never give you a different\npackage format, but it can silently stop moving."),"\n",l.createElement(t.p,null,"A ",l.createElement(t.code,null,"404")," means something narrower: we have never shipped that platform for that app, which is why\n",l.createElement(t.code,null,"/download/physicseditor/latest/linux-flatpak")," is a ",l.createElement(t.code,null,"404")," today."),"\n",l.createElement(t.p,null,"To catch a platform that has stopped moving, watch its per-platform ",l.createElement(t.code,null,"version")," in ",l.createElement(t.code,null,"latest.json"),". One\nrelease of lag proves nothing, since a release can ship without every platform, but one that stays\nbehind over several releases is no longer being built."),"\n",l.createElement(t.h3,{id:"the-minimum-os-version-is-not-expressed",style:{position:"relative"}},"The minimum OS version is not expressed",l.createElement(t.a,{href:"#the-minimum-os-version-is-not-expressed","aria-label":"the minimum os version is not expressed permalink",className:"anchor after"},l.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}}))),"\n",l.createElement(t.p,null,"The in-app updater knows that macOS 12 is offered TexturePacker 7.11.0, macOS 11 gets 7.5.0, and\nolder systems go further back still. The ",l.createElement(t.code,null,"latest")," URLs know nothing about any of that: they always\nresolve to the newest stable release. A macOS 12 user who runs the ",l.createElement(t.code,null,"curl")," line above gets ",l.createElement(D),", a\nversion the updater would never have offered them because their system is too old for it. For an\nolder system, use the per-product ",l.createElement(t.a,{href:"/texturepacker/download"},"download page")," and pick the release by\nhand."),"\n",l.createElement(t.h3,{id:"license-update-window",style:{position:"relative"}},"A license covers a date range, not a version range",l.createElement(t.a,{href:"#license-update-window","aria-label":"license update window permalink",className:"anchor after"},l.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}}))),"\n",l.createElement(t.p,null,"Single User and Volume licenses are perpetual and cover every release, major or minor, published\ninside their update window: twelve months from purchase, or forever with a
1Lifetime license. So a\nmajor version jump is not itself the problem. A build published after that window closed is, and\nfollowing ",l.createElement(t.code,null,"latest")," is a good way to meet one without noticing."),"\n",l.createElement(t.p,null,"Those licenses also do not permit use on a server or in an automated build at all. The floating\nDocker/CI license does, and it always permits the newest version, which is what makes it the right\none to put behind a ",l.createElement(t.code,null,"latest")," URL. See\n",l.createElement(t.a,{href:"/texturepacker/documentation/docker-ci"},"Docker and CI installation"),"."),"\n",l.createElement(t.h2,{id:"reserved-and-not-available-today",style:{position:"relative"}},"Reserved, and not available today",l.createElement(t.a,{href:"#reserved-and-not-available-today","aria-label":"reserved and not available today permalink",className:"anchor after"},l.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}}))),"\n",l.createElement(t.p,null,"These shapes are reserved so the contract can grow without breaking. ",l.createElement(t.strong,null,"All of them return ",l.createElement(t.code,null,"404"),"\nright now.")," Do not write them into a script expecting them to start working:"),"\n",l.createElement(t.table,null,l.createElement(t.thead,null,l.createElement(t.tr,null,l.createElement(t.th,null,"Shape"),l.createElement(t.th,null,"Intended for"))),l.createElement(t.tbody,null,l.createElement(t.tr,null,l.createElement(t.td,null,l.createElement(t.code,null,"mac-arm64"),", ",l.createElement(t.code,null,"mac-x64")),l.createElement(t.td,null,"separate Apple Silicon and Intel builds, if we ever stop shipping universal binaries")),l.createElement(t.tr,null,l.createElement(t.td,null,l.createElement(t.code,null,"/download/<product>/beta/<platform>")),l.createElement(t.td,null,"the newest beta instead of the newest stable")),l.createElement(t.tr,null,l.createElement(t.td,null,l.createElement(t.code,null,"/download/<product>/<major>/latest/<platform>")),l.createElement(t.td,null,"the newest release within one major version")))),"\n",l.createElement(t.p,null,l.createElement(t.code,null,"win64")," is deliberately ",l.createElement(t.strong,null,"not")," a token and will keep returning ",l.createElement(t.code,null,"404"),'. A URL with "64" in the name\nmust never be able to hand somebody a 32-bit build, and the ',l.createElement(t.code,null,"windows")," token resolves to whichever\nWindows build a release actually carries."),"\n",l.createElement(t.h2,{id:"package-managers",style:{position:"relative"}},"Package managers",l.createElement(t.a,{href:"#package-managers","aria-label":"package managers permalink",className:"anchor after"},l.createElement(t.span,{dangerouslySetInnerHTML:{__html:'<svg aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>'}}))),"\n",l.createElement(t.p,null,"Homebrew already tracks TexturePacker and BabelEdit through the ",l.createElement(t.a,{href:"#the-sparkle-appcast"},"Sparkle appcast"),"\nand computes its own checksums, so nothing here changes for ",l.createElement(t.code,null,"brew install"),". Chocolatey and winget have\nnothing, because neither of them speaks Sparkle. That is the gap these URLs exist to close, and it is\na format gap rather than a platform one: the appcast has carried Windows and Linux builds for years,\nbut only in an RSS dialect written for one updater framework."),"\n",l.createElement("div",{className:"h-16"}))}var J=function(e){void 0===e&&(e={});const{wrapper:t}=Object.assign({},(0,n.R)(),e.components);return t?l.createElement(t,e,l.createElement($,e)):$(e)},X=a(11042),Q=a(77891);function Y(e){let{children:t}=e;return l.createElement(Q.W,null,l.createElement("div",{className:"mx-auto px-4 max-w-4xl min-w-0"},l.createElement("main",{id:"main-content",className:"prose max-w-4xl"},t)))}function Z(e){return l.createElement(Y,e,l.createElement(J,e))}const K=e=>{let{pageContext:t}=e;return l.createElement(l.Fragment,null,l.createElement(X.A,{pageData:t.pageData}))}},70259:function(e,t,a){var n=a(34376),l=a(26198),r=a(96837),o=a(76080),s=function(e,t,a,c,i,d,h,m){for(var u,p,f=i,w=0,v=!!h&&o(h,m);w<c;)w in a&&(u=v?v(a[w],w,t):a[w],d>0&&n(u)?(p=l(u),f=s(e,t,u,p,f,d-1)-1):(r(f+1),e[f]=u),f++),w++;return f};e.exports=s},71072:function(e,t,a){var n=a(61828),l=a(88727);e.exports=Object.keys||function(e){return n(e,l)}},76080:function(e,t,a){var n=a(27476),l=a(79306),r=a(40616),o=n(n.bind);e.exports=function(e,t){return l(e),void 0===t?e:r?o(e,t):function(){return e.apply(t,arguments)}}},78350:function(e,t,a){var n=a(46518),l=a(70259),r=a(79306),o=a(48981),s=a(26198),c=a(1469);n({target:"Array",proto:!0},{flatMap:function(e){var t,a=o(this),n=s(a);return r(e),(t=c(a,0)).length=l(t,a,a,n,0,1,e,arguments.length>1?arguments[1]:void 0),t}})},87433:function(e,t,a){var n=a(34376),l=a(33517),r=a(20034),o=a(78227)("species"),s=Array;e.exports=function(e){var t;return n(e)&&(t=e.constructor,(l(t)&&(t===s||n(t.prototype))||r(t)&&null===(t=t[o]))&&(t=void 0)),void 0===t?s:t}}
1,96801:function(e,t,a){var n=a(43724),l=a(48686),r=a(24913),o=a(28551),s=a(25397),c=a(71072);t.f=n&&!l?Object.defineProperties:function(e,t){o(e);for(var a,n=s(t),l=c(t),i=l.length,d=0;i>d;)r.f(e,a=l[d++],n[a]);return e}},96837:function(e){var t=TypeError;e.exports=function(e){if(e>9007199254740991)throw t("Maximum allowed index exceeded");return e}}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.