@stacksjs/stx 0.2.339 → 0.2.340

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist/builtins/stx-image.js +3 -2
  2. package/dist/builtins/stx-link.js +2 -1
  3. package/dist/{chunk-63h3pg9g.js → chunk-0pje0pjq.js} +1 -1
  4. package/dist/{chunk-7157az6e.js → chunk-1w8bpytb.js} +1 -1
  5. package/dist/{chunk-c3xmcsvd.js → chunk-284ag6fs.js} +8 -8
  6. package/dist/{chunk-pswhshrp.js → chunk-3agx4hyd.js} +1 -1
  7. package/dist/{chunk-1q6xc3m2.js → chunk-3b9sxnty.js} +1 -1
  8. package/dist/{chunk-gz6mszbx.js → chunk-5ggt2ryn.js} +1 -1
  9. package/dist/{chunk-6n1qbh78.js → chunk-5t8ersrw.js} +1 -1
  10. package/dist/{chunk-wqyfz0y6.js → chunk-605955nh.js} +1 -1
  11. package/dist/{chunk-q1xcd8jg.js → chunk-6es5r4az.js} +1 -1
  12. package/dist/{chunk-x2pzekhq.js → chunk-6j6ek3j1.js} +1 -1
  13. package/dist/{chunk-63xv0h5e.js → chunk-6nde3nmc.js} +1 -1
  14. package/dist/{chunk-chkbvqq2.js → chunk-6zsc0z5a.js} +2 -2
  15. package/dist/{chunk-rzayq0ht.js → chunk-756dqqn1.js} +1 -1
  16. package/dist/{chunk-sntjqyqq.js → chunk-7xjm1zqa.js} +1 -1
  17. package/dist/{chunk-ca6t6nnn.js → chunk-82h5rp34.js} +1 -1
  18. package/dist/{chunk-e2emz8yb.js → chunk-8de19dmw.js} +1 -1
  19. package/dist/{chunk-az4mq459.js → chunk-95e1m9p8.js} +1 -1
  20. package/dist/{chunk-a2nsmczq.js → chunk-9pv7gcey.js} +1 -1
  21. package/dist/{chunk-408rnn07.js → chunk-a2mj88c6.js} +1 -1
  22. package/dist/{chunk-tvc2xwen.js → chunk-apa2v787.js} +1 -1
  23. package/dist/{chunk-09fb200s.js → chunk-cjcj2gsq.js} +1 -1
  24. package/dist/{chunk-vafj091j.js → chunk-d964bsxm.js} +283 -283
  25. package/dist/{chunk-0b3cqgyr.js → chunk-dy9xs2q0.js} +1 -1
  26. package/dist/{chunk-156j37tv.js → chunk-f476c9fb.js} +1 -1
  27. package/dist/{chunk-zm8b6e9n.js → chunk-fx07kyz7.js} +1 -1
  28. package/dist/{chunk-ar0fcmpj.js → chunk-g0059g3p.js} +1 -1
  29. package/dist/{chunk-e4sgv8p0.js → chunk-gf6t4419.js} +1 -1
  30. package/dist/{chunk-0tky87hx.js → chunk-h7hnzwd0.js} +1 -1
  31. package/dist/{chunk-mpbbfrpk.js → chunk-hkgnpf6v.js} +1 -1
  32. package/dist/{chunk-xhznr05n.js → chunk-hq9fa4pe.js} +1 -1
  33. package/dist/{chunk-e0r2hndd.js → chunk-hvw4mwee.js} +1 -1
  34. package/dist/{chunk-pvtnhx3j.js → chunk-kwxcertb.js} +1 -1
  35. package/dist/{chunk-191bg6nt.js → chunk-mb7rn0bf.js} +1 -1
  36. package/dist/{chunk-wdq75f5k.js → chunk-p7ntrtaj.js} +1 -1
  37. package/dist/{chunk-7qawmh8w.js → chunk-ppp84wry.js} +2 -2
  38. package/dist/{chunk-qtc28zy1.js → chunk-pxw0cpxe.js} +1 -1
  39. package/dist/{chunk-wdvee4kr.js → chunk-r5kr6jk4.js} +1 -1
  40. package/dist/{chunk-d72ps148.js → chunk-rceawm7w.js} +1 -1
  41. package/dist/{chunk-zfz3s2gd.js → chunk-shtg7kah.js} +1 -1
  42. package/dist/{chunk-48d9ey9k.js → chunk-t3mh10z3.js} +1 -1
  43. package/dist/{chunk-pqtype7s.js → chunk-vz0ebk4j.js} +1 -1
  44. package/dist/{chunk-evk1dzpg.js → chunk-wjcrcbd1.js} +1 -1
  45. package/dist/{chunk-wwzzyqcg.js → chunk-x80ahcnw.js} +1 -1
  46. package/dist/{chunk-xnpdkd13.js → chunk-x81z8m6a.js} +1 -1
  47. package/dist/{chunk-tzywy1s7.js → chunk-y07fna67.js} +1 -1
  48. package/dist/{chunk-n6eetz4r.js → chunk-yxpr7h26.js} +2 -2
  49. package/dist/{chunk-azhpwmty.js → chunk-yz0d60px.js} +1 -1
  50. package/dist/{chunk-6qe53wjc.js → chunk-z19pk2pn.js} +1 -1
  51. package/dist/{chunk-zqtrg15a.js → chunk-z7vb3mgn.js} +1 -1
  52. package/dist/{chunk-tsmdmf5h.js → chunk-zg2hvb1j.js} +28 -1
  53. package/dist/cli.js +8 -8
  54. package/dist/craft-entry.js +1 -1
  55. package/dist/craft.js +1 -1
  56. package/dist/expressions.js +9 -4
  57. package/dist/pwa.js +1 -1
  58. package/dist/signals.js +28 -1
  59. package/dist/ssg.js +2 -2
  60. package/dist/url-safety.d.ts +67 -0
  61. package/dist/url-safety.js +40 -0
  62. package/package.json +5 -5
package/dist/ssg.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- import{uk as Uq,vk as Iq,zk as Fq}from"./chunk-fa2mg49t.js";import{Ak as Eq,Bk as Tq,Ck as Dq}from"./chunk-axqfm9b4.js";import{$k as Aq,Xk as _q,_k as zq}from"./chunk-wdvee4kr.js";import"./chunk-156j37tv.js";import"./chunk-mjf3xmxn.js";import{Jp as P,Om as Oq,io as l,lo as Hq,rr as n}from"./chunk-vafj091j.js";import"./chunk-d7zxg9bp.js";import"./chunk-t00ga194.js";import"./chunk-hrdd26r4.js";import"./chunk-7r79j1hs.js";import"./chunk-ay336sqb.js";import{ht as Xq}from"./chunk-pswhshrp.js";import{mt as d}from"./chunk-vaggjvsm.js";import"./chunk-q9ckfwhh.js";import"./chunk-zfz3s2gd.js";import"./chunk-k3dtvay1.js";import{Tt as Bq}from"./chunk-aved735s.js";import"./chunk-799s9mwc.js";import"./chunk-7r80apc5.js";import"./chunk-2tjk867y.js";import"./chunk-t8ctvjqe.js";import"./chunk-deqnt1w5.js";import{$v as jq,Zv as Yq,_v as Vq,aw as Gq,bw as Lq,cw as Nq}from"./chunk-d6yyzpsv.js";import"./chunk-wb90259a.js";import{tw as y}from"./chunk-5g6rprcy.js";import O from"fs";import j from"path";import{createHash as xq}from"crypto";function yq(q){try{return new Bun.Transpiler({loader:"js",minifyWhitespace:!0}).transformSync(q).replace(/\}(var |let |const |function )/g,"};$1").trim()}catch{return q}}var Pq=/<script\s+type="stx\/island"\s+data-stx-island="([^"]+)"([^>]*)>([\s\S]*?)<\/script>/g;function Cq(q){return Bun.hash(q).toString(16)}function Qq(q,Z={}){if(!q.includes('type="stx/island"'))return{html:q,chunks:[]};let $=new Map,Q=q.replace(Pq,(J,W,z,A)=>{let G=A.trim();if(!G)return J;if(/\bdata-stx-src=/.test(z))return J;if(Z.minify)G=yq(G);let X=Cq(G);if(!$.has(X))$.set(X,G);let B=Z.integrity?` data-stx-integrity="sha384-${xq("sha384").update(G).digest("base64")}"`:"";return`<script type="stx/island" data-stx-island="${W}" data-stx-src="/_stx/islands/${X}.js"${B}${z}></script>`}),K=Array.from($,([J,W])=>({hash:J,code:W}));return{html:Q,chunks:K}}var gq=new Set(["visible","idle"]);function Zq(q){if(!q.includes("data-stx-src="))return q;let Z=new Map,$=/data-stx-scope="([^"]+)"[^>]*?stx-hydrate="([^"]+)"/g,Q;while((Q=$.exec(q))!==null)Z.set(Q[1],Q[2]);let K=new Set,J=/data-stx-island="([^"]+)"[^>]*?data-stx-src="([^"]+)"/g,W;while((W=J.exec(q))!==null)if(gq.has(Z.get(W[1])||""))K.add(W[2]);let z=q.lastIndexOf("</head>");if(K.size===0||z===-1)return q;let A=Array.from(K).filter((G)=>!q.includes(`rel="prefetch" href="${G}"`)).map((G)=>`<link rel="prefetch" href="${G}" as="script">`).join("");if(!A)return q;return q.slice(0,z)+A+q.slice(z)}var fq=["STX_SITE_URL","SITE_URL","URL","VERCEL_URL","CF_PAGES_URL"];function h(q){let Z=q.trim();if(!Z)return null;let $=/^https?:\/\//i.test(Z)?Z:`https://${Z}`;try{return new URL($).origin+new URL($).pathname.replace(/\/$/,"")}catch{return null}}function mq(q){for(let Z of fq){let $=q[Z];if(!$)continue;let Q=h($);if(Q)return{url:Q,source:"env",via:Z}}return null}function $q(q){if(!q||typeof q!=="object")return null;let Z=q,$=Z.site?.url??Z.url??Z.app?.url;return typeof $==="string"?h($):null}async function dq(q){let{existsSync:Z}=await import("fs"),$=await import("path");for(let Q of["site.config.ts","site.config.js",".config/site.config.ts"]){let K=$.resolve(q,Q);if(!Z(K))continue;try{let J=await import(K),W=$q(J.default??J);if(W)return W}catch{}}return null}async function Kq(q={}){let Z=q.cwd??process.cwd(),$=q.env??process.env;if(q.explicit){let W=h(q.explicit);if(W)return{url:W,source:"option"}}let Q=$q(q.stxConfig);if(Q)return{url:Q,source:"stx.config",via:"site.url"};let K=await dq(Z);if(K)return{url:K,source:"site.config",via:"url"};let J=mq($);if(J)return J;return{url:"http://localhost",source:"fallback"}}function Wq(){return["No site URL configured \u2014 sitemap.xml and robots.txt will point at http://localhost."," Set one in stx.config.ts (site: { url: 'https://example.com' }), in site.config.ts,"," via --domain, or with SITE_URL in the build environment."].join(`
2
+ import{uk as Uq,vk as Iq,zk as Fq}from"./chunk-fa2mg49t.js";import{Ak as Eq,Bk as Tq,Ck as Dq}from"./chunk-axqfm9b4.js";import{$k as Aq,Xk as _q,_k as zq}from"./chunk-r5kr6jk4.js";import"./chunk-f476c9fb.js";import"./chunk-mjf3xmxn.js";import{Jp as P,Om as Oq,io as l,lo as Hq,rr as n}from"./chunk-d964bsxm.js";import"./chunk-d7zxg9bp.js";import"./chunk-t00ga194.js";import"./chunk-hrdd26r4.js";import"./chunk-7r79j1hs.js";import"./chunk-ay336sqb.js";import{ht as Xq}from"./chunk-3agx4hyd.js";import{mt as d}from"./chunk-vaggjvsm.js";import"./chunk-q9ckfwhh.js";import"./chunk-shtg7kah.js";import"./chunk-k3dtvay1.js";import{Tt as Bq}from"./chunk-aved735s.js";import"./chunk-799s9mwc.js";import"./chunk-7r80apc5.js";import"./chunk-2tjk867y.js";import"./chunk-t8ctvjqe.js";import"./chunk-deqnt1w5.js";import{$v as jq,Zv as Yq,_v as Vq,aw as Gq,bw as Lq,cw as Nq}from"./chunk-d6yyzpsv.js";import"./chunk-wb90259a.js";import{tw as y}from"./chunk-5g6rprcy.js";import O from"fs";import j from"path";import{createHash as xq}from"crypto";function yq(q){try{return new Bun.Transpiler({loader:"js",minifyWhitespace:!0}).transformSync(q).replace(/\}(var |let |const |function )/g,"};$1").trim()}catch{return q}}var Pq=/<script\s+type="stx\/island"\s+data-stx-island="([^"]+)"([^>]*)>([\s\S]*?)<\/script>/g;function Cq(q){return Bun.hash(q).toString(16)}function Qq(q,Z={}){if(!q.includes('type="stx/island"'))return{html:q,chunks:[]};let $=new Map,Q=q.replace(Pq,(J,W,z,A)=>{let G=A.trim();if(!G)return J;if(/\bdata-stx-src=/.test(z))return J;if(Z.minify)G=yq(G);let X=Cq(G);if(!$.has(X))$.set(X,G);let B=Z.integrity?` data-stx-integrity="sha384-${xq("sha384").update(G).digest("base64")}"`:"";return`<script type="stx/island" data-stx-island="${W}" data-stx-src="/_stx/islands/${X}.js"${B}${z}></script>`}),K=Array.from($,([J,W])=>({hash:J,code:W}));return{html:Q,chunks:K}}var gq=new Set(["visible","idle"]);function Zq(q){if(!q.includes("data-stx-src="))return q;let Z=new Map,$=/data-stx-scope="([^"]+)"[^>]*?stx-hydrate="([^"]+)"/g,Q;while((Q=$.exec(q))!==null)Z.set(Q[1],Q[2]);let K=new Set,J=/data-stx-island="([^"]+)"[^>]*?data-stx-src="([^"]+)"/g,W;while((W=J.exec(q))!==null)if(gq.has(Z.get(W[1])||""))K.add(W[2]);let z=q.lastIndexOf("</head>");if(K.size===0||z===-1)return q;let A=Array.from(K).filter((G)=>!q.includes(`rel="prefetch" href="${G}"`)).map((G)=>`<link rel="prefetch" href="${G}" as="script">`).join("");if(!A)return q;return q.slice(0,z)+A+q.slice(z)}var fq=["STX_SITE_URL","SITE_URL","URL","VERCEL_URL","CF_PAGES_URL"];function h(q){let Z=q.trim();if(!Z)return null;let $=/^https?:\/\//i.test(Z)?Z:`https://${Z}`;try{return new URL($).origin+new URL($).pathname.replace(/\/$/,"")}catch{return null}}function mq(q){for(let Z of fq){let $=q[Z];if(!$)continue;let Q=h($);if(Q)return{url:Q,source:"env",via:Z}}return null}function $q(q){if(!q||typeof q!=="object")return null;let Z=q,$=Z.site?.url??Z.url??Z.app?.url;return typeof $==="string"?h($):null}async function dq(q){let{existsSync:Z}=await import("fs"),$=await import("path");for(let Q of["site.config.ts","site.config.js",".config/site.config.ts"]){let K=$.resolve(q,Q);if(!Z(K))continue;try{let J=await import(K),W=$q(J.default??J);if(W)return W}catch{}}return null}async function Kq(q={}){let Z=q.cwd??process.cwd(),$=q.env??process.env;if(q.explicit){let W=h(q.explicit);if(W)return{url:W,source:"option"}}let Q=$q(q.stxConfig);if(Q)return{url:Q,source:"stx.config",via:"site.url"};let K=await dq(Z);if(K)return{url:K,source:"site.config",via:"url"};let J=mq($);if(J)return J;return{url:"http://localhost",source:"fallback"}}function Wq(){return["No site URL configured \u2014 sitemap.xml and robots.txt will point at http://localhost."," Set one in stx.config.ts (site: { url: 'https://example.com' }), in site.config.ts,"," via --domain, or with SITE_URL in the build environment."].join(`
3
3
  `)}class wq{cacheDir;cache=new Map;loaded=!1;salt;constructor(q,Z=""){this.cacheDir=q,this.salt=Z}async load(){if(this.loaded)return;let q=j.join(this.cacheDir,"build-cache.json");if(await Bun.file(q).exists())try{let Z=JSON.parse(await Bun.file(q).text());for(let[$,Q]of Object.entries(Z))this.cache.set($,Q)}catch{}this.loaded=!0}async save(){await O.promises.mkdir(this.cacheDir,{recursive:!0});let q=j.join(this.cacheDir,"build-cache.json"),Z={};for(let[$,Q]of this.cache)Z[$]=Q;await Bun.write(q,JSON.stringify(Z))}getHash(q,Z){let $=O.statSync(q),Q=`${this.salt}|${q}:${$.mtimeMs}`;for(let J of Z)if(O.existsSync(J)){let W=O.statSync(J);Q+=`:${J}:${W.mtimeMs}`}let K=0;for(let J=0;J<Q.length;J++){let W=Q.charCodeAt(J);K=(K<<5)-K+W,K=K&K}return Math.abs(K).toString(36)}get(q,Z){let $=this.cache.get(q);if(!$)return null;let Q=this.getHash(Z,$.dependencies??[]);if($.hash!==Q)return this.cache.delete(q),null;return $.html}set(q,Z,$,Q){let K=this.getHash(Z,$);this.cache.set(q,{hash:K,html:Q,generatedAt:Date.now(),dependencies:$})}}class Rq{cacheDir;constructor(q){this.cacheDir=q}getFilePath(q){let Z=q.replace(/[^a-zA-Z0-9]/g,"_")+".json";return j.join(this.cacheDir,"isr",Z)}async get(q){let Z=this.getFilePath(q);if(!await Bun.file(Z).exists())return null;try{return JSON.parse(await Bun.file(Z).text())}catch{return null}}async set(q,Z){let $=this.getFilePath(q);await O.promises.mkdir(j.dirname($),{recursive:!0}),await Bun.write($,JSON.stringify(Z))}async invalidate(q){let Z=this.getFilePath(q);if(await Bun.file(Z).exists())await O.promises.unlink(Z)}async invalidatePattern(q){let Z=j.join(this.cacheDir,"isr");if(!O.existsSync(Z))return;let $=await O.promises.readdir(Z);for(let Q of $){let K=Q.replace(/_/g,"/").replace(".json","");if(q.test(K))await O.promises.unlink(j.join(Z,Q))}}async needsRevalidation(q){let Z=await this.get(q);if(!Z)return!0;return Date.now()>Z.revalidateAfter}}async function pq(q){try{let $=(await Bun.file(q).text()).match(/<script\s+server\b[^>]*>([\s\S]*?)<\/script>/i);if(!$)return null;let Q=$[1];if(!Q.includes("getStaticPaths"))return null;let K=Q.match(/(?:export\s+)?(?:async\s+)?function\s+getStaticPaths\s*\([^)]*\)\s*\{([\s\S]*?)\n[\t ]*\}/);if(!K)return null;return await Function(`
4
4
  return (async function getStaticPaths() {
5
5
  ${K[1]}
@@ -41,7 +41,7 @@ ${q.map((Q)=>{let K=new URL(Q.loc,Z).href,J=` <url>
41
41
  ${$.join(`
42
42
  `)}
43
43
  </channel>
44
- </rss>`}async function aq(q={}){let Z=Date.now();Aq();let $=await n(process.cwd()),Q=$.build||{},K=await Kq({explicit:q.domain||Q.domain,stxConfig:$});if(K.source==="fallback"&&(q.sitemap??Q.sitemap??!0))console.warn(Wq());let J={router:q.router??$?.router??{},pagesDir:q.pagesDir||Q.pagesDir||"pages",outputDir:q.outputDir||Q.outputDir||"dist",baseUrl:q.baseUrl||Q.baseUrl||"/",domain:K.url,revalidate:q.revalidate??Q.revalidate??!1,sitemap:q.sitemap??Q.sitemap??!0,sitemapExclude:q.sitemapExclude||Q.sitemapExclude||[],robots:q.robots??Q.robots??!0,rss:q.rss??Q.rss??!1,concurrency:q.concurrency||Q.concurrency||10,cache:q.cache??Q.cache??!0,cacheDir:q.cacheDir||Q.cacheDir||d(process.cwd(),"ssg-cache"),contentLoaders:q.contentLoaders||[],hooks:q.hooks||{},minify:q.minify??Q.minify??!0,generate404:q.generate404??Q.generate404??!0,publicDir:q.publicDir||Q.publicDir||"public",trailingSlash:q.trailingSlash??Q.trailingSlash??!1,cleanOutput:q.cleanOutput??Q.cleanOutput??!0,cacheSalt:q.cacheSalt??"",partialsDir:q.partialsDir||$?.partialsDir||"",componentsDir:q.componentsDir||$?.componentsDir||"",failOnIncludeError:q.failOnIncludeError??!0,chunkIslands:q.chunkIslands===!0,prefetchIslands:q.prefetchIslands===!0,integrityIslands:q.integrityIslands===!0},W={totalPages:0,successCount:0,failedCount:0,cachedCount:0,buildTime:0,pages:[],errors:[]},z=new Set,A=0,G={},X={};await J.hooks.onBuildStart?.();try{if(J.cleanOutput&&O.existsSync(J.outputDir))await O.promises.rm(J.outputDir,{recursive:!0});await O.promises.mkdir(J.outputDir,{recursive:!0});let L=J.cacheSalt;if(!L){let{resolveUserCssConfig:Y,fingerprintConfig:N}=await import("./chunk-sntjqyqq.js");try{L=N(await Y(process.cwd()))}catch{}}let{prepareImageDelivery:_}=await import("./chunk-97ej3abh.js"),F=await _(J.publicDir,J.outputDir);if(F.count>0)console.log(`Optimized ${F.count} image(s) for responsive delivery`),L=[L,F.fingerprint].filter(Boolean).join(":");let C=new wq(J.cacheDir,L);if(J.cache)await C.load();try{let{generateCss:Y}=await import("./chunk-sntjqyqq.js");await Y('<div class="bg-black"></div>')}catch{}Yq();let kq=$?.routeMiddleware?.dir||"middleware";await jq(process.cwd(),kq);let p=Vq();if(p.length>0)console.log(`Loaded ${p.length} middleware: ${p.join(", ")}`);let bq=$?.routeMiddleware?.global||[];Fq($?.layoutsDir,d(process.cwd()));let i=l(".",{pagesDir:J.pagesDir,pagesDirs:$?._layerPageDirs});console.log(`Found ${i.length} routes`);let g=[];for(let Y of i){let N=await uq(Y);for(let H of N)g.push({route:Y,url:H.url,params:H.params,props:H.props,revalidate:H.revalidate??J.revalidate})}W.totalPages=g.length,console.log(`Generating ${W.totalPages} pages...`);let b=[],s=[];for(let Y=0;Y<g.length;Y+=J.concurrency)s.push(g.slice(Y,Y+J.concurrency));for(let Y of s)await Promise.all(Y.map(async(N)=>{let{route:H,url:V,params:S,props:Sq,revalidate:a}=N;try{await J.hooks.onPageStart?.(V);let w=await Bun.file(H.filePath).text(),R=JJ(w),vq=R?.middleware?Array.isArray(R.middleware)?R.middleware:[R.middleware]:[],t=[...bq,...vq];if(t.length>0){let D=Gq(V,S,R||{}),M=Lq(D,null,void 0,{isServer:!0}),E=await Nq(t,M);if(E.redirect){let U=typeof E.redirect==="string"?E.redirect:E.redirect.path||E.redirect,x=QJ(String(U),V),k=ZJ(V,J);await O.promises.mkdir(j.dirname(k),{recursive:!0}),await Bun.write(k,x),W.successCount++,W.pages.push({route:V,outputPath:k,size:Buffer.byteLength(x),cached:!1}),b.push({loc:V,lastmod:new Date().toISOString()}),console.log(` \u21B3 ${V} \u2192 redirect to ${U}`);return}if(E.abort){let U=typeof E.abort==="string"?E.abort:E.abort?.error?.message||"aborted";console.warn(` \u21B3 ${V} \u2014 middleware aborted: ${U}`);return}}let e=w.match(/definePageMeta\s*\(\s*\{[\s\S]*?validate\s*:\s*((?:\([^)]*\)|[^,}])*(?:\{[\s\S]*?\})?)/m);if(e)try{if(!await Function("route",`
44
+ </rss>`}async function aq(q={}){let Z=Date.now();Aq();let $=await n(process.cwd()),Q=$.build||{},K=await Kq({explicit:q.domain||Q.domain,stxConfig:$});if(K.source==="fallback"&&(q.sitemap??Q.sitemap??!0))console.warn(Wq());let J={router:q.router??$?.router??{},pagesDir:q.pagesDir||Q.pagesDir||"pages",outputDir:q.outputDir||Q.outputDir||"dist",baseUrl:q.baseUrl||Q.baseUrl||"/",domain:K.url,revalidate:q.revalidate??Q.revalidate??!1,sitemap:q.sitemap??Q.sitemap??!0,sitemapExclude:q.sitemapExclude||Q.sitemapExclude||[],robots:q.robots??Q.robots??!0,rss:q.rss??Q.rss??!1,concurrency:q.concurrency||Q.concurrency||10,cache:q.cache??Q.cache??!0,cacheDir:q.cacheDir||Q.cacheDir||d(process.cwd(),"ssg-cache"),contentLoaders:q.contentLoaders||[],hooks:q.hooks||{},minify:q.minify??Q.minify??!0,generate404:q.generate404??Q.generate404??!0,publicDir:q.publicDir||Q.publicDir||"public",trailingSlash:q.trailingSlash??Q.trailingSlash??!1,cleanOutput:q.cleanOutput??Q.cleanOutput??!0,cacheSalt:q.cacheSalt??"",partialsDir:q.partialsDir||$?.partialsDir||"",componentsDir:q.componentsDir||$?.componentsDir||"",failOnIncludeError:q.failOnIncludeError??!0,chunkIslands:q.chunkIslands===!0,prefetchIslands:q.prefetchIslands===!0,integrityIslands:q.integrityIslands===!0},W={totalPages:0,successCount:0,failedCount:0,cachedCount:0,buildTime:0,pages:[],errors:[]},z=new Set,A=0,G={},X={};await J.hooks.onBuildStart?.();try{if(J.cleanOutput&&O.existsSync(J.outputDir))await O.promises.rm(J.outputDir,{recursive:!0});await O.promises.mkdir(J.outputDir,{recursive:!0});let L=J.cacheSalt;if(!L){let{resolveUserCssConfig:Y,fingerprintConfig:N}=await import("./chunk-7xjm1zqa.js");try{L=N(await Y(process.cwd()))}catch{}}let{prepareImageDelivery:_}=await import("./chunk-97ej3abh.js"),F=await _(J.publicDir,J.outputDir);if(F.count>0)console.log(`Optimized ${F.count} image(s) for responsive delivery`),L=[L,F.fingerprint].filter(Boolean).join(":");let C=new wq(J.cacheDir,L);if(J.cache)await C.load();try{let{generateCss:Y}=await import("./chunk-7xjm1zqa.js");await Y('<div class="bg-black"></div>')}catch{}Yq();let kq=$?.routeMiddleware?.dir||"middleware";await jq(process.cwd(),kq);let p=Vq();if(p.length>0)console.log(`Loaded ${p.length} middleware: ${p.join(", ")}`);let bq=$?.routeMiddleware?.global||[];Fq($?.layoutsDir,d(process.cwd()));let i=l(".",{pagesDir:J.pagesDir,pagesDirs:$?._layerPageDirs});console.log(`Found ${i.length} routes`);let g=[];for(let Y of i){let N=await uq(Y);for(let H of N)g.push({route:Y,url:H.url,params:H.params,props:H.props,revalidate:H.revalidate??J.revalidate})}W.totalPages=g.length,console.log(`Generating ${W.totalPages} pages...`);let b=[],s=[];for(let Y=0;Y<g.length;Y+=J.concurrency)s.push(g.slice(Y,Y+J.concurrency));for(let Y of s)await Promise.all(Y.map(async(N)=>{let{route:H,url:V,params:S,props:Sq,revalidate:a}=N;try{await J.hooks.onPageStart?.(V);let w=await Bun.file(H.filePath).text(),R=JJ(w),vq=R?.middleware?Array.isArray(R.middleware)?R.middleware:[R.middleware]:[],t=[...bq,...vq];if(t.length>0){let D=Gq(V,S,R||{}),M=Lq(D,null,void 0,{isServer:!0}),E=await Nq(t,M);if(E.redirect){let U=typeof E.redirect==="string"?E.redirect:E.redirect.path||E.redirect,x=QJ(String(U),V),k=ZJ(V,J);await O.promises.mkdir(j.dirname(k),{recursive:!0}),await Bun.write(k,x),W.successCount++,W.pages.push({route:V,outputPath:k,size:Buffer.byteLength(x),cached:!1}),b.push({loc:V,lastmod:new Date().toISOString()}),console.log(` \u21B3 ${V} \u2192 redirect to ${U}`);return}if(E.abort){let U=typeof E.abort==="string"?E.abort:E.abort?.error?.message||"aborted";console.warn(` \u21B3 ${V} \u2014 middleware aborted: ${U}`);return}}let e=w.match(/definePageMeta\s*\(\s*\{[\s\S]*?validate\s*:\s*((?:\([^)]*\)|[^,}])*(?:\{[\s\S]*?\})?)/m);if(e)try{if(!await Function("route",`
45
45
  const validate = ${e[1]};
46
46
  return validate(route);
47
47
  `)({params:S})){console.warn(`Validation failed for ${V}, skipping...`);return}}catch{}let I=null,qq=!1;if(J.cache){if(I=C.get(V,H.filePath),I)qq=!0,W.cachedCount++}if(!I){let D=await Mq(H,S,Sq,J,$);if(I=D.html,J.failOnIncludeError&&D.includeFailures.length>0)throw Error(`${D.includeFailures.length} include(s) failed to resolve, so this page would have shipped with an error banner where the markup should be:
@@ -0,0 +1,67 @@
1
+ /**
2
+ * A substituted value cannot introduce a URL scheme the page did not ask for.
3
+ *
4
+ * `{{ }}` escapes HTML, which stops a value closing its attribute or opening a
5
+ * tag -- and says nothing about what the attribute means. `javascript:alert(1)`
6
+ * contains no character escaping touches, so
7
+ * `<a href="{{ url }}">` with a stored URL ran script on click, from a value a
8
+ * person typed. The same goes for `vbscript:` (old Edge, still shipped in
9
+ * Windows-embedded browsers) and for `data:text/html`, which navigates to a
10
+ * document the attacker wrote, on the page's own origin in some browsers.
11
+ *
12
+ * What is *not* here matters as much:
13
+ *
14
+ * - **Only the scheme position.** `href="/search?q={{ term }}"` is left alone:
15
+ * a scheme can only be at the start of a URL, so a value substituted after
16
+ * anything else cannot introduce one.
17
+ * - **Only URL attributes.** `title="{{ text }}"` keeps every byte. A value is
18
+ * rewritten only where the browser would resolve it as a URL.
19
+ * - **`{!! !!}` is untouched**, as it is for HTML: the raw form is how a
20
+ * template says it vouches for the value, and a sanitizer with no way out is
21
+ * one people work around.
22
+ * - **`data:image/...` still loads**, which `<StxImage>`'s own blur placeholder
23
+ * needs; only the data URLs that carry a document are refused.
24
+ *
25
+ * Refused values are prefixed rather than emptied, the way Angular does it: the
26
+ * value stays visible in the DOM and in view-source, so the person debugging it
27
+ * sees what was blocked instead of an attribute that mysteriously went away,
28
+ * and `unsafe:javascript:alert(1)` is an unknown scheme, so a click does
29
+ * nothing.
30
+ */
31
+ /** What a refused value is rewritten to, so it is inert but still readable. */
32
+ export declare const UNSAFE_URL_PREFIX = "unsafe:";
33
+ /** Which policy an attribute name gets, or null when it holds no URL. */
34
+ export declare function urlAttributeKind(name: string): 'navigable' | 'fetched' | null;
35
+ /** Whether this value would introduce a scheme the attribute must not follow. */
36
+ export declare function isDangerousUrl(value: string, kind?: 'navigable' | 'fetched'): boolean;
37
+ /** The value as it may be written, refused ones prefixed to make them inert. */
38
+ export declare function sanitizeUrlValue(value: string, kind?: 'navigable' | 'fetched'): string;
39
+ /**
40
+ * The attribute a substitution at the end of `before` is landing in the scheme
41
+ * position of, or null.
42
+ *
43
+ * `before` is everything already written, so its tail says where the next
44
+ * characters go: inside an open quoted attribute value, with nothing before
45
+ * them in that value. Anything already written into the value means the
46
+ * substitution cannot be the scheme, and an unquoted value is not matched --
47
+ * the interpolation passes write quoted attributes.
48
+ */
49
+ export declare function urlSchemePosition(before: string): 'navigable' | 'fetched' | null;
50
+ /**
51
+ * The tail of the already-written output that a scheme check needs.
52
+ *
53
+ * A page can hold hundreds of substitutions, and slicing the whole output at
54
+ * each one is quadratic -- one of the compile budgets in
55
+ * scripts/performance-budgets.ts is what would notice. Only the enclosing tag's
56
+ * last attribute matters, so a window is enough.
57
+ */
58
+ export declare function schemeWindow(output: string, offset: number, span?: number): string;
59
+ /**
60
+ * Decide the final text for a value that was just substituted: `escaped` as it
61
+ * is, or prefixed when it landed in the scheme position of a URL attribute and
62
+ * introduces a scheme that attribute must not follow.
63
+ *
64
+ * The decision reads the raw value, not the escaped one, so it is testing what
65
+ * the browser will resolve rather than what the HTML escape happened to leave.
66
+ */
67
+ export declare function sanitizeSubstitutedUrl(before: string, raw: string, escaped?: string): string;
@@ -0,0 +1,40 @@
1
+ export const UNSAFE_URL_PREFIX = "unsafe:";
2
+ const NAVIGABLE = "href|xlink:href|action|formaction|ping|cite|manifest|data|to", FETCHED = "src|srcset|poster|background", SCRIPT_SCHEME = /^(?:javascript|vbscript|livescript|mocha|view-source):/, DOCUMENT_DATA = /^data:(?:text\/html|text\/xml|application\/xhtml\+xml|application\/xml|image\/svg\+xml)/, FETCHED_DATA = /^data:(?:text\/html|application\/xhtml\+xml)/, STRIPPED = new RegExp("[\\u0000-\\u0020\\u00A0\\u1680\\u2000-\\u200F\\u2028\\u2029\\u202F\\u205F\\u3000\\uFEFF]+", "g");
3
+ function normalizeScheme(value) {
4
+ return value.replace(STRIPPED, "").toLowerCase();
5
+ }
6
+ export function urlAttributeKind(name) {
7
+ const lower = name.toLowerCase();
8
+ if (new RegExp(`^(?:${NAVIGABLE})$`).test(lower))
9
+ return "navigable";
10
+ if (new RegExp(`^(?:${FETCHED})$`).test(lower))
11
+ return "fetched";
12
+ return null;
13
+ }
14
+ export function isDangerousUrl(value, kind = "navigable") {
15
+ const normalized = normalizeScheme(value);
16
+ if (SCRIPT_SCHEME.test(normalized))
17
+ return !0;
18
+ return kind === "navigable" ? DOCUMENT_DATA.test(normalized) : FETCHED_DATA.test(normalized);
19
+ }
20
+ export function sanitizeUrlValue(value, kind = "navigable") {
21
+ return isDangerousUrl(value, kind) ? UNSAFE_URL_PREFIX + value : value;
22
+ }
23
+ export function urlSchemePosition(before) {
24
+ const open = before.match(/(?:^|[\s"'`/])([A-Za-z][\w:-]*)\s*=\s*(["'])([^"']*)$/);
25
+ if (!open)
26
+ return null;
27
+ if (open[3].trim() !== "")
28
+ return null;
29
+ return urlAttributeKind(open[1]);
30
+ }
31
+ export function schemeWindow(output, offset, span = 256) {
32
+ if (offset <= span)
33
+ return output.slice(0, offset);
34
+ const window = output.slice(offset - span, offset), boundary = window.search(/[\s"'`/]/);
35
+ return boundary === -1 ? "" : window.slice(boundary);
36
+ }
37
+ export function sanitizeSubstitutedUrl(before, raw, escaped = raw) {
38
+ const kind = urlSchemePosition(before);
39
+ return kind && isDangerousUrl(raw, kind) ? UNSAFE_URL_PREFIX + escaped : escaped;
40
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/stx",
3
3
  "type": "module",
4
- "version": "0.2.339",
4
+ "version": "0.2.340",
5
5
  "description": "A performant UI Framework. Powered by Bun.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",
@@ -117,12 +117,12 @@
117
117
  "dependencies": {
118
118
  "@stacksjs/ts-css": "^0.3.2",
119
119
  "@stacksjs/clapp": "^0.2.16",
120
- "@stacksjs/desktop": "0.2.339",
121
- "@stacksjs/sanitizer": "0.2.339",
120
+ "@stacksjs/desktop": "0.2.340",
121
+ "@stacksjs/sanitizer": "0.2.340",
122
122
  "@stacksjs/ts-i18n": "^0.1.11",
123
- "bun-plugin-stx": "0.2.339",
123
+ "bun-plugin-stx": "0.2.340",
124
124
  "bunfig": "^0.15.19",
125
- "stx-router": "0.2.339",
125
+ "stx-router": "0.2.340",
126
126
  "ts-broadcasting": "^0.0.10",
127
127
  "ts-images": "^0.2.22",
128
128
  "ts-syntax-highlighter": "^0.2.1"