From d20cb3189d2169ecc7b52c078bfe813da4de96c1 Mon Sep 17 00:00:00 2001 From: ahuseyn Date: Wed, 13 Aug 2025 14:35:35 +0200 Subject: [PATCH 01/24] init preview example --- examples/nextjs/previews/.gitignore | 43 + examples/nextjs/previews/eslint.config.mjs | 20 + examples/nextjs/previews/jsconfig.json | 7 + examples/nextjs/previews/next.config.mjs | 6 + examples/nextjs/previews/package.json | 27 + examples/nextjs/previews/postcss.config.mjs | 5 + examples/nextjs/previews/public/favicon.ico | Bin 0 -> 25931 bytes .../previews/src/components/BlogPostItem.js | 32 + .../nextjs/previews/src/components/Header.js | 20 + .../nextjs/previews/src/components/Layout.js | 18 + .../previews/src/components/PreviewButton.js | 17 + .../previews/src/pages/[...identifier].js | 79 + examples/nextjs/previews/src/pages/_app.js | 10 + .../nextjs/previews/src/pages/_document.js | 13 + .../previews/src/pages/api/disable-preview.js | 7 + .../nextjs/previews/src/pages/api/preview.js | 53 + examples/nextjs/previews/src/pages/index.js | 111 ++ .../nextjs/previews/src/styles/globals.css | 137 ++ .../previews/src/utils/getAuthString.js | 5 + .../previews/src/wp-templates/archive.js | 38 + .../src/wp-templates/index-template.js | 33 + .../nextjs/previews/src/wp-templates/index.js | 19 + .../nextjs/previews/src/wp-templates/page.js | 24 + .../previews/src/wp-templates/single.js | 36 + pnpm-lock.yaml | 1330 ++++++++++++++++- 25 files changed, 2070 insertions(+), 20 deletions(-) create mode 100644 examples/nextjs/previews/.gitignore create mode 100644 examples/nextjs/previews/eslint.config.mjs create mode 100644 examples/nextjs/previews/jsconfig.json create mode 100644 examples/nextjs/previews/next.config.mjs create mode 100644 examples/nextjs/previews/package.json create mode 100644 examples/nextjs/previews/postcss.config.mjs create mode 100644 examples/nextjs/previews/public/favicon.ico create mode 100644 examples/nextjs/previews/src/components/BlogPostItem.js create mode 100644 examples/nextjs/previews/src/components/Header.js create mode 100644 examples/nextjs/previews/src/components/Layout.js create mode 100644 examples/nextjs/previews/src/components/PreviewButton.js create mode 100644 examples/nextjs/previews/src/pages/[...identifier].js create mode 100644 examples/nextjs/previews/src/pages/_app.js create mode 100644 examples/nextjs/previews/src/pages/_document.js create mode 100644 examples/nextjs/previews/src/pages/api/disable-preview.js create mode 100644 examples/nextjs/previews/src/pages/api/preview.js create mode 100644 examples/nextjs/previews/src/pages/index.js create mode 100644 examples/nextjs/previews/src/styles/globals.css create mode 100644 examples/nextjs/previews/src/utils/getAuthString.js create mode 100644 examples/nextjs/previews/src/wp-templates/archive.js create mode 100644 examples/nextjs/previews/src/wp-templates/index-template.js create mode 100644 examples/nextjs/previews/src/wp-templates/index.js create mode 100644 examples/nextjs/previews/src/wp-templates/page.js create mode 100644 examples/nextjs/previews/src/wp-templates/single.js diff --git a/examples/nextjs/previews/.gitignore b/examples/nextjs/previews/.gitignore new file mode 100644 index 000000000..f64000977 --- /dev/null +++ b/examples/nextjs/previews/.gitignore @@ -0,0 +1,43 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +package-lock.json \ No newline at end of file diff --git a/examples/nextjs/previews/eslint.config.mjs b/examples/nextjs/previews/eslint.config.mjs new file mode 100644 index 000000000..5165cec43 --- /dev/null +++ b/examples/nextjs/previews/eslint.config.mjs @@ -0,0 +1,20 @@ +import { FlatCompat } from "@eslint/eslintrc"; +import js from "@eslint/js"; +import { dirname } from "path"; +import { fileURLToPath } from "url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, +}); + +const eslintConfig = [ + ...compat.config({ + extends: ["eslint:recommended", "next/core-web-vitals"], + }), +]; + +export default eslintConfig; diff --git a/examples/nextjs/previews/jsconfig.json b/examples/nextjs/previews/jsconfig.json new file mode 100644 index 000000000..b8d6842d7 --- /dev/null +++ b/examples/nextjs/previews/jsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/examples/nextjs/previews/next.config.mjs b/examples/nextjs/previews/next.config.mjs new file mode 100644 index 000000000..d5456a15d --- /dev/null +++ b/examples/nextjs/previews/next.config.mjs @@ -0,0 +1,6 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + reactStrictMode: true, +}; + +export default nextConfig; diff --git a/examples/nextjs/previews/package.json b/examples/nextjs/previews/package.json new file mode 100644 index 000000000..ea7058a46 --- /dev/null +++ b/examples/nextjs/previews/package.json @@ -0,0 +1,27 @@ +{ + "name": "@faustjs/nextjs-preview-example", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@faustjs/nextjs": "workspace:*", + "@faustjs/template-hierarchy": "workspace:*", + "graphql": "^16.11.0", + "graphql-tag": "^2.12.6", + "next": "15.2.4", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@eslint/eslintrc": "^3", + "@tailwindcss/postcss": "^4", + "eslint": "^9", + "eslint-config-next": "15.2.4", + "tailwindcss": "^4" + } +} diff --git a/examples/nextjs/previews/postcss.config.mjs b/examples/nextjs/previews/postcss.config.mjs new file mode 100644 index 000000000..c7bcb4b1e --- /dev/null +++ b/examples/nextjs/previews/postcss.config.mjs @@ -0,0 +1,5 @@ +const config = { + plugins: ["@tailwindcss/postcss"], +}; + +export default config; diff --git a/examples/nextjs/previews/public/favicon.ico b/examples/nextjs/previews/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..718d6fea4835ec2d246af9800eddb7ffb276240c GIT binary patch literal 25931 zcmeHv30#a{`}aL_*G&7qml|y<+KVaDM2m#dVr!KsA!#An?kSQM(q<_dDNCpjEux83 zLb9Z^XxbDl(w>%i@8hT6>)&Gu{h#Oeyszu?xtw#Zb1mO{pgX9699l+Qppw7jXaYf~-84xW z)w4x8?=youko|}Vr~(D$UXIbiXABHh`p1?nn8Po~fxRJv}|0e(BPs|G`(TT%kKVJAdg5*Z|x0leQq0 zkdUBvb#>9F()jo|T~kx@OM8$9wzs~t2l;K=woNssA3l6|sx2r3+kdfVW@e^8e*E}v zA1y5{bRi+3Z`uD3{F7LgFJDdvm;nJilkzDku>BwXH(8ItVCXk*-lSJnR?-2UN%hJ){&rlvg`CDTj z)Bzo!3v7Ou#83zEDEFcKt(f1E0~=rqeEbTnMvWR#{+9pg%7G8y>u1OVRUSoox-ovF z2Ydma(;=YuBY(eI|04{hXzZD6_f(v~H;C~y5=DhAC{MMS>2fm~1H_t2$56pc$NH8( z5bH|<)71dV-_oCHIrzrT`2s-5w_+2CM0$95I6X8p^r!gHp+j_gd;9O<1~CEQQGS8) zS9Qh3#p&JM-G8rHekNmKVewU;pJRcTAog68KYo^dRo}(M>36U4Us zfgYWSiHZL3;lpWT=zNAW>Dh#mB!_@Lg%$ms8N-;aPqMn+C2HqZgz&9~Eu z4|Kp<`$q)Uw1R?y(~S>ePdonHxpV1#eSP1B;Ogo+-Pk}6#0GsZZ5!||ev2MGdh}_m z{DeR7?0-1^zVs&`AV6Vt;r3`I`OI_wgs*w=eO%_#7Kepl{B@xiyCANc(l zzIyd4y|c6PXWq9-|KM8(zIk8LPk(>a)zyFWjhT!$HJ$qX1vo@d25W<fvZQ2zUz5WRc(UnFMKHwe1| zWmlB1qdbiA(C0jmnV<}GfbKtmcu^2*P^O?MBLZKt|As~ge8&AAO~2K@zbXelK|4T<{|y4`raF{=72kC2Kn(L4YyenWgrPiv z@^mr$t{#X5VuIMeL!7Ab6_kG$&#&5p*Z{+?5U|TZ`B!7llpVmp@skYz&n^8QfPJzL z0G6K_OJM9x+Wu2gfN45phANGt{7=C>i34CV{Xqlx(fWpeAoj^N0Biu`w+MVcCUyU* zDZuzO0>4Z6fbu^T_arWW5n!E45vX8N=bxTVeFoep_G#VmNlQzAI_KTIc{6>c+04vr zx@W}zE5JNSU>!THJ{J=cqjz+4{L4A{Ob9$ZJ*S1?Ggg3klFp!+Y1@K+pK1DqI|_gq z5ZDXVpge8-cs!o|;K73#YXZ3AShj50wBvuq3NTOZ`M&qtjj#GOFfgExjg8Gn8>Vq5 z`85n+9|!iLCZF5$HJ$Iu($dm?8~-ofu}tEc+-pyke=3!im#6pk_Wo8IA|fJwD&~~F zc16osQ)EBo58U7XDuMexaPRjU@h8tXe%S{fA0NH3vGJFhuyyO!Uyl2^&EOpX{9As0 zWj+P>{@}jxH)8|r;2HdupP!vie{sJ28b&bo!8`D^x}TE$%zXNb^X1p@0PJ86`dZyj z%ce7*{^oo+6%&~I!8hQy-vQ7E)0t0ybH4l%KltWOo~8cO`T=157JqL(oq_rC%ea&4 z2NcTJe-HgFjNg-gZ$6!Y`SMHrlj}Etf7?r!zQTPPSv}{so2e>Fjs1{gzk~LGeesX%r(Lh6rbhSo_n)@@G-FTQy93;l#E)hgP@d_SGvyCp0~o(Y;Ee8{ zdVUDbHm5`2taPUOY^MAGOw*>=s7=Gst=D+p+2yON!0%Hk` zz5mAhyT4lS*T3LS^WSxUy86q&GnoHxzQ6vm8)VS}_zuqG?+3td68_x;etQAdu@sc6 zQJ&5|4(I?~3d-QOAODHpZ=hlSg(lBZ!JZWCtHHSj`0Wh93-Uk)_S%zsJ~aD>{`A0~ z9{AG(e|q3g5B%wYKRxiL2Y$8(4w6bzchKuloQW#e&S3n+P- z8!ds-%f;TJ1>)v)##>gd{PdS2Oc3VaR`fr=`O8QIO(6(N!A?pr5C#6fc~Ge@N%Vvu zaoAX2&(a6eWy_q&UwOhU)|P3J0Qc%OdhzW=F4D|pt0E4osw;%<%Dn58hAWD^XnZD= z>9~H(3bmLtxpF?a7su6J7M*x1By7YSUbxGi)Ot0P77`}P3{)&5Un{KD?`-e?r21!4vTTnN(4Y6Lin?UkSM z`MXCTC1@4A4~mvz%Rh2&EwY))LeoT=*`tMoqcEXI>TZU9WTP#l?uFv+@Dn~b(>xh2 z;>B?;Tz2SR&KVb>vGiBSB`@U7VIWFSo=LDSb9F{GF^DbmWAfpms8Sx9OX4CnBJca3 zlj9(x!dIjN?OG1X4l*imJNvRCk}F%!?SOfiOq5y^mZW)jFL@a|r-@d#f7 z2gmU8L3IZq0ynIws=}~m^#@&C%J6QFo~Mo4V`>v7MI-_!EBMMtb%_M&kvAaN)@ZVw z+`toz&WG#HkWDjnZE!6nk{e-oFdL^$YnbOCN}JC&{$#$O27@|Tn-skXr)2ml2~O!5 zX+gYoxhoc7qoU?C^3~&!U?kRFtnSEecWuH0B0OvLodgUAi}8p1 zrO6RSXHH}DMc$&|?D004DiOVMHV8kXCP@7NKB zgaZq^^O<7PoKEp72kby@W0Z!Y*Ay{&vfg#C&gG@YVR9g?FEocMUi1gSN$+V+ayF45{a zuDZDTN}mS|;BO%gEf}pjBfN2-gIrU#G5~cucA;dokXW89%>AyXJJI z9X4UlIWA|ZYHgbI z5?oFk@A=Ik7lrEQPDH!H+b`7_Y~aDb_qa=B2^Y&Ow41cU=4WDd40dp5(QS-WMN-=Y z9g;6_-JdNU;|6cPwf$ak*aJIcwL@1n$#l~zi{c{EW?T;DaW*E8DYq?Umtz{nJ&w-M zEMyTDrC&9K$d|kZe2#ws6)L=7K+{ zQw{XnV6UC$6-rW0emqm8wJoeZK)wJIcV?dST}Z;G0Arq{dVDu0&4kd%N!3F1*;*pW zR&qUiFzK=@44#QGw7k1`3t_d8&*kBV->O##t|tonFc2YWrL7_eqg+=+k;!F-`^b8> z#KWCE8%u4k@EprxqiV$VmmtiWxDLgnGu$Vs<8rppV5EajBXL4nyyZM$SWVm!wnCj-B!Wjqj5-5dNXukI2$$|Bu3Lrw}z65Lc=1G z^-#WuQOj$hwNGG?*CM_TO8Bg-1+qc>J7k5c51U8g?ZU5n?HYor;~JIjoWH-G>AoUP ztrWWLbRNqIjW#RT*WqZgPJXU7C)VaW5}MiijYbABmzoru6EmQ*N8cVK7a3|aOB#O& zBl8JY2WKfmj;h#Q!pN%9o@VNLv{OUL?rixHwOZuvX7{IJ{(EdPpuVFoQqIOa7giLVkBOKL@^smUA!tZ1CKRK}#SSM)iQHk)*R~?M!qkCruaS!#oIL1c z?J;U~&FfH#*98^G?i}pA{ z9Jg36t4=%6mhY(quYq*vSxptes9qy|7xSlH?G=S@>u>Ebe;|LVhs~@+06N<4CViBk zUiY$thvX;>Tby6z9Y1edAMQaiH zm^r3v#$Q#2T=X>bsY#D%s!bhs^M9PMAcHbCc0FMHV{u-dwlL;a1eJ63v5U*?Q_8JO zT#50!RD619#j_Uf))0ooADz~*9&lN!bBDRUgE>Vud-i5ck%vT=r^yD*^?Mp@Q^v+V zG#-?gKlr}Eeqifb{|So?HM&g91P8|av8hQoCmQXkd?7wIJwb z_^v8bbg`SAn{I*4bH$u(RZ6*xUhuA~hc=8czK8SHEKTzSxgbwi~9(OqJB&gwb^l4+m`k*Q;_?>Y-APi1{k zAHQ)P)G)f|AyjSgcCFps)Fh6Bca*Xznq36!pV6Az&m{O8$wGFD? zY&O*3*J0;_EqM#jh6^gMQKpXV?#1?>$ml1xvh8nSN>-?H=V;nJIwB07YX$e6vLxH( zqYwQ>qxwR(i4f)DLd)-$P>T-no_c!LsN@)8`e;W@)-Hj0>nJ-}Kla4-ZdPJzI&Mce zv)V_j;(3ERN3_@I$N<^|4Lf`B;8n+bX@bHbcZTopEmDI*Jfl)-pFDvo6svPRoo@(x z);_{lY<;);XzT`dBFpRmGrr}z5u1=pC^S-{ce6iXQlLGcItwJ^mZx{m$&DA_oEZ)B{_bYPq-HA zcH8WGoBG(aBU_j)vEy+_71T34@4dmSg!|M8Vf92Zj6WH7Q7t#OHQqWgFE3ARt+%!T z?oLovLVlnf?2c7pTc)~cc^($_8nyKwsN`RA-23ed3sdj(ys%pjjM+9JrctL;dy8a( z@en&CQmnV(()bu|Y%G1-4a(6x{aLytn$T-;(&{QIJB9vMox11U-1HpD@d(QkaJdEb zG{)+6Dos_L+O3NpWo^=gR?evp|CqEG?L&Ut#D*KLaRFOgOEK(Kq1@!EGcTfo+%A&I z=dLbB+d$u{sh?u)xP{PF8L%;YPPW53+@{>5W=Jt#wQpN;0_HYdw1{ksf_XhO4#2F= zyPx6Lx2<92L-;L5PD`zn6zwIH`Jk($?Qw({erA$^bC;q33hv!d!>%wRhj# zal^hk+WGNg;rJtb-EB(?czvOM=H7dl=vblBwAv>}%1@{}mnpUznfq1cE^sgsL0*4I zJ##!*B?=vI_OEVis5o+_IwMIRrpQyT_Sq~ZU%oY7c5JMIADzpD!Upz9h@iWg_>>~j zOLS;wp^i$-E?4<_cp?RiS%Rd?i;f*mOz=~(&3lo<=@(nR!_Rqiprh@weZlL!t#NCc zO!QTcInq|%#>OVgobj{~ixEUec`E25zJ~*DofsQdzIa@5^nOXj2T;8O`l--(QyU^$t?TGY^7#&FQ+2SS3B#qK*k3`ye?8jUYSajE5iBbJls75CCc(m3dk{t?- zopcER9{Z?TC)mk~gpi^kbbu>b-+a{m#8-y2^p$ka4n60w;Sc2}HMf<8JUvhCL0B&Btk)T`ctE$*qNW8L$`7!r^9T+>=<=2qaq-;ll2{`{Rg zc5a0ZUI$oG&j-qVOuKa=*v4aY#IsoM+1|c4Z)<}lEDvy;5huB@1RJPquU2U*U-;gu z=En2m+qjBzR#DEJDO`WU)hdd{Vj%^0V*KoyZ|5lzV87&g_j~NCjwv0uQVqXOb*QrQ zy|Qn`hxx(58c70$E;L(X0uZZ72M1!6oeg)(cdKO ze0gDaTz+ohR-#d)NbAH4x{I(21yjwvBQfmpLu$)|m{XolbgF!pmsqJ#D}(ylp6uC> z{bqtcI#hT#HW=wl7>p!38sKsJ`r8}lt-q%Keqy%u(xk=yiIJiUw6|5IvkS+#?JTBl z8H5(Q?l#wzazujH!8o>1xtn8#_w+397*_cy8!pQGP%K(Ga3pAjsaTbbXJlQF_+m+-UpUUent@xM zg%jqLUExj~o^vQ3Gl*>wh=_gOr2*|U64_iXb+-111aH}$TjeajM+I20xw(((>fej-@CIz4S1pi$(#}P7`4({6QS2CaQS4NPENDp>sAqD z$bH4KGzXGffkJ7R>V>)>tC)uax{UsN*dbeNC*v}#8Y#OWYwL4t$ePR?VTyIs!wea+ z5Urmc)X|^`MG~*dS6pGSbU+gPJoq*^a=_>$n4|P^w$sMBBy@f*Z^Jg6?n5?oId6f{ z$LW4M|4m502z0t7g<#Bx%X;9<=)smFolV&(V^(7Cv2-sxbxopQ!)*#ZRhTBpx1)Fc zNm1T%bONzv6@#|dz(w02AH8OXe>kQ#1FMCzO}2J_mST)+ExmBr9cva-@?;wnmWMOk z{3_~EX_xadgJGv&H@zK_8{(x84`}+c?oSBX*Ge3VdfTt&F}yCpFP?CpW+BE^cWY0^ zb&uBN!Ja3UzYHK-CTyA5=L zEMW{l3Usky#ly=7px648W31UNV@K)&Ub&zP1c7%)`{);I4b0Q<)B}3;NMG2JH=X$U zfIW4)4n9ZM`-yRj67I)YSLDK)qfUJ_ij}a#aZN~9EXrh8eZY2&=uY%2N0UFF7<~%M zsB8=erOWZ>Ct_#^tHZ|*q`H;A)5;ycw*IcmVxi8_0Xk}aJA^ath+E;xg!x+As(M#0=)3!NJR6H&9+zd#iP(m0PIW8$ z1Y^VX`>jm`W!=WpF*{ioM?C9`yOR>@0q=u7o>BP-eSHqCgMDj!2anwH?s%i2p+Q7D zzszIf5XJpE)IG4;d_(La-xenmF(tgAxK`Y4sQ}BSJEPs6N_U2vI{8=0C_F?@7<(G; zo$~G=8p+076G;`}>{MQ>t>7cm=zGtfbdDXm6||jUU|?X?CaE?(<6bKDYKeHlz}DA8 zXT={X=yp_R;HfJ9h%?eWvQ!dRgz&Su*JfNt!Wu>|XfU&68iRikRrHRW|ZxzRR^`eIGt zIeiDgVS>IeExKVRWW8-=A=yA`}`)ZkWBrZD`hpWIxBGkh&f#ijr449~m`j6{4jiJ*C!oVA8ZC?$1RM#K(_b zL9TW)kN*Y4%^-qPpMP7d4)o?Nk#>aoYHT(*g)qmRUb?**F@pnNiy6Fv9rEiUqD(^O zzyS?nBrX63BTRYduaG(0VVG2yJRe%o&rVrLjbxTaAFTd8s;<<@Qs>u(<193R8>}2_ zuwp{7;H2a*X7_jryzriZXMg?bTuegABb^87@SsKkr2)0Gyiax8KQWstw^v#ix45EVrcEhr>!NMhprl$InQMzjSFH54x5k9qHc`@9uKQzvL4ihcq{^B zPrVR=o_ic%Y>6&rMN)hTZsI7I<3&`#(nl+3y3ys9A~&^=4?PL&nd8)`OfG#n zwAMN$1&>K++c{^|7<4P=2y(B{jJsQ0a#U;HTo4ZmWZYvI{+s;Td{Yzem%0*k#)vjpB zia;J&>}ICate44SFYY3vEelqStQWFihx%^vQ@Do(sOy7yR2@WNv7Y9I^yL=nZr3mb zXKV5t@=?-Sk|b{XMhA7ZGB@2hqsx}4xwCW!in#C zI@}scZlr3-NFJ@NFaJlhyfcw{k^vvtGl`N9xSo**rDW4S}i zM9{fMPWo%4wYDG~BZ18BD+}h|GQKc-g^{++3MY>}W_uq7jGHx{mwE9fZiPCoxN$+7 zrODGGJrOkcPQUB(FD5aoS4g~7#6NR^ma7-!>mHuJfY5kTe6PpNNKC9GGRiu^L31uG z$7v`*JknQHsYB!Tm_W{a32TM099djW%5e+j0Ve_ct}IM>XLF1Ap+YvcrLV=|CKo6S zb+9Nl3_YdKP6%Cxy@6TxZ>;4&nTneadr z_ES90ydCev)LV!dN=#(*f}|ZORFdvkYBni^aLbUk>BajeWIOcmHP#8S)*2U~QKI%S zyrLmtPqb&TphJ;>yAxri#;{uyk`JJqODDw%(Z=2`1uc}br^V%>j!gS)D*q*f_-qf8&D;W1dJgQMlaH5er zN2U<%Smb7==vE}dDI8K7cKz!vs^73o9f>2sgiTzWcwY|BMYHH5%Vn7#kiw&eItCqa zIkR2~Q}>X=Ar8W|^Ms41Fm8o6IB2_j60eOeBB1Br!boW7JnoeX6Gs)?7rW0^5psc- zjS16yb>dFn>KPOF;imD}e!enuIniFzv}n$m2#gCCv4jM#ArwlzZ$7@9&XkFxZ4n!V zj3dyiwW4Ki2QG{@i>yuZXQizw_OkZI^-3otXC{!(lUpJF33gI60ak;Uqitp74|B6I zgg{b=Iz}WkhCGj1M=hu4#Aw173YxIVbISaoc z-nLZC*6Tgivd5V`K%GxhBsp@SUU60-rfc$=wb>zdJzXS&-5(NRRodFk;Kxk!S(O(a0e7oY=E( zAyS;Ow?6Q&XA+cnkCb{28_1N8H#?J!*$MmIwLq^*T_9-z^&UE@A(z9oGYtFy6EZef LrJugUA?W`A8`#=m literal 0 HcmV?d00001 diff --git a/examples/nextjs/previews/src/components/BlogPostItem.js b/examples/nextjs/previews/src/components/BlogPostItem.js new file mode 100644 index 000000000..58740f7f0 --- /dev/null +++ b/examples/nextjs/previews/src/components/BlogPostItem.js @@ -0,0 +1,32 @@ +import Link from "next/link"; + +export function BlogPostItem({ post }) { + const { title, date, excerpt, uri, featuredImage } = post ?? {}; + + return ( +
+ + + {featuredImage && ( + + )} + +

+ + {title} + +

+ +
+ + + Read more + +
+ ); +} diff --git a/examples/nextjs/previews/src/components/Header.js b/examples/nextjs/previews/src/components/Header.js new file mode 100644 index 000000000..d811ca3eb --- /dev/null +++ b/examples/nextjs/previews/src/components/Header.js @@ -0,0 +1,20 @@ +/* eslint-disable @next/next/no-html-link-for-pages */ +import Link from 'next/link'; + +export default function Header() { + return ( +
+
+
+ Headless +
+ + +
+
+ ); +} diff --git a/examples/nextjs/previews/src/components/Layout.js b/examples/nextjs/previews/src/components/Layout.js new file mode 100644 index 000000000..86172c15b --- /dev/null +++ b/examples/nextjs/previews/src/components/Layout.js @@ -0,0 +1,18 @@ +import { useRouter } from 'next/router'; +import Header from './Header'; +import PreviewButton from './PreviewButton'; + +export default function Layout({ children }) { + const router = useRouter(); + + return ( + <> +
+
+ {children} + + {router.isPreview && } +
+ + ); +} diff --git a/examples/nextjs/previews/src/components/PreviewButton.js b/examples/nextjs/previews/src/components/PreviewButton.js new file mode 100644 index 000000000..3c8a2122f --- /dev/null +++ b/examples/nextjs/previews/src/components/PreviewButton.js @@ -0,0 +1,17 @@ +import { useRouter } from "next/router"; + +export default function PreviewButton() { + const router = useRouter(); + + return ( +
+ Preview mode is on + + +
+ ); +} diff --git a/examples/nextjs/previews/src/pages/[...identifier].js b/examples/nextjs/previews/src/pages/[...identifier].js new file mode 100644 index 000000000..76dd4e5a4 --- /dev/null +++ b/examples/nextjs/previews/src/pages/[...identifier].js @@ -0,0 +1,79 @@ +import { getAuthString } from '@/utils/getAuthString'; +import availableTemplates from '@/wp-templates'; +import { + createDefaultClient, + setGraphQLClient, + uriToTemplate, +} from '@faustjs/nextjs/pages'; + +export default function Page(props) { + const { templateData } = props; + + const PageTemplate = availableTemplates[templateData?.template?.id]; + + return ; +} + +// Statically generate the pages, except for draft mode +// More info: https://nextjs.org/docs/pages/guides/draft-mode +export async function getStaticProps({ + params, + draftMode: isDraftModeEnabled, +}) { + // Send the authentication string only if draft mode is enabled + const headers = isDraftModeEnabled + ? { + Authorization: getAuthString(), + } + : null; + + const client = createDefaultClient( + process.env.NEXT_PUBLIC_WORDPRESS_URL, + headers, + ); + + setGraphQLClient(client); + + const uri = Array.isArray(params?.identifier) + ? '/' + params.identifier.join('/') + '/' + : '/'; + + const variables = isDraftModeEnabled + ? { + id: params.identifier?.[0], + isPreview: true, + } + : { uri }; + + try { + const templateData = await uriToTemplate({ + ...variables, + availableTemplates: Object.keys(availableTemplates), + wordpressUrl: process.env.NEXT_PUBLIC_WORDPRESS_URL, + }); + + if ( + !templateData?.template?.id || + templateData?.template?.id === '404 Not Found' + ) { + return { notFound: true }; + } + + return { + props: { + uri, + templateData: JSON.parse(JSON.stringify(templateData)), + }, + }; + } catch (error) { + console.error('Error resolving template:', error); + return { notFound: true }; + } +} + +export async function getStaticPaths() { + return { + paths: [], + fallback: 'blocking', + }; +} diff --git a/examples/nextjs/previews/src/pages/_app.js b/examples/nextjs/previews/src/pages/_app.js new file mode 100644 index 000000000..af8ade7e2 --- /dev/null +++ b/examples/nextjs/previews/src/pages/_app.js @@ -0,0 +1,10 @@ +import Layout from '@/components/Layout'; +import '@/styles/globals.css'; + +export default function App({ Component, pageProps }) { + return ( + + + + ); +} diff --git a/examples/nextjs/previews/src/pages/_document.js b/examples/nextjs/previews/src/pages/_document.js new file mode 100644 index 000000000..628a7334c --- /dev/null +++ b/examples/nextjs/previews/src/pages/_document.js @@ -0,0 +1,13 @@ +import { Html, Head, Main, NextScript } from "next/document"; + +export default function Document() { + return ( + + + +
+ + + + ); +} diff --git a/examples/nextjs/previews/src/pages/api/disable-preview.js b/examples/nextjs/previews/src/pages/api/disable-preview.js new file mode 100644 index 000000000..fbaba41f5 --- /dev/null +++ b/examples/nextjs/previews/src/pages/api/disable-preview.js @@ -0,0 +1,7 @@ +export default async function handler(_, res) { + // This removes __prerender_bypass cookie + // More info: https://nextjs.org/docs/pages/guides/draft-mode#clear-the-draft-mode-cookie + res.setDraftMode({ enable: false }); + + return res.redirect("/"); +} diff --git a/examples/nextjs/previews/src/pages/api/preview.js b/examples/nextjs/previews/src/pages/api/preview.js new file mode 100644 index 000000000..f6dfd1f09 --- /dev/null +++ b/examples/nextjs/previews/src/pages/api/preview.js @@ -0,0 +1,53 @@ +import { getAuthString } from '@/utils/getAuthString'; +import { createDefaultClient, setGraphQLClient } from '@faustjs/nextjs/pages'; +import { gql } from 'graphql-tag'; +import { print } from 'graphql'; + +const GET_CONTENT = gql` + query GetNode($id: ID! = 0) { + contentNode(id: $id, idType: DATABASE_ID, asPreview: true) { + databaseId + } + } +`; + +export default async function handler(req, res) { + const { secret, id } = req.query; + + if (!id) { + return res.status(400).json({ message: 'No ID provided.' }); + } + + // Check if preview secret token exists and matches environment variable + if (secret !== process.env.WP_PREVIEW_SECRET) { + return res.status(401).json({ message: 'Secret token is invalid.' }); + } + + const client = createDefaultClient(process.env.NEXT_PUBLIC_WORDPRESS_URL, { + Authorization: getAuthString(), + }); + setGraphQLClient(client); + + const { data, error } = await client.request(print(GET_CONTENT), { + id, + }); + + if (error) { + console.error('Error fetching content:', error); + return res.status(500).json({ message: 'Error fetching content.' }); + } + + if (!data?.contentNode) { + return res.status(404).json({ + message: 'Content could not be found. Verify your authentication method.', + }); + } + + // Enable draft mode + // More info: https://nextjs.org/docs/pages/guides/draft-mode#step-1-create-and-access-the-api-route + res.setDraftMode({ enable: true }); + + // Redirect with the databaseId retrieved from the query to prevent redirect at + // More info: https://developers.google.com/search/blog/2009/01/open-redirect-urls-is-your-site-being + res.redirect('/' + data?.contentNode?.databaseId); +} diff --git a/examples/nextjs/previews/src/pages/index.js b/examples/nextjs/previews/src/pages/index.js new file mode 100644 index 000000000..98bc6ad75 --- /dev/null +++ b/examples/nextjs/previews/src/pages/index.js @@ -0,0 +1,111 @@ +import Head from 'next/head'; + +export default function Home() { + // Simple hardcoded template data like in the Astro example + const templates = [ + { + data: { + name: 'Single', + type: 'single', + path: 'src/wp-templates/single.js', + description: 'Displays individual blog posts with full content', + }, + }, + { + data: { + name: 'Page', + type: 'page', + path: 'src/wp-templates/page.js', + description: 'Displays individual WordPress pages', + }, + }, + { + data: { + name: 'Archive', + type: 'archive', + path: 'src/wp-templates/archive.js', + description: 'Displays category, tag, and other archive pages', + }, + }, + { + data: { + name: 'Index', + type: 'index', + path: 'src/wp-templates/index-template.js', + description: + "Fallback template for any content that doesn't have a specific template", + }, + }, + ]; + + return ( + <> + + @faustjs/nextjs Example + + +
+

@faustjs/nextjs Template Hierarchy Example

+

+ This example demonstrates how the @faustjs/nextjs package discovers + WordPress templates in your Next.js project using the WordPress + template hierarchy. +

+ +

Discovered Templates

+

+ The following templates were automatically discovered from the{' '} + wp-templates directory: +

+ +
+ {templates.map((template) => ( +
+

{template.data.name}

+

+ Type: {template.data.type} +

+

+ Path: {template.data.path} +

+

+ Description: {template.data.description} +

+
+ ))} +
+ +

How It Works

+
+
    +
  1. + Templates are placed in the src/wp-templates/{' '} + directory +
  2. +
  3. + The index.js file exports all templates with dynamic + imports +
  4. +
  5. + Templates are organized by WordPress template hierarchy rules +
  6. +
  7. Each template gets metadata like type and path
  8. +
  9. You can query templates using the template registry
  10. +
+
+ +

Live Template Router Demo

+

+ See the template hierarchy in action with our catch-all route that + resolves WordPress URLs to templates: +

+ +
+ + ); +} diff --git a/examples/nextjs/previews/src/styles/globals.css b/examples/nextjs/previews/src/styles/globals.css new file mode 100644 index 000000000..af80a2233 --- /dev/null +++ b/examples/nextjs/previews/src/styles/globals.css @@ -0,0 +1,137 @@ +@import 'tailwindcss'; + +:root { + --background: #ffffff; + --foreground: #171717; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); +} + +.main { + background: var(--background); + color: var(--foreground); + font-family: Arial, Helvetica, sans-serif; +} + +.main { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + max-width: 900px; + margin: 0 auto; + padding: 20px; + line-height: 1.6; + background: #f9f9f9; +} + +h1 { + color: #2563eb; + border-bottom: 2px solid #e5e7eb; + padding-bottom: 10px; +} + +h2 { + color: #374151; + margin-top: 30px; +} + +code { + background: #f3f4f6; + padding: 2px 6px; + border-radius: 4px; + font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; + font-size: 0.9em; +} + +main { + background: white; + padding: 20px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.templates-list { + display: grid; + gap: 15px; + margin: 20px 0; +} + +.template-card { + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 8px; + padding: 20px; + border-left: 4px solid #3b82f6; +} + +.template-card h3 { + margin-top: 0; + color: #1e40af; +} + +.template-card p { + margin: 8px 0; +} + +.how-it-works { + background: #ecfdf5; + border: 1px solid #d1fae5; + border-radius: 8px; + padding: 20px; + margin: 20px 0; +} + +.how-it-works ol { + margin: 0; + padding-left: 20px; +} + +.how-it-works li { + margin-bottom: 8px; +} + +.router-links { + display: flex; + flex-direction: column; + gap: 10px; + margin-top: 15px; +} + +.router-links a { + display: inline-block; + background: #10b981; + color: white; + text-decoration: none; + padding: 10px 15px; + border-radius: 6px; + transition: background-color 0.2s; +} + +.router-links a:hover { + background: #059669; +} + +/* WordPress template styles */ +.content-area { + background: #e8f4f8; + padding: 20px; + border-radius: 8px; + margin-top: 20px; +} + +.post-preview { + background: #f0f8ff; + padding: 15px; + border-radius: 5px; + margin-bottom: 15px; + border-left: 4px solid #0066cc; +} + +.post-preview h3, +.post-preview h4 { + margin-top: 0; + color: #0066cc; +} diff --git a/examples/nextjs/previews/src/utils/getAuthString.js b/examples/nextjs/previews/src/utils/getAuthString.js new file mode 100644 index 000000000..a27e470be --- /dev/null +++ b/examples/nextjs/previews/src/utils/getAuthString.js @@ -0,0 +1,5 @@ +// Forming the authentication string for WordPress App Password +// More info: https://make.wordpress.org/core/2020/11/05/application-passwords-integration-guide/ + +export const getAuthString = () => + "Basic " + Buffer.from(process.env.WP_USERNAME + ":" + process.env.WP_APP_PASSWORD).toString("base64"); diff --git a/examples/nextjs/previews/src/wp-templates/archive.js b/examples/nextjs/previews/src/wp-templates/archive.js new file mode 100644 index 000000000..1ce8e4951 --- /dev/null +++ b/examples/nextjs/previews/src/wp-templates/archive.js @@ -0,0 +1,38 @@ +export default function ArchiveTemplate({ templateData }) { + const { seedQuery } = templateData || {}; + const posts = seedQuery?.data?.posts?.nodes || []; + const archiveInfo = seedQuery?.data?.category || seedQuery?.data?.tag || {}; + + return ( + <> +

WordPress Archive Template

+

This template would render category, tag, or other archive pages.

+
+ {archiveInfo.name ? ( +

Archive: {archiveInfo.name}

+ ) : ( +

Archive Page

+ )} + {archiveInfo.description &&

{archiveInfo.description}

} + +

Posts in this archive:

+ {posts.length > 0 ? ( + posts.map((post) => ( +
+

{post.title}

+

{post.excerpt || 'Post excerpt would appear here...'}

+
+ )) + ) : ( +
+

Sample Archive Post

+

+ Posts from this category, tag, or archive would be displayed here + using data from the WordPress GraphQL API. +

+
+ )} +
+ + ); +} diff --git a/examples/nextjs/previews/src/wp-templates/index-template.js b/examples/nextjs/previews/src/wp-templates/index-template.js new file mode 100644 index 000000000..b50380764 --- /dev/null +++ b/examples/nextjs/previews/src/wp-templates/index-template.js @@ -0,0 +1,33 @@ +export default function IndexTemplate({ templateData }) { + const { seedQuery } = templateData || {}; + const posts = seedQuery?.data?.posts?.nodes || []; + + return ( + <> +

WordPress Index Template

+

This is the fallback template for all WordPress content.

+
+

+ Any WordPress content that doesn't have a specific template would use + this one: +

+ {posts.length > 0 ? ( + posts.map((post) => ( +
+

{post.title}

+

{post.excerpt || 'Content would appear here...'}

+
+ )) + ) : ( +
+

Sample Content

+

+ This is the default template that would render any WordPress + content using data from the WordPress GraphQL API. +

+
+ )} +
+ + ); +} diff --git a/examples/nextjs/previews/src/wp-templates/index.js b/examples/nextjs/previews/src/wp-templates/index.js new file mode 100644 index 000000000..c4b921aaf --- /dev/null +++ b/examples/nextjs/previews/src/wp-templates/index.js @@ -0,0 +1,19 @@ +import dynamic from 'next/dynamic'; + +const single = dynamic(() => import('./single.js'), { + loading: () =>

Loading Single Template...

, +}); + +const page = dynamic(() => import('./page.js'), { + loading: () =>

Loading Page Template...

, +}); + +const archive = dynamic(() => import('./archive.js'), { + loading: () =>

Loading Archive Template...

, +}); + +const index = dynamic(() => import('./index-template.js'), { + loading: () =>

Loading Index Template...

, +}); + +export default { single, page, archive, index }; diff --git a/examples/nextjs/previews/src/wp-templates/page.js b/examples/nextjs/previews/src/wp-templates/page.js new file mode 100644 index 000000000..f604fc64c --- /dev/null +++ b/examples/nextjs/previews/src/wp-templates/page.js @@ -0,0 +1,24 @@ +export default function PageTemplate({ templateData }) { + const { seedQuery } = templateData || {}; + const page = seedQuery?.data?.page; + + return ( + <> +

WordPress Page Template

+

This template would render individual WordPress pages.

+
+ {page ? ( + <> +

{page.title}

+
+ + ) : ( +

+ Page title, content, and metadata would be displayed here using data + from the WordPress GraphQL API. +

+ )} +
+ + ); +} diff --git a/examples/nextjs/previews/src/wp-templates/single.js b/examples/nextjs/previews/src/wp-templates/single.js new file mode 100644 index 000000000..d1f53a512 --- /dev/null +++ b/examples/nextjs/previews/src/wp-templates/single.js @@ -0,0 +1,36 @@ +export default function SingleTemplate({ templateData }) { + const { seedQuery } = templateData || {}; + const post = seedQuery?.data?.post; + + return ( + <> +

WordPress Single Post Template

+

This template would render individual blog posts.

+
+ {post ? ( +
+

{post.title}

+
+ {post.categories?.nodes && ( +
+ Categories: + {post.categories.nodes.map((cat) => cat.name).join(', ')} +
+ )} + {post.tags?.nodes && ( +
+ Tags: + {post.tags.nodes.map((tag) => tag.name).join(', ')} +
+ )} +
+ ) : ( +

+ Blog post title, content, categories, tags, and other metadata would + be displayed here using data from the WordPress GraphQL API. +

+ )} +
+ + ); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a319e165c..22b1a97fa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -76,7 +76,7 @@ importers: version: link:../../../packages/template-hierarchy astro: specifier: ^5.1.1 - version: 5.12.8(rollup@4.46.2)(typescript@5.8.3) + version: 5.12.8(jiti@2.5.1)(lightningcss@1.30.1)(rollup@4.46.2)(typescript@5.8.3) graphql: specifier: ^16.9.0 version: 16.11.0 @@ -88,6 +88,46 @@ importers: specifier: ^5.6.3 version: 5.8.3 + examples/nextjs/previews: + dependencies: + '@faustjs/nextjs': + specifier: workspace:* + version: link:../../../packages/nextjs + '@faustjs/template-hierarchy': + specifier: workspace:* + version: link:../../../packages/template-hierarchy + graphql: + specifier: ^16.11.0 + version: 16.11.0 + graphql-tag: + specifier: ^2.12.6 + version: 2.12.6(graphql@16.11.0) + next: + specifier: 15.2.4 + version: 15.2.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + react: + specifier: ^19.0.0 + version: 19.1.1 + react-dom: + specifier: ^19.0.0 + version: 19.1.1(react@19.1.1) + devDependencies: + '@eslint/eslintrc': + specifier: ^3 + version: 3.3.1 + '@tailwindcss/postcss': + specifier: ^4 + version: 4.1.11 + eslint: + specifier: ^9 + version: 9.33.0(jiti@2.5.1) + eslint-config-next: + specifier: 15.2.4 + version: 15.2.4(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) + tailwindcss: + specifier: ^4 + version: 4.1.11 + examples/nextjs/template-hierarchy: dependencies: '@faustjs/nextjs': @@ -119,6 +159,46 @@ importers: specifier: ^15.1.3 version: 15.4.5(eslint@8.57.1)(typescript@5.8.3) + examples/sveltekit/template-hierarchy: + dependencies: + '@urql/core': + specifier: ^5.1.1 + version: 5.2.0(graphql@16.11.0) + '@urql/exchange-persisted': + specifier: ^4.3.1 + version: 4.3.1(@urql/core@5.2.0(graphql@16.11.0)) + deepmerge: + specifier: ^4.3.1 + version: 4.3.1 + graphql: + specifier: ^16.11.0 + version: 16.11.0 + devDependencies: + '@faustjs/sveltekit': + specifier: workspace:* + version: link:../../../packages/sveltekit + '@faustjs/template-hierarchy': + specifier: workspace:* + version: link:../../../packages/template-hierarchy + '@sveltejs/adapter-auto': + specifier: ^6.0.0 + version: 6.1.0(@sveltejs/kit@2.28.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)))(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1))) + '@sveltejs/kit': + specifier: ^2.16.0 + version: 2.28.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)))(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)) + '@sveltejs/vite-plugin-svelte': + specifier: ^5.0.0 + version: 5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)) + svelte: + specifier: ^5.0.0 + version: 5.38.1 + svelte-check: + specifier: ^4.0.0 + version: 4.3.1(picomatch@4.0.3)(svelte@5.38.1)(typescript@5.8.3) + vite: + specifier: ^6.2.6 + version: 6.3.5(jiti@2.5.1)(lightningcss@1.30.1) + packages/astro: dependencies: '@faustjs/graphql': @@ -129,7 +209,7 @@ importers: version: link:../template-hierarchy astro: specifier: ^5.0.0 - version: 5.12.8(rollup@4.46.2)(typescript@5.8.3) + version: 5.12.8(jiti@2.5.1)(lightningcss@1.30.1)(rollup@4.46.2)(typescript@5.8.3) graphql: specifier: ^16.8.1 version: 16.11.0 @@ -149,11 +229,29 @@ importers: version: 16.11.0 next: specifier: ^13.0.0 || ^14.0.0 || ^15.0.0 - version: 14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 14.2.31(react-dom@19.1.1(react@18.3.1))(react@18.3.1) react: specifier: ^18.0.0 || ^19.0.0 version: 18.3.1 + packages/sveltekit: + dependencies: + '@faustjs/graphql': + specifier: workspace:* + version: link:../graphql + '@faustjs/template-hierarchy': + specifier: workspace:* + version: link:../template-hierarchy + '@sveltejs/kit': + specifier: ^2.0.0 + version: 2.28.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)))(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)) + graphql: + specifier: ^16.8.1 + version: 16.11.0 + svelte: + specifier: ^4.0.0 || ^5.0.0 + version: 5.38.1 + packages/template-hierarchy: dependencies: graphql: @@ -165,6 +263,22 @@ importers: packages: + '@0no-co/graphql.web@1.2.0': + resolution: {integrity: sha512-/1iHy9TTr63gE1YcR5idjx8UREz1s0kFhydf3bBLCXyqjhkIc6igAzTOx3zPifCwFR87tsh/4Pa9cNts6d2otw==} + peerDependencies: + graphql: ^14.0.0 || ^15.0.0 || ^16.0.0 + peerDependenciesMeta: + graphql: + optional: true + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + '@astrojs/compiler@2.12.2': resolution: {integrity: sha512-w2zfvhjNCkNMmMMOn5b0J8+OmUaBL1o40ipMvqcG6NRpdC+lKxmTi48DT8Xw0SzJ3AfmeFLB45zXZXtmbsjcgw==} @@ -448,14 +562,50 @@ packages: resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint/config-array@0.21.0': + resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.3.1': + resolution: {integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.15.2': + resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/eslintrc@2.1.4': resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@8.57.1': resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint/js@9.33.0': + resolution: {integrity: sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.6': + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.3.5': + resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + '@humanwhocodes/config-array@0.13.0': resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} engines: {node: '>=10.10.0'} @@ -469,6 +619,14 @@ packages: resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} deprecated: Use @eslint/object-schema instead + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} + engines: {node: '>=18.18'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + '@img/sharp-darwin-arm64@0.33.5': resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -696,9 +854,26 @@ packages: cpu: [x64] os: [win32] + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + '@jridgewell/sourcemap-codec@1.5.4': resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} + '@jridgewell/trace-mapping@0.3.30': + resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} + '@manypkg/find-root@1.1.0': resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==} @@ -714,9 +889,15 @@ packages: '@next/env@14.2.31': resolution: {integrity: sha512-X8VxxYL6VuezrG82h0pUA1V+DuTSJp7Nv15bxq3ivrFqZLjx81rfeHMWOE9T0jm1n3DtHGv8gdn6B0T0kr0D3Q==} + '@next/env@15.2.4': + resolution: {integrity: sha512-+SFtMgoiYP3WoSswuNmxJOCwi06TdWE733D+WPjpXIe4LXGULwEaofiiAy6kbS0+XjM5xF5n3lKuBwN2SnqD9g==} + '@next/env@15.4.5': resolution: {integrity: sha512-ruM+q2SCOVCepUiERoxOmZY9ZVoecR3gcXNwCYZRvQQWRjhOiPJGmQ2fAiLR6YKWXcSAh7G79KEFxN3rwhs4LQ==} + '@next/eslint-plugin-next@15.2.4': + resolution: {integrity: sha512-O8ScvKtnxkp8kL9TpJTTKnMqlkZnS+QxwoQnJwPGBxjBbzd6OVVPEJ5/pMNrktSyXQD/chEfzfFzYLM6JANOOQ==} + '@next/eslint-plugin-next@15.4.5': resolution: {integrity: sha512-YhbrlbEt0m4jJnXHMY/cCUDBAWgd5SaTa5mJjzOt82QwflAFfW/h3+COp2TfVSzhmscIZ5sg2WXt3MLziqCSCw==} @@ -726,6 +907,12 @@ packages: cpu: [arm64] os: [darwin] + '@next/swc-darwin-arm64@15.2.4': + resolution: {integrity: sha512-1AnMfs655ipJEDC/FHkSr0r3lXBgpqKo4K1kiwfUf3iE68rDFXZ1TtHdMvf7D0hMItgDZ7Vuq3JgNMbt/+3bYw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + '@next/swc-darwin-arm64@15.4.5': resolution: {integrity: sha512-84dAN4fkfdC7nX6udDLz9GzQlMUwEMKD7zsseXrl7FTeIItF8vpk1lhLEnsotiiDt+QFu3O1FVWnqwcRD2U3KA==} engines: {node: '>= 10'} @@ -738,6 +925,12 @@ packages: cpu: [x64] os: [darwin] + '@next/swc-darwin-x64@15.2.4': + resolution: {integrity: sha512-3qK2zb5EwCwxnO2HeO+TRqCubeI/NgCe+kL5dTJlPldV/uwCnUgC7VbEzgmxbfrkbjehL4H9BPztWOEtsoMwew==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + '@next/swc-darwin-x64@15.4.5': resolution: {integrity: sha512-CL6mfGsKuFSyQjx36p2ftwMNSb8PQog8y0HO/ONLdQqDql7x3aJb/wB+LA651r4we2pp/Ck+qoRVUeZZEvSurA==} engines: {node: '>= 10'} @@ -750,6 +943,12 @@ packages: cpu: [arm64] os: [linux] + '@next/swc-linux-arm64-gnu@15.2.4': + resolution: {integrity: sha512-HFN6GKUcrTWvem8AZN7tT95zPb0GUGv9v0d0iyuTb303vbXkkbHDp/DxufB04jNVD+IN9yHy7y/6Mqq0h0YVaQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + '@next/swc-linux-arm64-gnu@15.4.5': resolution: {integrity: sha512-1hTVd9n6jpM/thnDc5kYHD1OjjWYpUJrJxY4DlEacT7L5SEOXIifIdTye6SQNNn8JDZrcN+n8AWOmeJ8u3KlvQ==} engines: {node: '>= 10'} @@ -762,6 +961,12 @@ packages: cpu: [arm64] os: [linux] + '@next/swc-linux-arm64-musl@15.2.4': + resolution: {integrity: sha512-Oioa0SORWLwi35/kVB8aCk5Uq+5/ZIumMK1kJV+jSdazFm2NzPDztsefzdmzzpx5oGCJ6FkUC7vkaUseNTStNA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + '@next/swc-linux-arm64-musl@15.4.5': resolution: {integrity: sha512-4W+D/nw3RpIwGrqpFi7greZ0hjrCaioGErI7XHgkcTeWdZd146NNu1s4HnaHonLeNTguKnL2Urqvj28UJj6Gqw==} engines: {node: '>= 10'} @@ -774,6 +979,12 @@ packages: cpu: [x64] os: [linux] + '@next/swc-linux-x64-gnu@15.2.4': + resolution: {integrity: sha512-yb5WTRaHdkgOqFOZiu6rHV1fAEK0flVpaIN2HB6kxHVSy/dIajWbThS7qON3W9/SNOH2JWkVCyulgGYekMePuw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + '@next/swc-linux-x64-gnu@15.4.5': resolution: {integrity: sha512-N6Mgdxe/Cn2K1yMHge6pclffkxzbSGOydXVKYOjYqQXZYjLCfN/CuFkaYDeDHY2VBwSHyM2fUjYBiQCIlxIKDA==} engines: {node: '>= 10'} @@ -786,6 +997,12 @@ packages: cpu: [x64] os: [linux] + '@next/swc-linux-x64-musl@15.2.4': + resolution: {integrity: sha512-Dcdv/ix6srhkM25fgXiyOieFUkz+fOYkHlydWCtB0xMST6X9XYI3yPDKBZt1xuhOytONsIFJFB08xXYsxUwJLw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + '@next/swc-linux-x64-musl@15.4.5': resolution: {integrity: sha512-YZ3bNDrS8v5KiqgWE0xZQgtXgCTUacgFtnEgI4ccotAASwSvcMPDLua7BWLuTfucoRv6mPidXkITJLd8IdJplQ==} engines: {node: '>= 10'} @@ -798,6 +1015,12 @@ packages: cpu: [arm64] os: [win32] + '@next/swc-win32-arm64-msvc@15.2.4': + resolution: {integrity: sha512-dW0i7eukvDxtIhCYkMrZNQfNicPDExt2jPb9AZPpL7cfyUo7QSNl1DjsHjmmKp6qNAqUESyT8YFl/Aw91cNJJg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + '@next/swc-win32-arm64-msvc@15.4.5': resolution: {integrity: sha512-9Wr4t9GkZmMNcTVvSloFtjzbH4vtT4a8+UHqDoVnxA5QyfWe6c5flTH1BIWPGNWSUlofc8dVJAE7j84FQgskvQ==} engines: {node: '>= 10'} @@ -816,6 +1039,12 @@ packages: cpu: [x64] os: [win32] + '@next/swc-win32-x64-msvc@15.2.4': + resolution: {integrity: sha512-SbnWkJmkS7Xl3kre8SdMF6F/XDh1DTFEhp0jRTj/uB8iPKoU2bb2NDfcu+iifv1+mxQEd1g2vvSxcZbXSKyWiQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + '@next/swc-win32-x64-msvc@15.4.5': resolution: {integrity: sha512-voWk7XtGvlsP+w8VBz7lqp8Y+dYw/MTI4KeS0gTVtfdhdJ5QwhXLmNrndFOin/MDoCvUaLWMkYKATaCoUkt2/A==} engines: {node: '>= 10'} @@ -980,6 +1209,43 @@ packages: '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@standard-schema/spec@1.0.0': + resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + + '@sveltejs/acorn-typescript@1.0.5': + resolution: {integrity: sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==} + peerDependencies: + acorn: ^8.9.0 + + '@sveltejs/adapter-auto@6.1.0': + resolution: {integrity: sha512-shOuLI5D2s+0zTv2ab5M5PqfknXqWbKi+0UwB9yLTRIdzsK1R93JOO8jNhIYSHdW+IYXIYnLniu+JZqXs7h9Wg==} + peerDependencies: + '@sveltejs/kit': ^2.0.0 + + '@sveltejs/kit@2.28.0': + resolution: {integrity: sha512-qrhygwHV5r6JrvCw4gwNqqxYGDi5YbajocxfKgFXmSFpFo8wQobUvsM0OfakN4h+0LEmXtqHRrC6BcyAkOwyoQ==} + engines: {node: '>=18.13'} + hasBin: true + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 + svelte: ^4.0.0 || ^5.0.0-next.0 + vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 + + '@sveltejs/vite-plugin-svelte-inspector@4.0.1': + resolution: {integrity: sha512-J/Nmb2Q2y7mck2hyCX4ckVHcR5tu2J+MtBEQqpDrrgELZ2uvraQcK/ioCV61AqkdXFgriksOKIceDcQmqnGhVw==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22} + peerDependencies: + '@sveltejs/vite-plugin-svelte': ^5.0.0 + svelte: ^5.0.0 + vite: ^6.0.0 + + '@sveltejs/vite-plugin-svelte@5.1.1': + resolution: {integrity: sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22} + peerDependencies: + svelte: ^5.0.0 + vite: ^6.0.0 + '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} @@ -992,9 +1258,100 @@ packages: '@swc/helpers@0.5.5': resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} + '@tailwindcss/node@4.1.11': + resolution: {integrity: sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==} + + '@tailwindcss/oxide-android-arm64@4.1.11': + resolution: {integrity: sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.11': + resolution: {integrity: sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.11': + resolution: {integrity: sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.11': + resolution: {integrity: sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11': + resolution: {integrity: sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.11': + resolution: {integrity: sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.11': + resolution: {integrity: sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.11': + resolution: {integrity: sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.11': + resolution: {integrity: sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.11': + resolution: {integrity: sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.11': + resolution: {integrity: sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.11': + resolution: {integrity: sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.11': + resolution: {integrity: sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==} + engines: {node: '>= 10'} + + '@tailwindcss/postcss@4.1.11': + resolution: {integrity: sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==} + '@tybys/wasm-util@0.10.0': resolution: {integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==} + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -1187,6 +1544,14 @@ packages: cpu: [x64] os: [win32] + '@urql/core@5.2.0': + resolution: {integrity: sha512-/n0ieD0mvvDnVAXEQgX/7qJiVcvYvNkOHeBvkwtylfjydar123caCXcl58PXFY11oU1oquJocVXHxLAbtv4x1A==} + + '@urql/exchange-persisted@4.3.1': + resolution: {integrity: sha512-VRGYFNW0gaT7+VCR/1rCMWEP9gvnDnlAolKisysLYkv8Zeysbqx+Omw4IgIRCLjI6oHWPkH8zOhFbqA3GRQ9HQ==} + peerDependencies: + '@urql/core': ^5.0.0 + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1201,6 +1566,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -1391,6 +1761,10 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + ci-info@3.9.0: resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} engines: {node: '>=8'} @@ -1447,6 +1821,10 @@ packages: cookie-es@1.2.2: resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} + cookie@0.6.0: + resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + engines: {node: '>= 0.6'} + cookie@1.0.2: resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} engines: {node: '>=18'} @@ -1508,6 +1886,10 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + define-data-property@1.1.4: resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} engines: {node: '>= 0.4'} @@ -1586,6 +1968,10 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + enhanced-resolve@5.18.3: + resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} + engines: {node: '>=10.13.0'} + enquirer@2.4.1: resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} engines: {node: '>=8.6'} @@ -1667,6 +2053,15 @@ packages: eslint-plugin-react: ^7.28.0 eslint-plugin-react-hooks: ^4.3.0 + eslint-config-next@15.2.4: + resolution: {integrity: sha512-v4gYjd4eYIme8qzaJItpR5MMBXJ0/YV07u7eb50kEnlEmX7yhOjdUdzz70v4fiINYRjLf8X8TbogF0k7wlz6sA==} + peerDependencies: + eslint: ^7.23.0 || ^8.0.0 || ^9.0.0 + typescript: '>=3.3.1' + peerDependenciesMeta: + typescript: + optional: true + eslint-config-next@15.4.5: resolution: {integrity: sha512-IMijiXaZ43qFB+Gcpnb374ipTKD8JIyVNR+6VsifFQ/LHyx+A9wgcgSIhCX5PYSjwOoSYD5LtNHKlM5uc23eww==} peerDependencies: @@ -1766,16 +2161,41 @@ packages: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint@8.57.1: resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true + eslint@9.33.0: + resolution: {integrity: sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + esm-env@1.2.2: + resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + espree@9.6.1: resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1789,6 +2209,9 @@ packages: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} + esrap@2.1.0: + resolution: {integrity: sha512-yzmPNpl7TBbMRC5Lj2JlJZNPml0tzqoqP5B1JXycNUwtqma9AKCO0M2wHrdgsHcy1WRW7S9rJknAMtByg3usgA==} + esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} @@ -1859,6 +2282,10 @@ packages: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -1875,6 +2302,10 @@ packages: resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} engines: {node: ^10.12.0 || >=12.0.0} + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + flatted@3.3.3: resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} @@ -1956,6 +2387,10 @@ packages: resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} engines: {node: '>=8'} + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -2182,6 +2617,9 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-reference@3.0.3: + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + is-regex@1.2.1: resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} @@ -2240,6 +2678,10 @@ packages: resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} + jiti@2.5.1: + resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} + hasBin: true + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2293,6 +2735,73 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lightningcss-darwin-arm64@1.30.1: + resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.1: + resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.1: + resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.1: + resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.1: + resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.1: + resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.1: + resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.1: + resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.1: + resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.1: + resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.1: + resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} + engines: {node: '>= 12.0.0'} + + locate-character@3.0.0: + resolution: {integrity: sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==} + locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -2473,6 +2982,19 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.0.2: + resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} + engines: {node: '>= 18'} + + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -2526,6 +3048,27 @@ packages: sass: optional: true + next@15.2.4: + resolution: {integrity: sha512-VwL+LAaPSxEkd3lU2xWbgEOtrM8oedmyhBqaVNmgKB+GvZlCy9rgaEc+y2on0wv+l0oSFqLtYD6dcC1eAedUaQ==} + engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.41.2 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + next@15.4.5: resolution: {integrity: sha512-nJ4v+IO9CPmbmcvsPebIoX3Q+S7f6Fu08/dEWu0Ttfa+wVwQRh9epcmsyCPjmL2b8MxC+CkBR97jgDhUUztI3g==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} @@ -2788,6 +3331,11 @@ packages: peerDependencies: react: ^18.3.1 + react-dom@19.1.1: + resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==} + peerDependencies: + react: ^19.1.1 + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -2799,6 +3347,10 @@ packages: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} + react@19.1.1: + resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==} + engines: {node: '>=0.10.0'} + read-yaml-file@1.1.0: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} @@ -2907,6 +3459,10 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + safe-array-concat@1.1.3: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} @@ -2925,6 +3481,9 @@ packages: scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scheduler@0.26.0: + resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -2934,6 +3493,9 @@ packages: engines: {node: '>=10'} hasBin: true + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -2992,6 +3554,10 @@ packages: resolution: {integrity: sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==} engines: {node: '>= 10'} + sirv@3.0.1: + resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} + engines: {node: '>=18'} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -3107,6 +3673,29 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} + svelte-check@4.3.1: + resolution: {integrity: sha512-lkh8gff5gpHLjxIV+IaApMxQhTGnir2pNUAqcNgeKkvK5bT/30Ey/nzBxNLDlkztCH4dP7PixkMt9SWEKFPBWg==} + engines: {node: '>= 18.0.0'} + hasBin: true + peerDependencies: + svelte: ^4.0.0 || ^5.0.0-next.0 + typescript: '>=5.0.0' + + svelte@5.38.1: + resolution: {integrity: sha512-fO6CLDfJYWHgfo6lQwkQU2vhCiHc2MBl6s3vEhK+sSZru17YL4R5s1v14ndRpqKAIkq8nCz6MTk1yZbESZWeyQ==} + engines: {node: '>=18'} + + tailwindcss@4.1.11: + resolution: {integrity: sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==} + + tapable@2.2.2: + resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} + engines: {node: '>=6'} + + tar@7.4.3: + resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} + engines: {node: '>=18'} + term-size@2.2.1: resolution: {integrity: sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==} engines: {node: '>=8'} @@ -3136,6 +3725,10 @@ packages: resolution: {integrity: sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==} engines: {node: '>=6'} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -3424,6 +4017,9 @@ packages: resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} engines: {node: '>=18'} + wonka@6.3.5: + resolution: {integrity: sha512-SSil+ecw6B4/Dm7Pf2sAshKQ5hWFvfyGlfPbEd6A14dOH6VDjrmbY86u6nZvy9omGwwIPFR8V41+of1EezgoUw==} + word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -3450,6 +4046,10 @@ packages: xxhash-wasm@1.1.0: resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -3470,6 +4070,9 @@ packages: resolution: {integrity: sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==} engines: {node: '>=18'} + zimmerframe@1.1.2: + resolution: {integrity: sha512-rAbqEGa8ovJy4pyBxZM70hg4pE6gDgaQ0Sl9M3enG3I0d6H4XSAM3GeNGLKnsBpuijUow064sf7ww1nutC5/3w==} + zod-to-json-schema@3.24.6: resolution: {integrity: sha512-h/z3PKvcTcTetyjl1fkj79MHNEjm+HpD6NXheWjzOekY7kV+lwDYnHw+ivHkijnCSMz1yJaWBD9vu/Fcmk+vEg==} peerDependencies: @@ -3489,6 +4092,17 @@ packages: snapshots: + '@0no-co/graphql.web@1.2.0(graphql@16.11.0)': + optionalDependencies: + graphql: 16.11.0 + + '@alloc/quick-lru@5.2.0': {} + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 + '@astrojs/compiler@2.12.2': {} '@astrojs/internal-helpers@0.7.1': {} @@ -3818,8 +4432,27 @@ snapshots: eslint: 8.57.1 eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.7.0(eslint@9.33.0(jiti@2.5.1))': + dependencies: + eslint: 9.33.0(jiti@2.5.1) + eslint-visitor-keys: 3.4.3 + '@eslint-community/regexpp@4.12.1': {} + '@eslint/config-array@0.21.0': + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.1 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.3.1': {} + + '@eslint/core@0.15.2': + dependencies: + '@types/json-schema': 7.0.15 + '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 @@ -3834,8 +4467,38 @@ snapshots: transitivePeerDependencies: - supports-color + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.1 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + '@eslint/js@8.57.1': {} + '@eslint/js@9.33.0': {} + + '@eslint/object-schema@2.1.6': {} + + '@eslint/plugin-kit@0.3.5': + dependencies: + '@eslint/core': 0.15.2 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 + '@humanwhocodes/config-array@0.13.0': dependencies: '@humanwhocodes/object-schema': 2.0.3 @@ -3848,6 +4511,10 @@ snapshots: '@humanwhocodes/object-schema@2.0.3': {} + '@humanwhocodes/retry@0.3.1': {} + + '@humanwhocodes/retry@0.4.3': {} + '@img/sharp-darwin-arm64@0.33.5': optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.0.4 @@ -4009,8 +4676,29 @@ snapshots: '@img/sharp-win32-x64@0.34.3': optional: true + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.4 + '@jridgewell/trace-mapping': 0.3.30 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 + + '@jridgewell/resolve-uri@3.1.2': {} + '@jridgewell/sourcemap-codec@1.5.4': {} + '@jridgewell/trace-mapping@0.3.30': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.4 + '@manypkg/find-root@1.1.0': dependencies: '@babel/runtime': 7.27.0 @@ -4043,8 +4731,14 @@ snapshots: '@next/env@14.2.31': {} + '@next/env@15.2.4': {} + '@next/env@15.4.5': {} + '@next/eslint-plugin-next@15.2.4': + dependencies: + fast-glob: 3.3.1 + '@next/eslint-plugin-next@15.4.5': dependencies: fast-glob: 3.3.1 @@ -4052,42 +4746,63 @@ snapshots: '@next/swc-darwin-arm64@14.2.31': optional: true + '@next/swc-darwin-arm64@15.2.4': + optional: true + '@next/swc-darwin-arm64@15.4.5': optional: true '@next/swc-darwin-x64@14.2.31': optional: true + '@next/swc-darwin-x64@15.2.4': + optional: true + '@next/swc-darwin-x64@15.4.5': optional: true '@next/swc-linux-arm64-gnu@14.2.31': optional: true + '@next/swc-linux-arm64-gnu@15.2.4': + optional: true + '@next/swc-linux-arm64-gnu@15.4.5': optional: true '@next/swc-linux-arm64-musl@14.2.31': optional: true + '@next/swc-linux-arm64-musl@15.2.4': + optional: true + '@next/swc-linux-arm64-musl@15.4.5': optional: true '@next/swc-linux-x64-gnu@14.2.31': optional: true + '@next/swc-linux-x64-gnu@15.2.4': + optional: true + '@next/swc-linux-x64-gnu@15.4.5': optional: true '@next/swc-linux-x64-musl@14.2.31': optional: true + '@next/swc-linux-x64-musl@15.2.4': + optional: true + '@next/swc-linux-x64-musl@15.4.5': optional: true '@next/swc-win32-arm64-msvc@14.2.31': optional: true + '@next/swc-win32-arm64-msvc@15.2.4': + optional: true + '@next/swc-win32-arm64-msvc@15.4.5': optional: true @@ -4097,6 +4812,9 @@ snapshots: '@next/swc-win32-x64-msvc@14.2.31': optional: true + '@next/swc-win32-x64-msvc@15.2.4': + optional: true + '@next/swc-win32-x64-msvc@15.4.5': optional: true @@ -4223,6 +4941,57 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} + '@standard-schema/spec@1.0.0': {} + + '@sveltejs/acorn-typescript@1.0.5(acorn@8.14.1)': + dependencies: + acorn: 8.14.1 + + '@sveltejs/adapter-auto@6.1.0(@sveltejs/kit@2.28.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)))(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)))': + dependencies: + '@sveltejs/kit': 2.28.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)))(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)) + + '@sveltejs/kit@2.28.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)))(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1))': + dependencies: + '@standard-schema/spec': 1.0.0 + '@sveltejs/acorn-typescript': 1.0.5(acorn@8.14.1) + '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)) + '@types/cookie': 0.6.0 + acorn: 8.14.1 + cookie: 0.6.0 + devalue: 5.1.1 + esm-env: 1.2.2 + kleur: 4.1.5 + magic-string: 0.30.17 + mrmime: 2.0.1 + sade: 1.8.1 + set-cookie-parser: 2.7.1 + sirv: 3.0.1 + svelte: 5.38.1 + vite: 6.3.5(jiti@2.5.1)(lightningcss@1.30.1) + + '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)))(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1))': + dependencies: + '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)) + debug: 4.4.1 + svelte: 5.38.1 + vite: 6.3.5(jiti@2.5.1)(lightningcss@1.30.1) + transitivePeerDependencies: + - supports-color + + '@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1))': + dependencies: + '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)))(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)) + debug: 4.4.1 + deepmerge: 4.3.1 + kleur: 4.1.5 + magic-string: 0.30.17 + svelte: 5.38.1 + vite: 6.3.5(jiti@2.5.1)(lightningcss@1.30.1) + vitefu: 1.1.1(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)) + transitivePeerDependencies: + - supports-color + '@swc/counter@0.1.3': {} '@swc/helpers@0.5.15': @@ -4238,11 +5007,85 @@ snapshots: '@swc/counter': 0.1.3 tslib: 2.8.1 + '@tailwindcss/node@4.1.11': + dependencies: + '@ampproject/remapping': 2.3.0 + enhanced-resolve: 5.18.3 + jiti: 2.5.1 + lightningcss: 1.30.1 + magic-string: 0.30.17 + source-map-js: 1.2.1 + tailwindcss: 4.1.11 + + '@tailwindcss/oxide-android-arm64@4.1.11': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.11': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.11': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.11': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.11': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.11': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.11': + optional: true + + '@tailwindcss/oxide@4.1.11': + dependencies: + detect-libc: 2.0.4 + tar: 7.4.3 + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.11 + '@tailwindcss/oxide-darwin-arm64': 4.1.11 + '@tailwindcss/oxide-darwin-x64': 4.1.11 + '@tailwindcss/oxide-freebsd-x64': 4.1.11 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.11 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.11 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.11 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.11 + '@tailwindcss/oxide-linux-x64-musl': 4.1.11 + '@tailwindcss/oxide-wasm32-wasi': 4.1.11 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.11 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.11 + + '@tailwindcss/postcss@4.1.11': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.1.11 + '@tailwindcss/oxide': 4.1.11 + postcss: 8.5.6 + tailwindcss: 4.1.11 + '@tybys/wasm-util@0.10.0': dependencies: tslib: 2.8.1 optional: true + '@types/cookie@0.6.0': {} + '@types/debug@4.1.12': dependencies: '@types/ms': 2.1.0 @@ -4296,6 +5139,25 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/type-utils': 5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) + '@typescript-eslint/utils': 5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) + debug: 4.4.1 + eslint: 9.33.0(jiti@2.5.1) + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare-lite: 1.4.0 + semver: 7.7.2 + tsutils: 3.21.0(typescript@5.8.3) + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3)': dependencies: '@typescript-eslint/scope-manager': 5.62.0 @@ -4308,6 +5170,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) + debug: 4.4.1 + eslint: 9.33.0(jiti@2.5.1) + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/scope-manager@5.62.0': dependencies: '@typescript-eslint/types': 5.62.0 @@ -4316,9 +5190,21 @@ snapshots: '@typescript-eslint/type-utils@5.62.0(eslint@8.57.1)(typescript@5.8.3)': dependencies: '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) - '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.8.3) + '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.8.3) + debug: 4.4.1 + eslint: 8.57.1 + tsutils: 3.21.0(typescript@5.8.3) + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/type-utils@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) + '@typescript-eslint/utils': 5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) debug: 4.4.1 - eslint: 8.57.1 + eslint: 9.33.0(jiti@2.5.1) tsutils: 3.21.0(typescript@5.8.3) optionalDependencies: typescript: 5.8.3 @@ -4356,6 +5242,21 @@ snapshots: - supports-color - typescript + '@typescript-eslint/utils@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3)': + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.5.1)) + '@types/json-schema': 7.0.15 + '@types/semver': 7.7.0 + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.8.3) + eslint: 9.33.0(jiti@2.5.1) + eslint-scope: 5.1.1 + semver: 7.7.2 + transitivePeerDependencies: + - supports-color + - typescript + '@typescript-eslint/visitor-keys@5.62.0': dependencies: '@typescript-eslint/types': 5.62.0 @@ -4422,16 +5323,34 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true + '@urql/core@5.2.0(graphql@16.11.0)': + dependencies: + '@0no-co/graphql.web': 1.2.0(graphql@16.11.0) + wonka: 6.3.5 + transitivePeerDependencies: + - graphql + + '@urql/exchange-persisted@4.3.1(@urql/core@5.2.0(graphql@16.11.0))': + dependencies: + '@urql/core': 5.2.0(graphql@16.11.0) + wonka: 6.3.5 + acorn-jsx@5.3.2(acorn@8.14.1): dependencies: acorn: 8.14.1 + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + acorn-walk@8.3.4: dependencies: acorn: 8.14.1 acorn@8.14.1: {} + acorn@8.15.0: {} + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -4539,7 +5458,7 @@ snapshots: ast-types-flow@0.0.8: {} - astro@5.12.8(rollup@4.46.2)(typescript@5.8.3): + astro@5.12.8(jiti@2.5.1)(lightningcss@1.30.1)(rollup@4.46.2)(typescript@5.8.3): dependencies: '@astrojs/compiler': 2.12.2 '@astrojs/internal-helpers': 0.7.1 @@ -4595,8 +5514,8 @@ snapshots: unist-util-visit: 5.0.0 unstorage: 1.16.1 vfile: 6.0.3 - vite: 6.3.5 - vitefu: 1.1.1(vite@6.3.5) + vite: 6.3.5(jiti@2.5.1)(lightningcss@1.30.1) + vitefu: 1.1.1(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)) xxhash-wasm: 1.1.0 yargs-parser: 21.1.1 yocto-spinner: 0.2.3 @@ -4736,6 +5655,8 @@ snapshots: dependencies: readdirp: 4.1.2 + chownr@3.0.0: {} + ci-info@3.9.0: {} ci-info@4.3.0: {} @@ -4778,6 +5699,8 @@ snapshots: cookie-es@1.2.2: {} + cookie@0.6.0: {} + cookie@1.0.2: {} cross-fetch@3.2.0: @@ -4837,6 +5760,8 @@ snapshots: deep-is@0.1.4: {} + deepmerge@4.3.1: {} + define-data-property@1.1.4: dependencies: es-define-property: 1.0.1 @@ -4857,8 +5782,7 @@ snapshots: detect-indent@6.1.0: {} - detect-libc@2.0.4: - optional: true + detect-libc@2.0.4: {} deterministic-object-hash@2.0.2: dependencies: @@ -4904,6 +5828,11 @@ snapshots: emoji-regex@9.2.2: {} + enhanced-resolve@5.18.3: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.2 + enquirer@2.4.1: dependencies: ansi-colors: 4.1.3 @@ -5072,6 +6001,26 @@ snapshots: object.assign: 4.1.7 object.entries: 1.1.9 + eslint-config-next@15.2.4(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3): + dependencies: + '@next/eslint-plugin-next': 15.2.4 + '@rushstack/eslint-patch': 1.12.0 + '@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) + '@typescript-eslint/parser': 5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) + eslint: 9.33.0(jiti@2.5.1) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.33.0(jiti@2.5.1)) + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.33.0(jiti@2.5.1)) + eslint-plugin-react: 7.37.5(eslint@9.33.0(jiti@2.5.1)) + eslint-plugin-react-hooks: 5.2.0(eslint@9.33.0(jiti@2.5.1)) + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - eslint-import-resolver-webpack + - eslint-plugin-import-x + - supports-color + eslint-config-next@15.4.5(eslint@8.57.1)(typescript@5.8.3): dependencies: '@next/eslint-plugin-next': 15.4.5 @@ -5080,7 +6029,7 @@ snapshots: '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.8.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.5(eslint@8.57.1) @@ -5104,7 +6053,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.1): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1 @@ -5119,14 +6068,40 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.33.0(jiti@2.5.1)): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.1 + eslint: 9.33.0(jiti@2.5.1) + get-tsconfig: 4.10.1 + is-bun-module: 2.0.0 + stable-hash: 0.0.5 + tinyglobby: 0.2.14 + unrs-resolver: 1.11.1 + optionalDependencies: + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.8.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) + eslint: 9.33.0(jiti@2.5.1) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.33.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color @@ -5141,7 +6116,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -5159,6 +6134,35 @@ snapshots: - eslint-import-resolver-webpack - supports-color + eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.33.0(jiti@2.5.1) + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1): dependencies: aria-query: 5.3.2 @@ -5178,6 +6182,25 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 + eslint-plugin-jsx-a11y@6.10.2(eslint@9.33.0(jiti@2.5.1)): + dependencies: + aria-query: 5.3.2 + array-includes: 3.1.8 + array.prototype.flatmap: 1.3.3 + ast-types-flow: 0.0.8 + axe-core: 4.10.3 + axobject-query: 4.1.0 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + eslint: 9.33.0(jiti@2.5.1) + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + language-tags: 1.0.9 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + safe-regex-test: 1.1.0 + string.prototype.includes: 2.0.1 + eslint-plugin-react-hooks@4.6.2(eslint@8.57.1): dependencies: eslint: 8.57.1 @@ -5186,6 +6209,10 @@ snapshots: dependencies: eslint: 8.57.1 + eslint-plugin-react-hooks@5.2.0(eslint@9.33.0(jiti@2.5.1)): + dependencies: + eslint: 9.33.0(jiti@2.5.1) + eslint-plugin-react@7.37.5(eslint@8.57.1): dependencies: array-includes: 3.1.8 @@ -5208,6 +6235,28 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 + eslint-plugin-react@7.37.5(eslint@9.33.0(jiti@2.5.1)): + dependencies: + array-includes: 3.1.8 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.2.1 + eslint: 9.33.0(jiti@2.5.1) + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + eslint-plugin-simple-import-sort@7.0.0(eslint@8.57.1): dependencies: eslint: 8.57.1 @@ -5222,8 +6271,15 @@ snapshots: esrecurse: 4.3.0 estraverse: 5.3.0 + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + eslint-visitor-keys@3.4.3: {} + eslint-visitor-keys@4.2.1: {} + eslint@8.57.1: dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@8.57.1) @@ -5267,6 +6323,56 @@ snapshots: transitivePeerDependencies: - supports-color + eslint@9.33.0(jiti@2.5.1): + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.33.0(jiti@2.5.1)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.21.0 + '@eslint/config-helpers': 0.3.1 + '@eslint/core': 0.15.2 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.33.0 + '@eslint/plugin-kit': 0.3.5 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.1 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.5.1 + transitivePeerDependencies: + - supports-color + + esm-env@1.2.2: {} + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + espree@9.6.1: dependencies: acorn: 8.14.1 @@ -5279,6 +6385,10 @@ snapshots: dependencies: estraverse: 5.3.0 + esrap@2.1.0: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.4 + esrecurse@4.3.0: dependencies: estraverse: 5.3.0 @@ -5343,6 +6453,10 @@ snapshots: dependencies: flat-cache: 3.2.0 + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -5363,6 +6477,11 @@ snapshots: keyv: 4.5.4 rimraf: 3.0.2 + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + flatted@3.3.3: {} flattie@1.1.1: {} @@ -5471,6 +6590,8 @@ snapshots: dependencies: type-fest: 0.20.2 + globals@14.0.0: {} + globalthis@1.0.4: dependencies: define-properties: 1.2.1 @@ -5746,6 +6867,10 @@ snapshots: is-plain-obj@4.1.0: {} + is-reference@3.0.3: + dependencies: + '@types/estree': 1.0.8 + is-regex@1.2.1: dependencies: call-bound: 1.0.4 @@ -5808,6 +6933,8 @@ snapshots: has-symbols: 1.1.0 set-function-name: 2.0.2 + jiti@2.5.1: {} + js-tokens@4.0.0: {} js-yaml@3.14.1: @@ -5859,6 +6986,53 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lightningcss-darwin-arm64@1.30.1: + optional: true + + lightningcss-darwin-x64@1.30.1: + optional: true + + lightningcss-freebsd-x64@1.30.1: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.1: + optional: true + + lightningcss-linux-arm64-gnu@1.30.1: + optional: true + + lightningcss-linux-arm64-musl@1.30.1: + optional: true + + lightningcss-linux-x64-gnu@1.30.1: + optional: true + + lightningcss-linux-x64-musl@1.30.1: + optional: true + + lightningcss-win32-arm64-msvc@1.30.1: + optional: true + + lightningcss-win32-x64-msvc@1.30.1: + optional: true + + lightningcss@1.30.1: + dependencies: + detect-libc: 2.0.4 + optionalDependencies: + lightningcss-darwin-arm64: 1.30.1 + lightningcss-darwin-x64: 1.30.1 + lightningcss-freebsd-x64: 1.30.1 + lightningcss-linux-arm-gnueabihf: 1.30.1 + lightningcss-linux-arm64-gnu: 1.30.1 + lightningcss-linux-arm64-musl: 1.30.1 + lightningcss-linux-x64-gnu: 1.30.1 + lightningcss-linux-x64-musl: 1.30.1 + lightningcss-win32-arm64-msvc: 1.30.1 + lightningcss-win32-x64-msvc: 1.30.1 + + locate-character@3.0.0: {} + locate-path@5.0.0: dependencies: p-locate: 4.1.0 @@ -6221,6 +7395,14 @@ snapshots: minimist@1.2.8: {} + minipass@7.1.2: {} + + minizlib@3.0.2: + dependencies: + minipass: 7.1.2 + + mkdirp@3.0.1: {} + mri@1.2.0: {} mrmime@1.0.1: {} @@ -6239,7 +7421,7 @@ snapshots: neotraverse@0.6.18: {} - next@14.2.31(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next@14.2.31(react-dom@19.1.1(react@18.3.1))(react@18.3.1): dependencies: '@next/env': 14.2.31 '@swc/helpers': 0.5.5 @@ -6248,7 +7430,7 @@ snapshots: graceful-fs: 4.2.11 postcss: 8.4.31 react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react-dom: 19.1.1(react@18.3.1) styled-jsx: 5.1.1(react@18.3.1) optionalDependencies: '@next/swc-darwin-arm64': 14.2.31 @@ -6264,6 +7446,31 @@ snapshots: - '@babel/core' - babel-plugin-macros + next@15.2.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + '@next/env': 15.2.4 + '@swc/counter': 0.1.3 + '@swc/helpers': 0.5.15 + busboy: 1.6.0 + caniuse-lite: 1.0.30001731 + postcss: 8.4.31 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + styled-jsx: 5.1.6(react@19.1.1) + optionalDependencies: + '@next/swc-darwin-arm64': 15.2.4 + '@next/swc-darwin-x64': 15.2.4 + '@next/swc-linux-arm64-gnu': 15.2.4 + '@next/swc-linux-arm64-musl': 15.2.4 + '@next/swc-linux-x64-gnu': 15.2.4 + '@next/swc-linux-x64-musl': 15.2.4 + '@next/swc-win32-arm64-msvc': 15.2.4 + '@next/swc-win32-x64-msvc': 15.2.4 + sharp: 0.33.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + next@15.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@next/env': 15.4.5 @@ -6515,6 +7722,16 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 + react-dom@19.1.1(react@18.3.1): + dependencies: + react: 18.3.1 + scheduler: 0.26.0 + + react-dom@19.1.1(react@19.1.1): + dependencies: + react: 19.1.1 + scheduler: 0.26.0 + react-is@16.13.1: {} react-refresh@0.14.2: {} @@ -6523,6 +7740,8 @@ snapshots: dependencies: loose-envify: 1.4.0 + react@19.1.1: {} + read-yaml-file@1.1.0: dependencies: graceful-fs: 4.2.11 @@ -6711,6 +7930,10 @@ snapshots: dependencies: queue-microtask: 1.2.3 + sade@1.8.1: + dependencies: + mri: 1.2.0 + safe-array-concat@1.1.3: dependencies: call-bind: 1.0.8 @@ -6736,10 +7959,14 @@ snapshots: dependencies: loose-envify: 1.4.0 + scheduler@0.26.0: {} + semver@6.3.1: {} semver@7.7.2: {} + set-cookie-parser@2.7.1: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -6877,6 +8104,12 @@ snapshots: mrmime: 1.0.1 totalist: 1.1.0 + sirv@3.0.1: + dependencies: + '@polka/url': 1.0.0-next.29 + mrmime: 2.0.1 + totalist: 3.0.1 + sisteransi@1.0.5: {} slash@3.0.0: {} @@ -6987,12 +8220,59 @@ snapshots: client-only: 0.0.1 react: 18.3.1 + styled-jsx@5.1.6(react@19.1.1): + dependencies: + client-only: 0.0.1 + react: 19.1.1 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 supports-preserve-symlinks-flag@1.0.0: {} + svelte-check@4.3.1(picomatch@4.0.3)(svelte@5.38.1)(typescript@5.8.3): + dependencies: + '@jridgewell/trace-mapping': 0.3.30 + chokidar: 4.0.3 + fdir: 6.4.6(picomatch@4.0.3) + picocolors: 1.1.1 + sade: 1.8.1 + svelte: 5.38.1 + typescript: 5.8.3 + transitivePeerDependencies: + - picomatch + + svelte@5.38.1: + dependencies: + '@jridgewell/remapping': 2.3.5 + '@jridgewell/sourcemap-codec': 1.5.4 + '@sveltejs/acorn-typescript': 1.0.5(acorn@8.14.1) + '@types/estree': 1.0.8 + acorn: 8.14.1 + aria-query: 5.3.2 + axobject-query: 4.1.0 + clsx: 2.1.1 + esm-env: 1.2.2 + esrap: 2.1.0 + is-reference: 3.0.3 + locate-character: 3.0.0 + magic-string: 0.30.17 + zimmerframe: 1.1.2 + + tailwindcss@4.1.11: {} + + tapable@2.2.2: {} + + tar@7.4.3: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.0.2 + mkdirp: 3.0.1 + yallist: 5.0.0 + term-size@2.2.1: {} text-table@0.2.0: {} @@ -7016,6 +8296,8 @@ snapshots: totalist@1.1.0: {} + totalist@3.0.1: {} + tr46@0.0.3: {} trim-lines@3.0.1: {} @@ -7222,7 +8504,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite@6.3.5: + vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1): dependencies: esbuild: 0.25.8 fdir: 6.4.6(picomatch@4.0.3) @@ -7232,10 +8514,12 @@ snapshots: tinyglobby: 0.2.14 optionalDependencies: fsevents: 2.3.3 + jiti: 2.5.1 + lightningcss: 1.30.1 - vitefu@1.1.1(vite@6.3.5): + vitefu@1.1.1(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)): optionalDependencies: - vite: 6.3.5 + vite: 6.3.5(jiti@2.5.1)(lightningcss@1.30.1) web-namespaces@2.0.1: {} @@ -7312,6 +8596,8 @@ snapshots: dependencies: string-width: 7.2.0 + wonka@6.3.5: {} + word-wrap@1.2.5: {} wrap-ansi@9.0.0: @@ -7326,6 +8612,8 @@ snapshots: xxhash-wasm@1.1.0: {} + yallist@5.0.0: {} + yargs-parser@21.1.1: {} yocto-queue@0.1.0: {} @@ -7338,6 +8626,8 @@ snapshots: yoctocolors@2.1.1: {} + zimmerframe@1.1.2: {} + zod-to-json-schema@3.24.6(zod@3.25.76): dependencies: zod: 3.25.76 From 404576da0312b8fdf357a4a7cf3f8a4ff23b2ae5 Mon Sep 17 00:00:00 2001 From: ahuseyn Date: Wed, 13 Aug 2025 14:36:10 +0200 Subject: [PATCH 02/24] update gql client, add headers param --- packages/graphql/client.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/graphql/client.js b/packages/graphql/client.js index 6a80e8672..2ad3cfeca 100644 --- a/packages/graphql/client.js +++ b/packages/graphql/client.js @@ -38,7 +38,7 @@ export function buildGraphQLEndpoint(wordpressUrl) { * @returns {import('./types.js').GraphQLClient} A basic GraphQL client * @throws {Error} If no WordPress URL is provided */ -export function createDefaultGraphQLClient(wordpressUrl) { +export function createDefaultGraphQLClient(wordpressUrl, headers = {}) { if (!wordpressUrl) { throw new Error( 'WordPress URL is required to create a default GraphQL client.', @@ -54,6 +54,7 @@ export function createDefaultGraphQLClient(wordpressUrl) { method: 'POST', headers: { 'Content-Type': 'application/json', + ...headers, }, body: JSON.stringify({ query, variables }), }); From 2437c6aee416b8733f83c253d2b7c747e5741136 Mon Sep 17 00:00:00 2001 From: ahuseyn Date: Wed, 13 Aug 2025 14:36:38 +0200 Subject: [PATCH 03/24] update template-hierarchy, add id and asPreview --- packages/nextjs/pages/templateHierarchy.js | 10 ++++++++-- packages/template-hierarchy/seedQueryExecutor.js | 11 +++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/nextjs/pages/templateHierarchy.js b/packages/nextjs/pages/templateHierarchy.js index 2e91c685c..3014cc904 100644 --- a/packages/nextjs/pages/templateHierarchy.js +++ b/packages/nextjs/pages/templateHierarchy.js @@ -20,6 +20,8 @@ import { getGraphQLClient } from '@faustjs/graphql'; */ export async function uriToTemplate({ uri, + id, + asPreview, availableTemplates, graphqlClient, wordpressUrl, @@ -37,17 +39,21 @@ export async function uriToTemplate({ const client = getGraphQLClient(graphqlClient); const { data, error } = await getSeedQuery({ uri, + id, + asPreview, graphqlClient: client, }); returnData.seedQuery = { data, error }; + const seedNode = data?.nodeByUri || data?.contentNode; + if (error) { console.error('Error fetching seedQuery:', error); return returnData; } - if (!data?.nodeByUri) { + if (!seedNode) { console.error('HTTP/404 - Not Found in WordPress:', uri); return returnData; // Let Next.js handle 404s } @@ -59,7 +65,7 @@ export async function uriToTemplate({ return returnData; } - const possibleTemplates = getPossibleTemplates(data.nodeByUri); + const possibleTemplates = getPossibleTemplates(seedNode); returnData.possibleTemplates = possibleTemplates; diff --git a/packages/template-hierarchy/seedQueryExecutor.js b/packages/template-hierarchy/seedQueryExecutor.js index 16181e737..15a4a4dd5 100644 --- a/packages/template-hierarchy/seedQueryExecutor.js +++ b/packages/template-hierarchy/seedQueryExecutor.js @@ -10,13 +10,20 @@ import { print } from 'graphql'; * @param {import('./types.js').SeedQueryOptions} options - Query options * @returns {Promise} Query result */ -export async function getSeedQuery({ uri, graphqlClient }) { +export async function getSeedQuery({ uri, id, asPreview, graphqlClient }) { if (!graphqlClient) { throw new Error('GraphQL client is required for getSeedQuery'); } try { - const result = await graphqlClient.request(print(SEED_QUERY), { uri }); + const result = await graphqlClient.request(print(SEED_QUERY), { + uri, + id, + asPreview, + }); + + console.log('Seed query result:', uri, id, asPreview, result); + return { data: result.data || result, error: result.error || null, From f66dcc03ba66c827e2ccf03f87612e395917092d Mon Sep 17 00:00:00 2001 From: ahuseyn Date: Wed, 13 Aug 2025 15:15:45 +0200 Subject: [PATCH 04/24] add preview utility to @faustjs/nextjs package --- .../previews/src/pages/[...identifier].js | 2 +- .../nextjs/previews/src/pages/api/preview.js | 60 ++------------- packages/nextjs/pages/enablePreview.js | 73 +++++++++++++++++++ packages/nextjs/pages/index.js | 3 + 4 files changed, 85 insertions(+), 53 deletions(-) create mode 100644 packages/nextjs/pages/enablePreview.js diff --git a/examples/nextjs/previews/src/pages/[...identifier].js b/examples/nextjs/previews/src/pages/[...identifier].js index 76dd4e5a4..59f5a86cb 100644 --- a/examples/nextjs/previews/src/pages/[...identifier].js +++ b/examples/nextjs/previews/src/pages/[...identifier].js @@ -41,7 +41,7 @@ export async function getStaticProps({ const variables = isDraftModeEnabled ? { id: params.identifier?.[0], - isPreview: true, + asPreview: true, } : { uri }; diff --git a/examples/nextjs/previews/src/pages/api/preview.js b/examples/nextjs/previews/src/pages/api/preview.js index f6dfd1f09..7cbca238b 100644 --- a/examples/nextjs/previews/src/pages/api/preview.js +++ b/examples/nextjs/previews/src/pages/api/preview.js @@ -1,53 +1,9 @@ import { getAuthString } from '@/utils/getAuthString'; -import { createDefaultClient, setGraphQLClient } from '@faustjs/nextjs/pages'; -import { gql } from 'graphql-tag'; -import { print } from 'graphql'; - -const GET_CONTENT = gql` - query GetNode($id: ID! = 0) { - contentNode(id: $id, idType: DATABASE_ID, asPreview: true) { - databaseId - } - } -`; - -export default async function handler(req, res) { - const { secret, id } = req.query; - - if (!id) { - return res.status(400).json({ message: 'No ID provided.' }); - } - - // Check if preview secret token exists and matches environment variable - if (secret !== process.env.WP_PREVIEW_SECRET) { - return res.status(401).json({ message: 'Secret token is invalid.' }); - } - - const client = createDefaultClient(process.env.NEXT_PUBLIC_WORDPRESS_URL, { - Authorization: getAuthString(), - }); - setGraphQLClient(client); - - const { data, error } = await client.request(print(GET_CONTENT), { - id, - }); - - if (error) { - console.error('Error fetching content:', error); - return res.status(500).json({ message: 'Error fetching content.' }); - } - - if (!data?.contentNode) { - return res.status(404).json({ - message: 'Content could not be found. Verify your authentication method.', - }); - } - - // Enable draft mode - // More info: https://nextjs.org/docs/pages/guides/draft-mode#step-1-create-and-access-the-api-route - res.setDraftMode({ enable: true }); - - // Redirect with the databaseId retrieved from the query to prevent redirect at - // More info: https://developers.google.com/search/blog/2009/01/open-redirect-urls-is-your-site-being - res.redirect('/' + data?.contentNode?.databaseId); -} +import { enablePreview } from '@faustjs/nextjs/pages'; + +export default enablePreview({ + wordpressUrl: process.env.NEXT_PUBLIC_WORDPRESS_URL, + expectedSecret: process.env.WP_PREVIEW_SECRET, + // graphqlClient: client, + bearerToken: getAuthString(), +}); diff --git a/packages/nextjs/pages/enablePreview.js b/packages/nextjs/pages/enablePreview.js new file mode 100644 index 000000000..0ef10afdf --- /dev/null +++ b/packages/nextjs/pages/enablePreview.js @@ -0,0 +1,73 @@ +import { createDefaultClient, setGraphQLClient } from '@faustjs/nextjs/pages'; +import { print } from 'graphql'; +import { gql } from 'graphql-tag'; + +const VERIFY_NODE = gql` + query GetNode($id: ID! = 0) { + contentNode(id: $id, idType: DATABASE_ID, asPreview: true) { + databaseId + } + } +`; + +export function enablePreview({ + wordpressUrl, + expectedSecret, + secretParamName = 'secret', + idParamName = 'id', + graphqlClient, + bearerToken, +}) { + return async function handler(req, res) { + const id = req.query[idParamName]; + const secret = req.query[secretParamName]; + + if (!id) { + return res.status(400).json({ message: 'No ID received.' }); + } + + // Check if preview secret token exists and matches environment variable + if (secret !== expectedSecret) { + return res.status(401).json({ message: 'Secret token is invalid.' }); + } + + if (!wordpressUrl) { + return res + .status(500) + .json({ message: 'WordPress URL is not configured.' }); + } + + let client; + + if (graphqlClient) { + client = graphqlClient; + } else { + client = createDefaultClient(wordpressUrl, { + Authorization: bearerToken, + }); + setGraphQLClient(client); + } + + const { data, error } = await client.request(print(VERIFY_NODE), { + id, + }); + + if (error) { + console.error('Error fetching content:', error); + return res.status(500).json({ message: 'Error fetching content.' }); + } + + if (!data?.contentNode) { + return res.status(404).json({ + message: + 'Content could not be found. Verify your authentication method.', + }); + } + + // Enable draft mode + res.setDraftMode({ enable: true }); + + // Redirect with the databaseId retrieved from the query + res.redirect('/' + data?.contentNode?.databaseId); + }; +} diff --git a/packages/nextjs/pages/index.js b/packages/nextjs/pages/index.js index a4dababf2..12278c47f 100644 --- a/packages/nextjs/pages/index.js +++ b/packages/nextjs/pages/index.js @@ -8,6 +8,9 @@ import './types.js'; // Export template hierarchy utilities export { uriToTemplate } from './templateHierarchy.js'; +// Export Next.js preview enabling api handler +export { enablePreview } from './enablePreview.js'; + // Export GraphQL client configuration // Export GraphQL client configuration export { From ebe7a5dce675f0d44e287b646521d9b25963cf02 Mon Sep 17 00:00:00 2001 From: ahuseyn Date: Tue, 19 Aug 2025 14:04:05 +0200 Subject: [PATCH 05/24] initial --- examples/nextjs/previews/package.json | 1 + .../previews/src/pages/[...identifier].js | 12 ++++- .../nextjs/previews/src/queries/getLayout.js | 10 ++++ .../nextjs/previews/src/queries/getPost.js | 27 ++++++++++ .../nextjs/previews/src/wp-templates/page.js | 2 +- .../previews/src/wp-templates/single.js | 16 ++++++ .../src/wp-templates/templateQueries.js | 3 ++ packages/data-fetching/README.md | 1 + .../data-fetching/fetchTemplateQueries.js | 49 +++++++++++++++++++ packages/data-fetching/index.js | 6 +++ packages/data-fetching/package.json | 23 +++++++++ packages/nextjs/pages/templateHierarchy.js | 5 +- pnpm-lock.yaml | 22 +++++++-- 13 files changed, 169 insertions(+), 8 deletions(-) create mode 100644 examples/nextjs/previews/src/queries/getLayout.js create mode 100644 examples/nextjs/previews/src/queries/getPost.js create mode 100644 examples/nextjs/previews/src/wp-templates/templateQueries.js create mode 100644 packages/data-fetching/README.md create mode 100644 packages/data-fetching/fetchTemplateQueries.js create mode 100644 packages/data-fetching/index.js create mode 100644 packages/data-fetching/package.json diff --git a/examples/nextjs/previews/package.json b/examples/nextjs/previews/package.json index ea7058a46..e2fb03d82 100644 --- a/examples/nextjs/previews/package.json +++ b/examples/nextjs/previews/package.json @@ -11,6 +11,7 @@ "dependencies": { "@faustjs/nextjs": "workspace:*", "@faustjs/template-hierarchy": "workspace:*", + "@faustjs/data-fetching": "workspace:*", "graphql": "^16.11.0", "graphql-tag": "^2.12.6", "next": "15.2.4", diff --git a/examples/nextjs/previews/src/pages/[...identifier].js b/examples/nextjs/previews/src/pages/[...identifier].js index 59f5a86cb..5917c95ed 100644 --- a/examples/nextjs/previews/src/pages/[...identifier].js +++ b/examples/nextjs/previews/src/pages/[...identifier].js @@ -1,13 +1,15 @@ import { getAuthString } from '@/utils/getAuthString'; import availableTemplates from '@/wp-templates'; +import availableQueries from '@/wp-templates/templateQueries'; import { createDefaultClient, setGraphQLClient, uriToTemplate, } from '@faustjs/nextjs/pages'; +import { fetchTemplateQueries } from '@faustjs/data-fetching'; export default function Page(props) { - const { templateData } = props; + const { templateData, queriesData } = props; const PageTemplate = availableTemplates[templateData?.template?.id]; @@ -59,10 +61,18 @@ export async function getStaticProps({ return { notFound: true }; } + const queriesData = await fetchTemplateQueries({ + availableQueries, + templateData, + client, + locale: templateData?.seedNode?.locale, + }); + return { props: { uri, templateData: JSON.parse(JSON.stringify(templateData)), + queriesData, }, }; } catch (error) { diff --git a/examples/nextjs/previews/src/queries/getLayout.js b/examples/nextjs/previews/src/queries/getLayout.js new file mode 100644 index 000000000..7f1b7f012 --- /dev/null +++ b/examples/nextjs/previews/src/queries/getLayout.js @@ -0,0 +1,10 @@ +const { default: gql } = require('graphql-tag'); + +export const GET_LAYOUT = gql` + query GetLayout { + generalSettings { + title + description + } + } +`; diff --git a/examples/nextjs/previews/src/queries/getPost.js b/examples/nextjs/previews/src/queries/getPost.js new file mode 100644 index 000000000..30ba7b8d7 --- /dev/null +++ b/examples/nextjs/previews/src/queries/getPost.js @@ -0,0 +1,27 @@ +import gql from 'graphql-tag'; + +export const GET_POST = gql` + query GetPost($databaseId: ID!, $asPreview: Boolean = false) { + post(id: $databaseId, idType: DATABASE_ID, asPreview: $asPreview) { + title + content + date + author { + node { + name + } + } + featuredImage { + node { + id + sourceUrl + altText + mediaDetails { + width + height + } + } + } + } + } +`; diff --git a/examples/nextjs/previews/src/wp-templates/page.js b/examples/nextjs/previews/src/wp-templates/page.js index f604fc64c..334e5de77 100644 --- a/examples/nextjs/previews/src/wp-templates/page.js +++ b/examples/nextjs/previews/src/wp-templates/page.js @@ -1,4 +1,4 @@ -export default function PageTemplate({ templateData }) { +export default function Component({ templateData }) { const { seedQuery } = templateData || {}; const page = seedQuery?.data?.page; diff --git a/examples/nextjs/previews/src/wp-templates/single.js b/examples/nextjs/previews/src/wp-templates/single.js index d1f53a512..36b82be56 100644 --- a/examples/nextjs/previews/src/wp-templates/single.js +++ b/examples/nextjs/previews/src/wp-templates/single.js @@ -1,3 +1,6 @@ +import { GET_LAYOUT } from '@/queries/getLayout'; +import { GET_POST } from '@/queries/getPost'; + export default function SingleTemplate({ templateData }) { const { seedQuery } = templateData || {}; const post = seedQuery?.data?.post; @@ -34,3 +37,16 @@ export default function SingleTemplate({ templateData }) { ); } + +export const queries = [ + { + query: GET_LAYOUT, + }, + { + query: GET_POST, + variables: ({ databaseId }, ctx) => ({ + databaseId, + asPreview: ctx?.asPreview, + }), + }, +]; diff --git a/examples/nextjs/previews/src/wp-templates/templateQueries.js b/examples/nextjs/previews/src/wp-templates/templateQueries.js new file mode 100644 index 000000000..ac3c0b582 --- /dev/null +++ b/examples/nextjs/previews/src/wp-templates/templateQueries.js @@ -0,0 +1,3 @@ +import { queries as single } from './single'; + +export default { single }; diff --git a/packages/data-fetching/README.md b/packages/data-fetching/README.md new file mode 100644 index 000000000..14294dfc8 --- /dev/null +++ b/packages/data-fetching/README.md @@ -0,0 +1 @@ +# @faustjs/data-fetching diff --git a/packages/data-fetching/fetchTemplateQueries.js b/packages/data-fetching/fetchTemplateQueries.js new file mode 100644 index 000000000..be82acda3 --- /dev/null +++ b/packages/data-fetching/fetchTemplateQueries.js @@ -0,0 +1,49 @@ +/** + * @file Fetches template-specific GraphQL queries for FaustJS templates + */ + +/** + * @param {Object} params + * @param {Object.} params.availableQueries - Map of template IDs to their queries + * @param {Object} params.templateData - Template data with seedNode and template info + * @param {Object} params.client - GraphQL client with request method + * @param {Object} [params.extraVariables={}] - Additional variables for queries + * @param {string} [params.locale] - Locale for internationalization + * @returns {Promise} Array of query results or undefined + */ +export async function fetchTemplateQueries({ + availableQueries, + templateData, + client, + extraVariables = {}, + locale, +}) { + if (!client) { + throw new Error('GraphQL client is required to fetch template queries'); + } + + // Get the template queries for the current template + const templateQueries = availableQueries[templateData?.template?.id]; + + if (templateQueries) { + const queryCalls = templateQueries.map(({ query, variables }) => { + const queryVariables = variables + ? variables( + templateData.seedNode, + { + asPreview: false, + locale, + }, + extraVariables, + ) + : undefined; + + return client.request({ + query, + variables: queryVariables, + }); + }); + + return await Promise.all(queryCalls); + } +} diff --git a/packages/data-fetching/index.js b/packages/data-fetching/index.js new file mode 100644 index 000000000..748305b01 --- /dev/null +++ b/packages/data-fetching/index.js @@ -0,0 +1,6 @@ +/** + * @file Main entry point for the data-fetching package + */ + +// Export data fetching utilities +export { fetchTemplateQueries } from './fetchTemplateQueries.js'; diff --git a/packages/data-fetching/package.json b/packages/data-fetching/package.json new file mode 100644 index 000000000..13e94bacc --- /dev/null +++ b/packages/data-fetching/package.json @@ -0,0 +1,23 @@ +{ + "name": "@faustjs/data-fetching", + "version": "0.0.1-alpha.0", + "description": "Data fetching utilities for FaustJS", + "type": "module", + "exports": { + ".": "./index.js" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "MIT", + "packageManager": "pnpm@10.14.0", + "engines": { + "node": ">=24" + }, + "peerDependencies": { + "graphql": "^16.0.0", + "graphql-tag": "^2.12.0" + } +} diff --git a/packages/nextjs/pages/templateHierarchy.js b/packages/nextjs/pages/templateHierarchy.js index 3014cc904..e9afbc41b 100644 --- a/packages/nextjs/pages/templateHierarchy.js +++ b/packages/nextjs/pages/templateHierarchy.js @@ -29,10 +29,11 @@ export async function uriToTemplate({ /** @type {import('../types.js').NextJSTemplateData} */ const returnData = { uri, - seedQuery: undefined, + seedQuery: undefined, // TODO remove in favor of seedNode availableTemplates: undefined, possibleTemplates: undefined, template: undefined, + seedNode: undefined, }; // Get the GraphQL client - use provided one or get configured one @@ -48,6 +49,8 @@ export async function uriToTemplate({ const seedNode = data?.nodeByUri || data?.contentNode; + returnData.seedNode = seedNode ?? error; + if (error) { console.error('Error fetching seedQuery:', error); return returnData; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 22b1a97fa..dad6121ba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -90,6 +90,9 @@ importers: examples/nextjs/previews: dependencies: + '@faustjs/data-fetching': + specifier: workspace:* + version: link:../../../packages/data-fetching '@faustjs/nextjs': specifier: workspace:* version: link:../../../packages/nextjs @@ -214,6 +217,15 @@ importers: specifier: ^16.8.1 version: 16.11.0 + packages/data-fetching: + dependencies: + graphql: + specifier: ^16.0.0 + version: 16.11.0 + graphql-tag: + specifier: ^2.12.0 + version: 2.12.6(graphql@16.11.0) + packages/graphql: {} packages/nextjs: @@ -6009,7 +6021,7 @@ snapshots: '@typescript-eslint/parser': 5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) eslint: 9.33.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.33.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.33.0(jiti@2.5.1)) eslint-plugin-react: 7.37.5(eslint@9.33.0(jiti@2.5.1)) @@ -6068,7 +6080,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.33.0(jiti@2.5.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1 @@ -6094,14 +6106,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) eslint: 9.33.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.33.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color @@ -6145,7 +6157,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.33.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 From 01c7132072f12f350d43d5655a3b9bb8fbe9ac48 Mon Sep 17 00:00:00 2001 From: ahuseyn Date: Tue, 19 Aug 2025 15:37:13 +0200 Subject: [PATCH 06/24] new templates --- .../previews/src/pages/[...identifier].js | 2 +- .../nextjs/previews/src/queries/getArchive.js | 68 ++++++++++++++ .../nextjs/previews/src/queries/getPage.js | 27 ++++++ examples/nextjs/previews/src/queries/index.js | 5 + .../previews/src/wp-templates/archive.js | 93 ++++++++++++------- .../nextjs/previews/src/wp-templates/page.js | 62 +++++++++---- .../previews/src/wp-templates/single.js | 58 ++++++------ .../src/wp-templates/templateQueries.js | 3 - .../data-fetching/fetchTemplateQueries.js | 22 +++-- packages/graphql/client.js | 12 ++- 10 files changed, 261 insertions(+), 91 deletions(-) create mode 100644 examples/nextjs/previews/src/queries/getArchive.js create mode 100644 examples/nextjs/previews/src/queries/getPage.js create mode 100644 examples/nextjs/previews/src/queries/index.js delete mode 100644 examples/nextjs/previews/src/wp-templates/templateQueries.js diff --git a/examples/nextjs/previews/src/pages/[...identifier].js b/examples/nextjs/previews/src/pages/[...identifier].js index 5917c95ed..49682e944 100644 --- a/examples/nextjs/previews/src/pages/[...identifier].js +++ b/examples/nextjs/previews/src/pages/[...identifier].js @@ -1,6 +1,6 @@ import { getAuthString } from '@/utils/getAuthString'; import availableTemplates from '@/wp-templates'; -import availableQueries from '@/wp-templates/templateQueries'; +import availableQueries from '@/queries'; import { createDefaultClient, setGraphQLClient, diff --git a/examples/nextjs/previews/src/queries/getArchive.js b/examples/nextjs/previews/src/queries/getArchive.js new file mode 100644 index 000000000..fc59b7f14 --- /dev/null +++ b/examples/nextjs/previews/src/queries/getArchive.js @@ -0,0 +1,68 @@ +import gql from 'graphql-tag'; + +export const GET_ARCHIVE = gql` + query GetArchivePage($uri: String!) { + nodeByUri(uri: $uri) { + ... on Category { + name + posts { + edges { + node { + id + title + content + date + uri + featuredImage { + node { + id + sourceUrl + altText + mediaDetails { + width + height + } + } + } + author { + node { + name + } + } + } + } + } + } + ... on Tag { + name + posts { + edges { + node { + id + title + content + date + uri + featuredImage { + node { + id + sourceUrl + altText + mediaDetails { + width + height + } + } + } + author { + node { + name + } + } + } + } + } + } + } + } +`; diff --git a/examples/nextjs/previews/src/queries/getPage.js b/examples/nextjs/previews/src/queries/getPage.js new file mode 100644 index 000000000..76646f1a5 --- /dev/null +++ b/examples/nextjs/previews/src/queries/getPage.js @@ -0,0 +1,27 @@ +import gql from 'graphql-tag'; + +export const GET_PAGE = gql` + query GetPage($databaseId: ID!, $asPreview: Boolean = false) { + page(id: $databaseId, idType: DATABASE_ID, asPreview: $asPreview) { + title + content + date + author { + node { + name + } + } + featuredImage { + node { + id + sourceUrl + altText + mediaDetails { + width + height + } + } + } + } + } +`; diff --git a/examples/nextjs/previews/src/queries/index.js b/examples/nextjs/previews/src/queries/index.js new file mode 100644 index 000000000..5b677233e --- /dev/null +++ b/examples/nextjs/previews/src/queries/index.js @@ -0,0 +1,5 @@ +import { queries as single } from '../wp-templates/single'; +import { queries as page } from '../wp-templates/page'; +import { queries as archive } from '../wp-templates/archive'; + +export default { single, page, archive }; diff --git a/examples/nextjs/previews/src/wp-templates/archive.js b/examples/nextjs/previews/src/wp-templates/archive.js index 1ce8e4951..6417d07a2 100644 --- a/examples/nextjs/previews/src/wp-templates/archive.js +++ b/examples/nextjs/previews/src/wp-templates/archive.js @@ -1,38 +1,69 @@ -export default function ArchiveTemplate({ templateData }) { - const { seedQuery } = templateData || {}; - const posts = seedQuery?.data?.posts?.nodes || []; - const archiveInfo = seedQuery?.data?.category || seedQuery?.data?.tag || {}; +import { GET_LAYOUT } from '@/queries/getLayout'; +import { GET_ARCHIVE } from '@/queries/getArchive'; +import Head from 'next/head'; + +export default function CategoryTemplate({ queriesData }) { + const { getCategory } = queriesData || {}; + const categoryData = getCategory?.data?.nodeByUri; + const { posts, name } = categoryData || {}; return ( <> -

WordPress Archive Template

-

This template would render category, tag, or other archive pages.

-
- {archiveInfo.name ? ( -

Archive: {archiveInfo.name}

- ) : ( -

Archive Page

- )} - {archiveInfo.description &&

{archiveInfo.description}

} - -

Posts in this archive:

- {posts.length > 0 ? ( - posts.map((post) => ( -
-

{post.title}

-

{post.excerpt || 'Post excerpt would appear here...'}

-
- )) - ) : ( -
-

Sample Archive Post

-

- Posts from this category, tag, or archive would be displayed here - using data from the WordPress GraphQL API. -

-
- )} + + {name} + + +
+

{name}

+ + {posts?.edges?.map((item) => { + const post = item.node; + + return ( +
+

+ + {post.title} + +

+ + {post.featuredImage && ( + {post.featuredImage.node.altText + )} + +
+ By {post.author.node.name} on{' '} + {new Date(post.date).toLocaleDateString()} +
+ +
+
+ ); + })}
); } + +export const queries = [ + { + name: 'getLayout', + query: GET_LAYOUT, + }, + { + name: 'getCategory', + query: GET_ARCHIVE, + variables: ({ uri }) => ({ + uri, + }), + }, +]; diff --git a/examples/nextjs/previews/src/wp-templates/page.js b/examples/nextjs/previews/src/wp-templates/page.js index 334e5de77..876cea1b7 100644 --- a/examples/nextjs/previews/src/wp-templates/page.js +++ b/examples/nextjs/previews/src/wp-templates/page.js @@ -1,24 +1,52 @@ -export default function Component({ templateData }) { - const { seedQuery } = templateData || {}; - const page = seedQuery?.data?.page; +import { GET_LAYOUT } from '@/queries/getLayout'; +import { GET_PAGE } from '@/queries/getPage'; +import Head from 'next/head'; + +export default function PageTemplate({ queriesData }) { + const { getPage } = queriesData || {}; + const { title, content, featuredImage } = getPage?.data?.page || {}; return ( <> -

WordPress Page Template

-

This template would render individual WordPress pages.

-
- {page ? ( - <> -

{page.title}

-
- - ) : ( -

- Page title, content, and metadata would be displayed here using data - from the WordPress GraphQL API. -

+ + {title} + + +
+
+

+ {title} +

+
+ + {featuredImage && ( + )} -
+ +
+ ); } + +export const queries = [ + { + name: 'getLayout', + query: GET_LAYOUT, + }, + { + name: 'getPage', + query: GET_PAGE, + variables: ({ databaseId }, ctx) => ({ + databaseId, + asPreview: ctx?.asPreview, + }), + }, +]; diff --git a/examples/nextjs/previews/src/wp-templates/single.js b/examples/nextjs/previews/src/wp-templates/single.js index 36b82be56..ef7479c3e 100644 --- a/examples/nextjs/previews/src/wp-templates/single.js +++ b/examples/nextjs/previews/src/wp-templates/single.js @@ -1,48 +1,48 @@ import { GET_LAYOUT } from '@/queries/getLayout'; import { GET_POST } from '@/queries/getPost'; +import Head from 'next/head'; -export default function SingleTemplate({ templateData }) { - const { seedQuery } = templateData || {}; - const post = seedQuery?.data?.post; +export default function SingleTemplate({ queriesData }) { + const { getPost } = queriesData || {}; + const { title, content, featuredImage } = getPost?.data?.post || {}; return ( <> -

WordPress Single Post Template

-

This template would render individual blog posts.

-
- {post ? ( -
-

{post.title}

-
- {post.categories?.nodes && ( -
- Categories: - {post.categories.nodes.map((cat) => cat.name).join(', ')} -
- )} - {post.tags?.nodes && ( -
- Tags: - {post.tags.nodes.map((tag) => tag.name).join(', ')} -
- )} -
- ) : ( -

- Blog post title, content, categories, tags, and other metadata would - be displayed here using data from the WordPress GraphQL API. -

+ + {title} + + +
+
+

+ {title} +

+
+ + {featuredImage && ( + )} -
+ +
+ ); } export const queries = [ { + name: 'getLayout', query: GET_LAYOUT, }, { + name: 'getPost', query: GET_POST, variables: ({ databaseId }, ctx) => ({ databaseId, diff --git a/examples/nextjs/previews/src/wp-templates/templateQueries.js b/examples/nextjs/previews/src/wp-templates/templateQueries.js deleted file mode 100644 index ac3c0b582..000000000 --- a/examples/nextjs/previews/src/wp-templates/templateQueries.js +++ /dev/null @@ -1,3 +0,0 @@ -import { queries as single } from './single'; - -export default { single }; diff --git a/packages/data-fetching/fetchTemplateQueries.js b/packages/data-fetching/fetchTemplateQueries.js index be82acda3..9db1ddb98 100644 --- a/packages/data-fetching/fetchTemplateQueries.js +++ b/packages/data-fetching/fetchTemplateQueries.js @@ -2,6 +2,8 @@ * @file Fetches template-specific GraphQL queries for FaustJS templates */ +import { print } from 'graphql'; + /** * @param {Object} params * @param {Object.} params.availableQueries - Map of template IDs to their queries @@ -15,7 +17,7 @@ export async function fetchTemplateQueries({ availableQueries, templateData, client, - extraVariables = {}, + extraVariables, locale, }) { if (!client) { @@ -38,12 +40,20 @@ export async function fetchTemplateQueries({ ) : undefined; - return client.request({ - query, - variables: queryVariables, - }); + return client.request(print(query), queryVariables); }); - return await Promise.all(queryCalls); + const results = await Promise.all(queryCalls); + + return results.reduce((acc, result, index) => { + const queryName = templateQueries[index].name ?? `query${index + 1}`; + + return { + ...acc, + [queryName]: result, + }; + }, {}); } + + return null; } diff --git a/packages/graphql/client.js b/packages/graphql/client.js index 2ad3cfeca..87552e174 100644 --- a/packages/graphql/client.js +++ b/packages/graphql/client.js @@ -60,15 +60,19 @@ export function createDefaultGraphQLClient(wordpressUrl, headers = {}) { }); if (!response.ok) { - console.error(); + const message = await response.json(); + return { - error: `GraphQL request failed: ${response.status}`, - message: await response.body.text(), + error: `GraphQL request failed: ${response.statusText}`, + message: message.errors[0]?.message || 'Unknown error', }; } const result = await response.json(); - return { data: result.data, error: result.errors?.[0]?.message }; + return { + data: result.data, + error: result.errors?.[0]?.message ?? null, + }; } catch (error) { return { error: error.message }; } From b5bf801d65408935e40425bb6d28bc7b13d5b3be Mon Sep 17 00:00:00 2001 From: ahuseyn Date: Tue, 19 Aug 2025 16:37:26 +0200 Subject: [PATCH 07/24] add homepage --- ......identifier].js => [[...identifier]].js} | 6 +- examples/nextjs/previews/src/pages/index.js | 111 ------------------ .../nextjs/previews/src/queries/getPosts.js | 33 ++++++ examples/nextjs/previews/src/queries/index.js | 5 - .../nextjs/previews/src/wp-templates/home.js | 29 +++++ .../src/wp-templates/index-template.js | 33 ------ .../nextjs/previews/src/wp-templates/index.js | 4 +- .../src/wp-templates/templateQueries.js | 6 + packages/nextjs/pages/templateHierarchy.js | 2 + 9 files changed, 74 insertions(+), 155 deletions(-) rename examples/nextjs/previews/src/pages/{[...identifier].js => [[...identifier]].js} (89%) delete mode 100644 examples/nextjs/previews/src/pages/index.js create mode 100644 examples/nextjs/previews/src/queries/getPosts.js delete mode 100644 examples/nextjs/previews/src/queries/index.js create mode 100644 examples/nextjs/previews/src/wp-templates/home.js delete mode 100644 examples/nextjs/previews/src/wp-templates/index-template.js create mode 100644 examples/nextjs/previews/src/wp-templates/templateQueries.js diff --git a/examples/nextjs/previews/src/pages/[...identifier].js b/examples/nextjs/previews/src/pages/[[...identifier]].js similarity index 89% rename from examples/nextjs/previews/src/pages/[...identifier].js rename to examples/nextjs/previews/src/pages/[[...identifier]].js index 49682e944..f83d2582e 100644 --- a/examples/nextjs/previews/src/pages/[...identifier].js +++ b/examples/nextjs/previews/src/pages/[[...identifier]].js @@ -1,6 +1,6 @@ import { getAuthString } from '@/utils/getAuthString'; import availableTemplates from '@/wp-templates'; -import availableQueries from '@/queries'; +import availableQueries from '@/wp-templates/templateQueries'; import { createDefaultClient, setGraphQLClient, @@ -9,15 +9,13 @@ import { import { fetchTemplateQueries } from '@faustjs/data-fetching'; export default function Page(props) { - const { templateData, queriesData } = props; + const { templateData } = props; const PageTemplate = availableTemplates[templateData?.template?.id]; return ; } -// Statically generate the pages, except for draft mode -// More info: https://nextjs.org/docs/pages/guides/draft-mode export async function getStaticProps({ params, draftMode: isDraftModeEnabled, diff --git a/examples/nextjs/previews/src/pages/index.js b/examples/nextjs/previews/src/pages/index.js deleted file mode 100644 index 98bc6ad75..000000000 --- a/examples/nextjs/previews/src/pages/index.js +++ /dev/null @@ -1,111 +0,0 @@ -import Head from 'next/head'; - -export default function Home() { - // Simple hardcoded template data like in the Astro example - const templates = [ - { - data: { - name: 'Single', - type: 'single', - path: 'src/wp-templates/single.js', - description: 'Displays individual blog posts with full content', - }, - }, - { - data: { - name: 'Page', - type: 'page', - path: 'src/wp-templates/page.js', - description: 'Displays individual WordPress pages', - }, - }, - { - data: { - name: 'Archive', - type: 'archive', - path: 'src/wp-templates/archive.js', - description: 'Displays category, tag, and other archive pages', - }, - }, - { - data: { - name: 'Index', - type: 'index', - path: 'src/wp-templates/index-template.js', - description: - "Fallback template for any content that doesn't have a specific template", - }, - }, - ]; - - return ( - <> - - @faustjs/nextjs Example - - -
-

@faustjs/nextjs Template Hierarchy Example

-

- This example demonstrates how the @faustjs/nextjs package discovers - WordPress templates in your Next.js project using the WordPress - template hierarchy. -

- -

Discovered Templates

-

- The following templates were automatically discovered from the{' '} - wp-templates directory: -

- -
- {templates.map((template) => ( -
-

{template.data.name}

-

- Type: {template.data.type} -

-

- Path: {template.data.path} -

-

- Description: {template.data.description} -

-
- ))} -
- -

How It Works

-
-
    -
  1. - Templates are placed in the src/wp-templates/{' '} - directory -
  2. -
  3. - The index.js file exports all templates with dynamic - imports -
  4. -
  5. - Templates are organized by WordPress template hierarchy rules -
  6. -
  7. Each template gets metadata like type and path
  8. -
  9. You can query templates using the template registry
  10. -
-
- -

Live Template Router Demo

-

- See the template hierarchy in action with our catch-all route that - resolves WordPress URLs to templates: -

- -
- - ); -} diff --git a/examples/nextjs/previews/src/queries/getPosts.js b/examples/nextjs/previews/src/queries/getPosts.js new file mode 100644 index 000000000..9cfcdd4e1 --- /dev/null +++ b/examples/nextjs/previews/src/queries/getPosts.js @@ -0,0 +1,33 @@ +import gql from 'graphql-tag'; + +export const GET_POSTS = gql` + query GetPosts { + posts { + edges { + node { + id + title + content + date + uri + featuredImage { + node { + id + sourceUrl + altText + mediaDetails { + width + height + } + } + } + author { + node { + name + } + } + } + } + } + } +`; diff --git a/examples/nextjs/previews/src/queries/index.js b/examples/nextjs/previews/src/queries/index.js deleted file mode 100644 index 5b677233e..000000000 --- a/examples/nextjs/previews/src/queries/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import { queries as single } from '../wp-templates/single'; -import { queries as page } from '../wp-templates/page'; -import { queries as archive } from '../wp-templates/archive'; - -export default { single, page, archive }; diff --git a/examples/nextjs/previews/src/wp-templates/home.js b/examples/nextjs/previews/src/wp-templates/home.js new file mode 100644 index 000000000..2fc1c310d --- /dev/null +++ b/examples/nextjs/previews/src/wp-templates/home.js @@ -0,0 +1,29 @@ +import { BlogPostItem } from '@/components/BlogPostItem'; +import { GET_POSTS } from '@/queries/getPosts'; +import Head from 'next/head'; + +export default function HomeTemplate({ queriesData }) { + const { getPosts } = queriesData || {}; + const posts = getPosts?.data?.posts; + + return ( + <> + + {'Home'} + + + {posts?.edges?.map((item) => { + const post = item.node; + + return ; + })} + + ); +} + +export const queries = [ + { + name: 'getPosts', + query: GET_POSTS, + }, +]; diff --git a/examples/nextjs/previews/src/wp-templates/index-template.js b/examples/nextjs/previews/src/wp-templates/index-template.js deleted file mode 100644 index b50380764..000000000 --- a/examples/nextjs/previews/src/wp-templates/index-template.js +++ /dev/null @@ -1,33 +0,0 @@ -export default function IndexTemplate({ templateData }) { - const { seedQuery } = templateData || {}; - const posts = seedQuery?.data?.posts?.nodes || []; - - return ( - <> -

WordPress Index Template

-

This is the fallback template for all WordPress content.

-
-

- Any WordPress content that doesn't have a specific template would use - this one: -

- {posts.length > 0 ? ( - posts.map((post) => ( -
-

{post.title}

-

{post.excerpt || 'Content would appear here...'}

-
- )) - ) : ( -
-

Sample Content

-

- This is the default template that would render any WordPress - content using data from the WordPress GraphQL API. -

-
- )} -
- - ); -} diff --git a/examples/nextjs/previews/src/wp-templates/index.js b/examples/nextjs/previews/src/wp-templates/index.js index c4b921aaf..119695774 100644 --- a/examples/nextjs/previews/src/wp-templates/index.js +++ b/examples/nextjs/previews/src/wp-templates/index.js @@ -12,8 +12,8 @@ const archive = dynamic(() => import('./archive.js'), { loading: () =>

Loading Archive Template...

, }); -const index = dynamic(() => import('./index-template.js'), { +const home = dynamic(() => import('./home.js'), { loading: () =>

Loading Index Template...

, }); -export default { single, page, archive, index }; +export default { single, page, archive, home }; diff --git a/examples/nextjs/previews/src/wp-templates/templateQueries.js b/examples/nextjs/previews/src/wp-templates/templateQueries.js new file mode 100644 index 000000000..10cc13425 --- /dev/null +++ b/examples/nextjs/previews/src/wp-templates/templateQueries.js @@ -0,0 +1,6 @@ +import { queries as single } from './single'; +import { queries as page } from './page'; +import { queries as archive } from './archive'; +import { queries as home } from './home'; + +export default { single, page, archive, home }; diff --git a/packages/nextjs/pages/templateHierarchy.js b/packages/nextjs/pages/templateHierarchy.js index e9afbc41b..43e365e3f 100644 --- a/packages/nextjs/pages/templateHierarchy.js +++ b/packages/nextjs/pages/templateHierarchy.js @@ -70,6 +70,8 @@ export async function uriToTemplate({ const possibleTemplates = getPossibleTemplates(seedNode); + console.log(possibleTemplates); + returnData.possibleTemplates = possibleTemplates; if (!possibleTemplates || possibleTemplates.length === 0) { From fdb6f0c93cc28cf4c62d04487fa2f44c4884efef Mon Sep 17 00:00:00 2001 From: ahuseyn Date: Tue, 19 Aug 2025 17:18:43 +0200 Subject: [PATCH 08/24] style fixes --- .../nextjs/previews/src/components/Header.js | 2 +- .../nextjs/previews/src/components/Layout.js | 2 +- .../nextjs/previews/src/styles/globals.css | 120 +----------------- 3 files changed, 3 insertions(+), 121 deletions(-) diff --git a/examples/nextjs/previews/src/components/Header.js b/examples/nextjs/previews/src/components/Header.js index d811ca3eb..2d6ad7137 100644 --- a/examples/nextjs/previews/src/components/Header.js +++ b/examples/nextjs/previews/src/components/Header.js @@ -3,7 +3,7 @@ import Link from 'next/link'; export default function Header() { return ( -
+
Headless diff --git a/examples/nextjs/previews/src/components/Layout.js b/examples/nextjs/previews/src/components/Layout.js index 86172c15b..773b4f45c 100644 --- a/examples/nextjs/previews/src/components/Layout.js +++ b/examples/nextjs/previews/src/components/Layout.js @@ -8,7 +8,7 @@ export default function Layout({ children }) { return ( <>
-
+
{children} {router.isPreview && } diff --git a/examples/nextjs/previews/src/styles/globals.css b/examples/nextjs/previews/src/styles/globals.css index af80a2233..5a142e3ec 100644 --- a/examples/nextjs/previews/src/styles/globals.css +++ b/examples/nextjs/previews/src/styles/globals.css @@ -12,126 +12,8 @@ --font-mono: var(--font-geist-mono); } -.main { +body { background: var(--background); color: var(--foreground); font-family: Arial, Helvetica, sans-serif; } - -.main { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - max-width: 900px; - margin: 0 auto; - padding: 20px; - line-height: 1.6; - background: #f9f9f9; -} - -h1 { - color: #2563eb; - border-bottom: 2px solid #e5e7eb; - padding-bottom: 10px; -} - -h2 { - color: #374151; - margin-top: 30px; -} - -code { - background: #f3f4f6; - padding: 2px 6px; - border-radius: 4px; - font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; - font-size: 0.9em; -} - -main { - background: white; - padding: 20px; - border-radius: 8px; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); -} - -.templates-list { - display: grid; - gap: 15px; - margin: 20px 0; -} - -.template-card { - background: #f8fafc; - border: 1px solid #e2e8f0; - border-radius: 8px; - padding: 20px; - border-left: 4px solid #3b82f6; -} - -.template-card h3 { - margin-top: 0; - color: #1e40af; -} - -.template-card p { - margin: 8px 0; -} - -.how-it-works { - background: #ecfdf5; - border: 1px solid #d1fae5; - border-radius: 8px; - padding: 20px; - margin: 20px 0; -} - -.how-it-works ol { - margin: 0; - padding-left: 20px; -} - -.how-it-works li { - margin-bottom: 8px; -} - -.router-links { - display: flex; - flex-direction: column; - gap: 10px; - margin-top: 15px; -} - -.router-links a { - display: inline-block; - background: #10b981; - color: white; - text-decoration: none; - padding: 10px 15px; - border-radius: 6px; - transition: background-color 0.2s; -} - -.router-links a:hover { - background: #059669; -} - -/* WordPress template styles */ -.content-area { - background: #e8f4f8; - padding: 20px; - border-radius: 8px; - margin-top: 20px; -} - -.post-preview { - background: #f0f8ff; - padding: 15px; - border-radius: 5px; - margin-bottom: 15px; - border-left: 4px solid #0066cc; -} - -.post-preview h3, -.post-preview h4 { - margin-top: 0; - color: #0066cc; -} From 79e2a37261321cdf8ffa10ef8a0d451a7ac33d53 Mon Sep 17 00:00:00 2001 From: ahuseyn Date: Wed, 20 Aug 2025 13:03:40 +0200 Subject: [PATCH 09/24] update uri expression --- examples/nextjs/previews/src/pages/[[...identifier]].js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/nextjs/previews/src/pages/[[...identifier]].js b/examples/nextjs/previews/src/pages/[[...identifier]].js index f83d2582e..3eea8f967 100644 --- a/examples/nextjs/previews/src/pages/[[...identifier]].js +++ b/examples/nextjs/previews/src/pages/[[...identifier]].js @@ -34,9 +34,7 @@ export async function getStaticProps({ setGraphQLClient(client); - const uri = Array.isArray(params?.identifier) - ? '/' + params.identifier.join('/') + '/' - : '/'; + const uri = params?.identifier ? `/${params.identifier.join('/')}/` : '/'; const variables = isDraftModeEnabled ? { From 14be433d91f7edcc7f62e74ccd5085cb276582dd Mon Sep 17 00:00:00 2001 From: ahuseyn Date: Wed, 20 Aug 2025 13:12:40 +0200 Subject: [PATCH 10/24] rename example name, add readme --- .../{previews => kitchen-sink}/.gitignore | 0 examples/nextjs/kitchen-sink/README.md | 40 ++++++++++++++++++ .../eslint.config.mjs | 0 .../{previews => kitchen-sink}/jsconfig.json | 0 .../next.config.mjs | 0 .../{previews => kitchen-sink}/package.json | 2 +- .../postcss.config.mjs | 0 .../public/favicon.ico | Bin .../src/components/BlogPostItem.js | 0 .../src/components/Header.js | 0 .../src/components/Layout.js | 0 .../src/components/PreviewButton.js | 0 .../src/pages/[[...identifier]].js | 0 .../src/pages/_app.js | 0 .../src/pages/_document.js | 0 .../src/pages/api/disable-preview.js | 0 .../src/pages/api/preview.js | 0 .../src/queries/getArchive.js | 0 .../src/queries/getLayout.js | 0 .../src/queries/getPage.js | 0 .../src/queries/getPost.js | 0 .../src/queries/getPosts.js | 0 .../src/styles/globals.css | 0 .../src/utils/getAuthString.js | 0 .../src/wp-templates/archive.js | 0 .../src/wp-templates/home.js | 0 .../src/wp-templates/index.js | 2 +- .../src/wp-templates/page.js | 0 .../src/wp-templates/single.js | 0 .../src/wp-templates/templateQueries.js | 0 30 files changed, 42 insertions(+), 2 deletions(-) rename examples/nextjs/{previews => kitchen-sink}/.gitignore (100%) create mode 100644 examples/nextjs/kitchen-sink/README.md rename examples/nextjs/{previews => kitchen-sink}/eslint.config.mjs (100%) rename examples/nextjs/{previews => kitchen-sink}/jsconfig.json (100%) rename examples/nextjs/{previews => kitchen-sink}/next.config.mjs (100%) rename examples/nextjs/{previews => kitchen-sink}/package.json (92%) rename examples/nextjs/{previews => kitchen-sink}/postcss.config.mjs (100%) rename examples/nextjs/{previews => kitchen-sink}/public/favicon.ico (100%) rename examples/nextjs/{previews => kitchen-sink}/src/components/BlogPostItem.js (100%) rename examples/nextjs/{previews => kitchen-sink}/src/components/Header.js (100%) rename examples/nextjs/{previews => kitchen-sink}/src/components/Layout.js (100%) rename examples/nextjs/{previews => kitchen-sink}/src/components/PreviewButton.js (100%) rename examples/nextjs/{previews => kitchen-sink}/src/pages/[[...identifier]].js (100%) rename examples/nextjs/{previews => kitchen-sink}/src/pages/_app.js (100%) rename examples/nextjs/{previews => kitchen-sink}/src/pages/_document.js (100%) rename examples/nextjs/{previews => kitchen-sink}/src/pages/api/disable-preview.js (100%) rename examples/nextjs/{previews => kitchen-sink}/src/pages/api/preview.js (100%) rename examples/nextjs/{previews => kitchen-sink}/src/queries/getArchive.js (100%) rename examples/nextjs/{previews => kitchen-sink}/src/queries/getLayout.js (100%) rename examples/nextjs/{previews => kitchen-sink}/src/queries/getPage.js (100%) rename examples/nextjs/{previews => kitchen-sink}/src/queries/getPost.js (100%) rename examples/nextjs/{previews => kitchen-sink}/src/queries/getPosts.js (100%) rename examples/nextjs/{previews => kitchen-sink}/src/styles/globals.css (100%) rename examples/nextjs/{previews => kitchen-sink}/src/utils/getAuthString.js (100%) rename examples/nextjs/{previews => kitchen-sink}/src/wp-templates/archive.js (100%) rename examples/nextjs/{previews => kitchen-sink}/src/wp-templates/home.js (100%) rename examples/nextjs/{previews => kitchen-sink}/src/wp-templates/index.js (92%) rename examples/nextjs/{previews => kitchen-sink}/src/wp-templates/page.js (100%) rename examples/nextjs/{previews => kitchen-sink}/src/wp-templates/single.js (100%) rename examples/nextjs/{previews => kitchen-sink}/src/wp-templates/templateQueries.js (100%) diff --git a/examples/nextjs/previews/.gitignore b/examples/nextjs/kitchen-sink/.gitignore similarity index 100% rename from examples/nextjs/previews/.gitignore rename to examples/nextjs/kitchen-sink/.gitignore diff --git a/examples/nextjs/kitchen-sink/README.md b/examples/nextjs/kitchen-sink/README.md new file mode 100644 index 000000000..a8b986645 --- /dev/null +++ b/examples/nextjs/kitchen-sink/README.md @@ -0,0 +1,40 @@ +# Kitchen Sink Example + +A comprehensive Next.js example showcasing FaustJS features including template hierarchy, data fetching, and WordPress integration. + +## Features + +- **Template Hierarchy**: Dynamic WordPress template mapping (single, page, archive, home) +- **Data Fetching**: Server-side rendering with WordPress GraphQL data +- **Preview Mode**: WordPress post/page previews with authentication +- **Fallback Routing**: Catch-all routing with blocking fallback strategy +- **Dynamic Loading**: Code-split templates for optimal performance + +## Quick Start + +1. Install dependencies: + + ```bash + npm install + ``` + +2. Configure environment variables: + + ```bash + cp .env.example .env.local + # Add your NEXT_PUBLIC_WORDPRESS_URL + ``` + +3. Run development server: + ```bash + npm run dev + ``` + +## Architecture + +- Uses `[[...identifier]].js` for dynamic WordPress routing +- Template queries are fetched server-side using `@faustjs/data-fetching` +- WordPress templates are dynamically loaded based on content type +- Supports both static generation and draft previews + +Perfect for testing FaustJS capabilities and as a reference implementation. diff --git a/examples/nextjs/previews/eslint.config.mjs b/examples/nextjs/kitchen-sink/eslint.config.mjs similarity index 100% rename from examples/nextjs/previews/eslint.config.mjs rename to examples/nextjs/kitchen-sink/eslint.config.mjs diff --git a/examples/nextjs/previews/jsconfig.json b/examples/nextjs/kitchen-sink/jsconfig.json similarity index 100% rename from examples/nextjs/previews/jsconfig.json rename to examples/nextjs/kitchen-sink/jsconfig.json diff --git a/examples/nextjs/previews/next.config.mjs b/examples/nextjs/kitchen-sink/next.config.mjs similarity index 100% rename from examples/nextjs/previews/next.config.mjs rename to examples/nextjs/kitchen-sink/next.config.mjs diff --git a/examples/nextjs/previews/package.json b/examples/nextjs/kitchen-sink/package.json similarity index 92% rename from examples/nextjs/previews/package.json rename to examples/nextjs/kitchen-sink/package.json index e2fb03d82..3faedb491 100644 --- a/examples/nextjs/previews/package.json +++ b/examples/nextjs/kitchen-sink/package.json @@ -1,5 +1,5 @@ { - "name": "@faustjs/nextjs-preview-example", + "name": "@faustjs/kitchen-sink-example", "version": "0.1.0", "private": true, "scripts": { diff --git a/examples/nextjs/previews/postcss.config.mjs b/examples/nextjs/kitchen-sink/postcss.config.mjs similarity index 100% rename from examples/nextjs/previews/postcss.config.mjs rename to examples/nextjs/kitchen-sink/postcss.config.mjs diff --git a/examples/nextjs/previews/public/favicon.ico b/examples/nextjs/kitchen-sink/public/favicon.ico similarity index 100% rename from examples/nextjs/previews/public/favicon.ico rename to examples/nextjs/kitchen-sink/public/favicon.ico diff --git a/examples/nextjs/previews/src/components/BlogPostItem.js b/examples/nextjs/kitchen-sink/src/components/BlogPostItem.js similarity index 100% rename from examples/nextjs/previews/src/components/BlogPostItem.js rename to examples/nextjs/kitchen-sink/src/components/BlogPostItem.js diff --git a/examples/nextjs/previews/src/components/Header.js b/examples/nextjs/kitchen-sink/src/components/Header.js similarity index 100% rename from examples/nextjs/previews/src/components/Header.js rename to examples/nextjs/kitchen-sink/src/components/Header.js diff --git a/examples/nextjs/previews/src/components/Layout.js b/examples/nextjs/kitchen-sink/src/components/Layout.js similarity index 100% rename from examples/nextjs/previews/src/components/Layout.js rename to examples/nextjs/kitchen-sink/src/components/Layout.js diff --git a/examples/nextjs/previews/src/components/PreviewButton.js b/examples/nextjs/kitchen-sink/src/components/PreviewButton.js similarity index 100% rename from examples/nextjs/previews/src/components/PreviewButton.js rename to examples/nextjs/kitchen-sink/src/components/PreviewButton.js diff --git a/examples/nextjs/previews/src/pages/[[...identifier]].js b/examples/nextjs/kitchen-sink/src/pages/[[...identifier]].js similarity index 100% rename from examples/nextjs/previews/src/pages/[[...identifier]].js rename to examples/nextjs/kitchen-sink/src/pages/[[...identifier]].js diff --git a/examples/nextjs/previews/src/pages/_app.js b/examples/nextjs/kitchen-sink/src/pages/_app.js similarity index 100% rename from examples/nextjs/previews/src/pages/_app.js rename to examples/nextjs/kitchen-sink/src/pages/_app.js diff --git a/examples/nextjs/previews/src/pages/_document.js b/examples/nextjs/kitchen-sink/src/pages/_document.js similarity index 100% rename from examples/nextjs/previews/src/pages/_document.js rename to examples/nextjs/kitchen-sink/src/pages/_document.js diff --git a/examples/nextjs/previews/src/pages/api/disable-preview.js b/examples/nextjs/kitchen-sink/src/pages/api/disable-preview.js similarity index 100% rename from examples/nextjs/previews/src/pages/api/disable-preview.js rename to examples/nextjs/kitchen-sink/src/pages/api/disable-preview.js diff --git a/examples/nextjs/previews/src/pages/api/preview.js b/examples/nextjs/kitchen-sink/src/pages/api/preview.js similarity index 100% rename from examples/nextjs/previews/src/pages/api/preview.js rename to examples/nextjs/kitchen-sink/src/pages/api/preview.js diff --git a/examples/nextjs/previews/src/queries/getArchive.js b/examples/nextjs/kitchen-sink/src/queries/getArchive.js similarity index 100% rename from examples/nextjs/previews/src/queries/getArchive.js rename to examples/nextjs/kitchen-sink/src/queries/getArchive.js diff --git a/examples/nextjs/previews/src/queries/getLayout.js b/examples/nextjs/kitchen-sink/src/queries/getLayout.js similarity index 100% rename from examples/nextjs/previews/src/queries/getLayout.js rename to examples/nextjs/kitchen-sink/src/queries/getLayout.js diff --git a/examples/nextjs/previews/src/queries/getPage.js b/examples/nextjs/kitchen-sink/src/queries/getPage.js similarity index 100% rename from examples/nextjs/previews/src/queries/getPage.js rename to examples/nextjs/kitchen-sink/src/queries/getPage.js diff --git a/examples/nextjs/previews/src/queries/getPost.js b/examples/nextjs/kitchen-sink/src/queries/getPost.js similarity index 100% rename from examples/nextjs/previews/src/queries/getPost.js rename to examples/nextjs/kitchen-sink/src/queries/getPost.js diff --git a/examples/nextjs/previews/src/queries/getPosts.js b/examples/nextjs/kitchen-sink/src/queries/getPosts.js similarity index 100% rename from examples/nextjs/previews/src/queries/getPosts.js rename to examples/nextjs/kitchen-sink/src/queries/getPosts.js diff --git a/examples/nextjs/previews/src/styles/globals.css b/examples/nextjs/kitchen-sink/src/styles/globals.css similarity index 100% rename from examples/nextjs/previews/src/styles/globals.css rename to examples/nextjs/kitchen-sink/src/styles/globals.css diff --git a/examples/nextjs/previews/src/utils/getAuthString.js b/examples/nextjs/kitchen-sink/src/utils/getAuthString.js similarity index 100% rename from examples/nextjs/previews/src/utils/getAuthString.js rename to examples/nextjs/kitchen-sink/src/utils/getAuthString.js diff --git a/examples/nextjs/previews/src/wp-templates/archive.js b/examples/nextjs/kitchen-sink/src/wp-templates/archive.js similarity index 100% rename from examples/nextjs/previews/src/wp-templates/archive.js rename to examples/nextjs/kitchen-sink/src/wp-templates/archive.js diff --git a/examples/nextjs/previews/src/wp-templates/home.js b/examples/nextjs/kitchen-sink/src/wp-templates/home.js similarity index 100% rename from examples/nextjs/previews/src/wp-templates/home.js rename to examples/nextjs/kitchen-sink/src/wp-templates/home.js diff --git a/examples/nextjs/previews/src/wp-templates/index.js b/examples/nextjs/kitchen-sink/src/wp-templates/index.js similarity index 92% rename from examples/nextjs/previews/src/wp-templates/index.js rename to examples/nextjs/kitchen-sink/src/wp-templates/index.js index 119695774..7b8c16ce4 100644 --- a/examples/nextjs/previews/src/wp-templates/index.js +++ b/examples/nextjs/kitchen-sink/src/wp-templates/index.js @@ -1,4 +1,4 @@ -import dynamic from 'next/dynamic'; +import dynamic from 'next/dynamic.js'; const single = dynamic(() => import('./single.js'), { loading: () =>

Loading Single Template...

, diff --git a/examples/nextjs/previews/src/wp-templates/page.js b/examples/nextjs/kitchen-sink/src/wp-templates/page.js similarity index 100% rename from examples/nextjs/previews/src/wp-templates/page.js rename to examples/nextjs/kitchen-sink/src/wp-templates/page.js diff --git a/examples/nextjs/previews/src/wp-templates/single.js b/examples/nextjs/kitchen-sink/src/wp-templates/single.js similarity index 100% rename from examples/nextjs/previews/src/wp-templates/single.js rename to examples/nextjs/kitchen-sink/src/wp-templates/single.js diff --git a/examples/nextjs/previews/src/wp-templates/templateQueries.js b/examples/nextjs/kitchen-sink/src/wp-templates/templateQueries.js similarity index 100% rename from examples/nextjs/previews/src/wp-templates/templateQueries.js rename to examples/nextjs/kitchen-sink/src/wp-templates/templateQueries.js From d3003d4bd286786a0916c08b3d585d1916b47ad9 Mon Sep 17 00:00:00 2001 From: ahuseyn Date: Thu, 21 Aug 2025 14:36:01 +0200 Subject: [PATCH 11/24] init project --- examples/astro/kitchen-sink/.gitignore | 22 ++ examples/astro/kitchen-sink/README.md | 138 ++++++++++++ examples/astro/kitchen-sink/astro.config.mjs | 31 +++ examples/astro/kitchen-sink/package.json | 28 +++ .../kitchen-sink/src/components/Nav.astro | 50 +++++ .../components/TemplateHierarchyInfo.astro | 52 +++++ .../astro/kitchen-sink/src/content.config.js | 7 + examples/astro/kitchen-sink/src/env.d.ts | 7 + .../kitchen-sink/src/layouts/Layout.astro | 36 ++++ .../src/layouts/WordPressLayout.astro | 66 ++++++ .../astro/kitchen-sink/src/pages/404.astro | 24 +++ .../src/pages/[...identifier].astro | 56 +++++ .../astro/kitchen-sink/src/pages/index.astro | 198 ++++++++++++++++++ .../src/pages/wp-templates/archive.astro | 86 ++++++++ .../src/pages/wp-templates/home.astro | 88 ++++++++ .../src/pages/wp-templates/index.astro | 80 +++++++ .../src/pages/wp-templates/single.astro | 73 +++++++ .../kitchen-sink/src/queries/getArchive.js | 68 ++++++ .../kitchen-sink/src/queries/getLayout.js | 10 + .../astro/kitchen-sink/src/queries/getPage.js | 27 +++ .../astro/kitchen-sink/src/queries/getPost.js | 27 +++ .../kitchen-sink/src/queries/getPosts.js | 33 +++ .../kitchen-sink/src/utils/getAuthString.js | 8 + examples/astro/kitchen-sink/tsconfig.json | 9 + packages/astro/templateHierarchy.js | 18 +- packages/graphql/client.js | 2 + pnpm-lock.yaml | 37 +++- 27 files changed, 1271 insertions(+), 10 deletions(-) create mode 100644 examples/astro/kitchen-sink/.gitignore create mode 100644 examples/astro/kitchen-sink/README.md create mode 100644 examples/astro/kitchen-sink/astro.config.mjs create mode 100644 examples/astro/kitchen-sink/package.json create mode 100644 examples/astro/kitchen-sink/src/components/Nav.astro create mode 100644 examples/astro/kitchen-sink/src/components/TemplateHierarchyInfo.astro create mode 100644 examples/astro/kitchen-sink/src/content.config.js create mode 100644 examples/astro/kitchen-sink/src/env.d.ts create mode 100644 examples/astro/kitchen-sink/src/layouts/Layout.astro create mode 100644 examples/astro/kitchen-sink/src/layouts/WordPressLayout.astro create mode 100644 examples/astro/kitchen-sink/src/pages/404.astro create mode 100644 examples/astro/kitchen-sink/src/pages/[...identifier].astro create mode 100644 examples/astro/kitchen-sink/src/pages/index.astro create mode 100644 examples/astro/kitchen-sink/src/pages/wp-templates/archive.astro create mode 100644 examples/astro/kitchen-sink/src/pages/wp-templates/home.astro create mode 100644 examples/astro/kitchen-sink/src/pages/wp-templates/index.astro create mode 100644 examples/astro/kitchen-sink/src/pages/wp-templates/single.astro create mode 100644 examples/astro/kitchen-sink/src/queries/getArchive.js create mode 100644 examples/astro/kitchen-sink/src/queries/getLayout.js create mode 100644 examples/astro/kitchen-sink/src/queries/getPage.js create mode 100644 examples/astro/kitchen-sink/src/queries/getPost.js create mode 100644 examples/astro/kitchen-sink/src/queries/getPosts.js create mode 100644 examples/astro/kitchen-sink/src/utils/getAuthString.js create mode 100644 examples/astro/kitchen-sink/tsconfig.json diff --git a/examples/astro/kitchen-sink/.gitignore b/examples/astro/kitchen-sink/.gitignore new file mode 100644 index 000000000..f26f0594a --- /dev/null +++ b/examples/astro/kitchen-sink/.gitignore @@ -0,0 +1,22 @@ +# Local environment variables +.env +.env.local +.env.production + +# Build output +dist/ +.astro/ + +# Dependencies +node_modules/ + +# Logs +*.log + +# Editor +.vscode/ +.idea/ + +# OS +.DS_Store +Thumbs.db diff --git a/examples/astro/kitchen-sink/README.md b/examples/astro/kitchen-sink/README.md new file mode 100644 index 000000000..433c933ee --- /dev/null +++ b/examples/astro/kitchen-sink/README.md @@ -0,0 +1,138 @@ +# @faustjs/astro Template Hierarchy Example + +This example demonstrates how to use the `@faustjs/astro` package to automatically discover WordPress templates in an Astro project using the WordPress template hierarchy. + +## What This Example Shows + +- **Template Discovery**: Automatically finds WordPress template files in your `wp-templates` directory +- **Content Collections**: Uses Astro's content collections to organize and query templates +- **Template Hierarchy**: Follows WordPress template hierarchy rules for organizing templates +- **Template Resolution**: SSR catch-all route that dynamically resolves WordPress URLs to templates + +## Project Structure + +``` +src/ +├── layouts/ +│ └── WordPressLayout.astro # Shared layout for all templates +├── content.config.js # Content collection configuration +└── pages/ + ├── index.astro # Demo page showing discovered templates + ├── 404.astro # Custom 404 page + ├── [...uri].astro # Catch-all route implementing template resolution + └── wp-templates/ # WordPress template files (protected from direct access) + ├── index.astro # Home/blog template + ├── page.astro # Page template + ├── single-post.astro # Single post template + └── archive.astro # Archive template +``` + +## How It Works + +1. **Template Files**: Place your WordPress templates in `src/pages/wp-templates/` +2. **Auto-Discovery**: The `createTemplateCollection()` function finds all template files +3. **Content Collections**: Templates are available as an Astro content collection with `id` and `path` +4. **SSR Resolution**: The `[...uri].astro` route dynamically fetches content and resolves templates +5. **Shared Layout**: All templates use `WordPressLayout.astro` for consistent styling +6. **Template Protection**: Templates can't be accessed directly (e.g., `/wp-templates/page` returns 404) + +## Running the Example + +```bash +# Install dependencies (includes GraphQL requirements) +pnpm install + +# Copy environment configuration +cp .env.example .env + +# Edit .env to set your WordPress URL +# WORDPRESS_URL=https://your-wordpress-site.com + +# Start development server (SSR mode) +pnpm dev + +# Build for production (outputs server bundle) +pnpm build +``` + +## Dependencies + +This example requires the following dependencies (automatically installed): + +- `@faustjs/astro` - Astro integration with WordPress template hierarchy +- `@faustjs/template-hierarchy` - Core template hierarchy logic +- `astro` - Astro framework (peer dependency) +- `graphql` - GraphQL core library (peer dependency) +- `graphql-tag` - GraphQL query parsing (peer dependency) + +## WordPress Setup + +This example requires a WordPress site with WPGraphQL plugin installed: + +1. **Install WPGraphQL**: Install the [WPGraphQL plugin](https://www.wpgraphql.com/) on your WordPress site +2. **Set Environment Variable**: Copy `.env.example` to `.env` and set your WordPress URL +3. **GraphQL Endpoint**: The endpoint should be `https://your-site.com/graphql` + +### Supported WordPress Content + +The example automatically discovers and creates routes for: + +- **Posts**: Individual blog posts with categories and tags +- **Pages**: Static pages like About, Contact, etc. +- **Category Archives**: Archive pages for each category +- **Tag Archives**: Archive pages for each tag +- **Home Page**: Main blog index + +## Template Discovery + +The `@faustjs/astro` package automatically discovers templates in your `wp-templates` directory: + +- **index.astro** → Home template +- **page.astro** → Page template +- **single-post.astro** → Single post template +- **archive.astro** → Archive template + +Each template is automatically added to the content collection with an `id` and `path`. + +## Template Resolution Demo + +The catch-all route (`[...uri].astro`) fetches real WordPress content and demonstrates URL resolution: + +- `/` → `index.astro` (home page) +- `/about` → `page.astro` (WordPress page) +- `/hello-world` → `single-post.astro` (WordPress post) +- `/category/uncategorized` → `archive.astro` (category archive) +- `/tag/fun` → `archive.astro` (tag archive) + +_Note: Actual URLs depend on your WordPress content_ + +## Content Collection Usage + +```js +// Get all templates +import { getCollection } from 'astro:content'; +const templates = await getCollection('templates'); + +// Find a specific template by id +const pageTemplate = templates.find((t) => t.id === 'page'); + +// Each template has: { id: string, path: string } +console.log(pageTemplate); // { id: 'page', path: 'wp-templates/page' } +``` + +## Real-World Usage + +This example shows how a real WordPress headless site works with SSR: + +1. **WordPress GraphQL API**: Dynamically fetches content from WordPress on each request +2. **Server-Side Rendering**: Renders pages on-demand with fresh WordPress content +3. **Template Resolution**: Uses WordPress template hierarchy to render content +4. **Content Types**: Handles posts, pages, categories, and tags dynamically +5. **SEO Ready**: Generates proper HTML with fresh WordPress content +6. **404 Handling**: Automatically redirects to 404 for non-existent content + +## Learn More + +- [WordPress Template Hierarchy](https://developer.wordpress.org/themes/basics/template-hierarchy/) +- [Astro Content Collections](https://docs.astro.build/en/guides/content-collections/) +- [@faustjs/template-hierarchy](../packages/template-hierarchy/) diff --git a/examples/astro/kitchen-sink/astro.config.mjs b/examples/astro/kitchen-sink/astro.config.mjs new file mode 100644 index 000000000..b1ccfb253 --- /dev/null +++ b/examples/astro/kitchen-sink/astro.config.mjs @@ -0,0 +1,31 @@ +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + output: 'server', + // Enable SSR for dynamic WordPress content + vite: { + resolve: { + alias: { + '@': '/src', + }, + }, + server: { + watch: { + // Explicitly watch the packages directories + ignored: [ + '**/node_modules/**', + '!**/node_modules/@faustjs/**', // Watch @faustjs packages + ], + followSymlinks: true, + }, + }, + optimizeDeps: { + // Don't pre-bundle workspace packages so changes are picked up immediately + exclude: [ + '@faustjs/astro', + '@faustjs/template-hierarchy', + '@faustjs/graphql', + ], + }, + }, +}); diff --git a/examples/astro/kitchen-sink/package.json b/examples/astro/kitchen-sink/package.json new file mode 100644 index 000000000..7f52963b1 --- /dev/null +++ b/examples/astro/kitchen-sink/package.json @@ -0,0 +1,28 @@ +{ + "name": "@faustjs/astro-kitchen-sink-example", + "private": true, + "version": "0.1.0", + "license": "0BSD", + "type": "module", + "scripts": { + "dev": "astro dev", + "start": "astro dev", + "build": "astro check && astro build", + "preview": "astro preview", + "astro": "astro" + }, + "dependencies": { + "@faustjs/astro": "workspace:*", + "@faustjs/template-hierarchy": "workspace:*", + "@faustjs/data-fetching": "workspace:*", + "astro": "^5.1.1", + "graphql": "^16.9.0", + "graphql-tag": "^2.12.6" + }, + "devDependencies": { + "typescript": "^5.6.3" + }, + "engines": { + "node": ">=24" + } +} diff --git a/examples/astro/kitchen-sink/src/components/Nav.astro b/examples/astro/kitchen-sink/src/components/Nav.astro new file mode 100644 index 000000000..d35cab7de --- /dev/null +++ b/examples/astro/kitchen-sink/src/components/Nav.astro @@ -0,0 +1,50 @@ +--- +/** + * We've hard coded this but you can use the client to fetch this data from WP Menus! + * import { client, gql } from "../lib/client"; + */ +--- + + + diff --git a/examples/astro/kitchen-sink/src/components/TemplateHierarchyInfo.astro b/examples/astro/kitchen-sink/src/components/TemplateHierarchyInfo.astro new file mode 100644 index 000000000..3bbaafe89 --- /dev/null +++ b/examples/astro/kitchen-sink/src/components/TemplateHierarchyInfo.astro @@ -0,0 +1,52 @@ +--- +const template = Astro.locals.templateData; + +if (!template) { + return null; +} +--- + + diff --git a/examples/astro/kitchen-sink/src/content.config.js b/examples/astro/kitchen-sink/src/content.config.js new file mode 100644 index 000000000..e8c52b83b --- /dev/null +++ b/examples/astro/kitchen-sink/src/content.config.js @@ -0,0 +1,7 @@ +import { defineCollection } from "astro:content"; +import { createTemplateCollection } from "@faustjs/astro"; + +// Set up WordPress template collection +const templates = defineCollection(createTemplateCollection()); + +export const collections = { templates }; diff --git a/examples/astro/kitchen-sink/src/env.d.ts b/examples/astro/kitchen-sink/src/env.d.ts new file mode 100644 index 000000000..7b59111d0 --- /dev/null +++ b/examples/astro/kitchen-sink/src/env.d.ts @@ -0,0 +1,7 @@ +declare namespace App { + interface Locals { + templateData?: import('./lib/templateHierarchy').TemplateData; + isPreview?: boolean; + client?: import('./lib/types').GraphQLClient; + } +} diff --git a/examples/astro/kitchen-sink/src/layouts/Layout.astro b/examples/astro/kitchen-sink/src/layouts/Layout.astro new file mode 100644 index 000000000..f078b89be --- /dev/null +++ b/examples/astro/kitchen-sink/src/layouts/Layout.astro @@ -0,0 +1,36 @@ +--- +// import Nav from '../components/Nav.astro'; +// import TemplateHierarchyInfo from "../components/TemplateHierarchyInfo.astro"; +--- + + + + + + + + + Astro Basics + + + + +
+ +
+ + + + diff --git a/examples/astro/kitchen-sink/src/layouts/WordPressLayout.astro b/examples/astro/kitchen-sink/src/layouts/WordPressLayout.astro new file mode 100644 index 000000000..2c95b429f --- /dev/null +++ b/examples/astro/kitchen-sink/src/layouts/WordPressLayout.astro @@ -0,0 +1,66 @@ +--- +export interface Props { + title: string; +} + +const { title } = Astro.props; +--- + + + + + + + + {title} + + +
+ +
+ + + + diff --git a/examples/astro/kitchen-sink/src/pages/404.astro b/examples/astro/kitchen-sink/src/pages/404.astro new file mode 100644 index 000000000..b220b4f32 --- /dev/null +++ b/examples/astro/kitchen-sink/src/pages/404.astro @@ -0,0 +1,24 @@ +--- +import WordPressLayout from '@/layouts/WordPressLayout.astro'; + +// Set the response status to 404 +Astro.response.status = 404; +--- + + +

404 - Page Not Found

+

Sorry, the page you're looking for doesn't exist.

+
+

This could happen if:

+
    +
  • The page has been moved or deleted
  • +
  • You typed the URL incorrectly
  • +
  • The WordPress content couldn't be found
  • +
+

+ + ← Go back to the homepage + +

+
+
diff --git a/examples/astro/kitchen-sink/src/pages/[...identifier].astro b/examples/astro/kitchen-sink/src/pages/[...identifier].astro new file mode 100644 index 000000000..6d7384cc2 --- /dev/null +++ b/examples/astro/kitchen-sink/src/pages/[...identifier].astro @@ -0,0 +1,56 @@ +--- +import { + uriToTemplate, + createDefaultClient, + setGraphQLClient, +} from '@faustjs/astro'; +import { getAuthString } from '../utils/getAuthString.js'; + +const isPreview = Astro.url.searchParams.get('preview') === 'true'; + +const headers = isPreview + ? { + Authorization: getAuthString(), + } + : null; + +// Set up GraphQL client using environment variable +const client = createDefaultClient(import.meta.env.WORDPRESS_URL, headers); + +setGraphQLClient(client); + +// URI comes as a string from Astro params, ensure it starts with / +const { identifier = '/' } = Astro.params; + +const variables = isPreview + ? { + id: identifier, + asPreview: true, + } + : { uri: identifier }; + +console.log('VVV', variables); + +const results = await uriToTemplate(variables); + +console.log(results); + +Astro.locals.templateData = results; +Astro.locals.isPreview = isPreview || false; +Astro.locals.client = client; + +if (results.template) { + return Astro.rewrite(results.template.path); +} +--- + +

Oops! You shouldn't be here, something went wrong!

+
+	
+	  {JSON.stringify(
+		results,
+		null,
+		2
+	  )}
+	
+  
diff --git a/examples/astro/kitchen-sink/src/pages/index.astro b/examples/astro/kitchen-sink/src/pages/index.astro new file mode 100644 index 000000000..9859cf006 --- /dev/null +++ b/examples/astro/kitchen-sink/src/pages/index.astro @@ -0,0 +1,198 @@ +--- +// Demo page showing template discovery +import { getCollection } from 'astro:content'; + +// Get all templates from our content collection +const templates = await getCollection('templates'); +--- + + + + + + + + @faustjs/astro Example + + +
+

@faustjs/astro Template Hierarchy Example

+

+ This example demonstrates how the @faustjs/astro package discovers + WordPress templates in your Astro project using the WordPress template + hierarchy. +

+ +

Discovered Templates

+

+ The following templates were automatically discovered from the wp-templates directory: +

+ +
+ { + templates.map((template) => ( +
+

{template.data.name}

+

+ Type: {template.data.type} +

+

+ Priority: {template.data.priority} +

+

+ Path: {template.data.path} +

+ {template.data.description && ( +

+ Description: {template.data.description} +

+ )} +
+ )) + } +
+ +

How It Works

+
+
    +
  1. + Templates are placed in the src/pages/wp-templates/ directory +
  2. +
  3. + The createTemplateCollection() function automatically discovers + them +
  4. +
  5. Templates are organized by WordPress template hierarchy rules
  6. +
  7. Each template gets metadata like type, priority, and path
  8. +
  9. You can query templates using Astro's content collections API
  10. +
+
+ +

Live Template Router Demo

+

+ See the template hierarchy in action with our catch-all route that + resolves WordPress URLs to templates: +

+ +
+ + + + diff --git a/examples/astro/kitchen-sink/src/pages/wp-templates/archive.astro b/examples/astro/kitchen-sink/src/pages/wp-templates/archive.astro new file mode 100644 index 000000000..89eb63d38 --- /dev/null +++ b/examples/astro/kitchen-sink/src/pages/wp-templates/archive.astro @@ -0,0 +1,86 @@ +--- +import Layout from "../../layouts/Layout.astro"; +import { authHeaders, client, gql } from "../../lib/client"; +/** + * This is a template for the WordPress template hierarchy. + * It will be used to render the WordPress templates. + * It should never be used directly. + * This check confirms that the template is being used in the correct context. + * If the template is being used directly, it will redirect to the 404 page. + */ +if (Astro.url.pathname === Astro.originPathname) { + return Astro.rewrite("/404"); +} + +const isPreview = Astro.locals.isPreview; + +const { data, error } = await client.query( + gql` + query ArchiveTemplateNodeQuery($uri: String!) { + node: nodeByUri(uri: $uri) { + __typename + ... on TermNode { + name + description + } + ... on Tag { + contentNodes(where: { stati: [DRAFT, PUBLISH, FUTURE, PENDING] }) { + nodes { + ... on NodeWithTitle { + title + } + uri + } + } + } + ... on Category { + contentNodes(where: { stati: [DRAFT, PUBLISH, FUTURE, PENDING] }) { + nodes { + ... on NodeWithTitle { + title + } + uri + } + } + } + } + terms { + nodes { + uri + } + } + } + `, + { + uri: Astro.originPathname, + }, + { + fetchOptions: { + headers: { + ...authHeaders(isPreview), + }, + }, + } +); + +if (error) { + console.error("Error fetching data:", error); + return Astro.rewrite("/500"); +} +--- + + +
+

{data.node.name}

+

+

    + { + data.node.contentNodes.nodes.map((content) => ( +
  1. + +
  2. + )) + } +
+
+
diff --git a/examples/astro/kitchen-sink/src/pages/wp-templates/home.astro b/examples/astro/kitchen-sink/src/pages/wp-templates/home.astro new file mode 100644 index 000000000..816aada23 --- /dev/null +++ b/examples/astro/kitchen-sink/src/pages/wp-templates/home.astro @@ -0,0 +1,88 @@ +--- +import Layout from "../../layouts/Layout.astro"; +import { authHeaders, client, gql } from "../../lib/client"; + +/** + * This is a template for the WordPress template hierarchy. + * It will be used to render the WordPress templates. + * It should never be used directly. + * This check confirms that the template is being used in the correct context. + * If the template is being used directly, it will redirect to the 404 page. + */ +if (Astro.url.pathname === Astro.originPathname) { + return Astro.rewrite("/404"); +} + +const isPreview = Astro.locals.isPreview; + +const { data, error } = await client.query( + gql` + query homeTemplatePostQuery { + posts(first: 6, where: { stati: [DRAFT, PUBLISH, FUTURE, PENDING] }) { + nodes { + id + title + uri + excerpt + } + } + } + `, + {}, + { + fetchOptions: { + headers: { + ...authHeaders(isPreview), + }, + }, + } +); + +if (error) { + console.error("Error fetching data:", error); + return Astro.rewrite("/500"); +} +--- + + +
+

My WP + Astro Blog!

+ +

I like sharing my life!

+ +
+

Recent Posts

+
+ { + data.posts.nodes.map((post) => { + return ( +
+

{post.title}

+
+ + Read more... + +
+ ); + }) + } +
+
+
+ + + diff --git a/examples/astro/kitchen-sink/src/pages/wp-templates/index.astro b/examples/astro/kitchen-sink/src/pages/wp-templates/index.astro new file mode 100644 index 000000000..2c0cca05d --- /dev/null +++ b/examples/astro/kitchen-sink/src/pages/wp-templates/index.astro @@ -0,0 +1,80 @@ +--- +/** + * This is a template for the WordPress template hierarchy. + * It will be used to render the WordPress templates. + * It should never be used directly. + * This check confirms that the template is being used in the correct context. + * If the template is being used directly, it will redirect to the 404 page. + */ +if (Astro.url.pathname === Astro.originPathname) { + return Astro.rewrite("/404"); +} + +import Layout from "../../layouts/Layout.astro"; +import { authHeaders, client, gql } from "../../lib/client"; + +const databaseId = Astro.locals.templateData?.databaseId; +const isPreview = Astro.locals.isPreview; + +const query = gql` + query indexTemplateNodeQuery($id: ID!, $isPreview: Boolean = false) { + node: contentNode(id: $id, idType: DATABASE_ID, asPreview: $isPreview) { + __typename + uri + id + ... on NodeWithTitle { + title + } + ... on NodeWithContentEditor { + content + } + } + } +`; + +const { data, error } = await client.query( + query, + { + id: databaseId, + isPreview, + }, + { + fetchOptions: { + headers: { + ...authHeaders(isPreview), + }, + }, + } +); + +if (error) { + console.error("Error fetching data:", error); + return Astro.rewrite("/500"); +} +--- + + +

+ This is the index template for the WordPress template hierarchy. + It will be used to render the WordPress content if no more appropriate template + is provided (e.g. front-page, single, singular, archive, etc). It should never + be used directly. +

+ + {data.node.title &&

} + {data.node.content &&
} + { + !data.node.content && !data.node.title && ( +
+        {JSON.stringify(data ?? {}, null, 2)}
+      
+ ) + } + + + diff --git a/examples/astro/kitchen-sink/src/pages/wp-templates/single.astro b/examples/astro/kitchen-sink/src/pages/wp-templates/single.astro new file mode 100644 index 000000000..e9b772414 --- /dev/null +++ b/examples/astro/kitchen-sink/src/pages/wp-templates/single.astro @@ -0,0 +1,73 @@ +--- +import Layout from '../../layouts/Layout.astro'; +import { GET_POST } from '../../queries/getPost.js'; +import { print } from 'graphql'; + +/** + * This is a template for the WordPress template hierarchy. + * It will be used to render the WordPress templates. + * It should never be used directly. + * This check confirms that the template is being used in the correct context. + * If the template is being used directly, it will redirect to the 404 page. + */ +if (Astro.url.pathname === Astro.originPathname) { + return Astro.rewrite('/404'); +} + +const isPreview = Astro.locals.isPreview; +const databaseId = Astro.locals.templateData?.seedNode?.databaseId; + +const { data, error } = await Astro.locals.client.request(print(GET_POST), { + databaseId, + asPreview: isPreview, +}); + +if (error) { + console.error('Error fetching data:', error); + return Astro.rewrite('/500'); +} + +if (!data.post) { + console.error('HTTP/404 - Not Found in WordPress:', databaseId); + return Astro.rewrite('/404'); +} + +const { title, content, featuredImage } = data?.post || {}; +--- + + +
+
+

+ {title} +

+
+ + { + featuredImage && ( + + ) + } + +
+
+
+ + diff --git a/examples/astro/kitchen-sink/src/queries/getArchive.js b/examples/astro/kitchen-sink/src/queries/getArchive.js new file mode 100644 index 000000000..fc59b7f14 --- /dev/null +++ b/examples/astro/kitchen-sink/src/queries/getArchive.js @@ -0,0 +1,68 @@ +import gql from 'graphql-tag'; + +export const GET_ARCHIVE = gql` + query GetArchivePage($uri: String!) { + nodeByUri(uri: $uri) { + ... on Category { + name + posts { + edges { + node { + id + title + content + date + uri + featuredImage { + node { + id + sourceUrl + altText + mediaDetails { + width + height + } + } + } + author { + node { + name + } + } + } + } + } + } + ... on Tag { + name + posts { + edges { + node { + id + title + content + date + uri + featuredImage { + node { + id + sourceUrl + altText + mediaDetails { + width + height + } + } + } + author { + node { + name + } + } + } + } + } + } + } + } +`; diff --git a/examples/astro/kitchen-sink/src/queries/getLayout.js b/examples/astro/kitchen-sink/src/queries/getLayout.js new file mode 100644 index 000000000..7f1b7f012 --- /dev/null +++ b/examples/astro/kitchen-sink/src/queries/getLayout.js @@ -0,0 +1,10 @@ +const { default: gql } = require('graphql-tag'); + +export const GET_LAYOUT = gql` + query GetLayout { + generalSettings { + title + description + } + } +`; diff --git a/examples/astro/kitchen-sink/src/queries/getPage.js b/examples/astro/kitchen-sink/src/queries/getPage.js new file mode 100644 index 000000000..76646f1a5 --- /dev/null +++ b/examples/astro/kitchen-sink/src/queries/getPage.js @@ -0,0 +1,27 @@ +import gql from 'graphql-tag'; + +export const GET_PAGE = gql` + query GetPage($databaseId: ID!, $asPreview: Boolean = false) { + page(id: $databaseId, idType: DATABASE_ID, asPreview: $asPreview) { + title + content + date + author { + node { + name + } + } + featuredImage { + node { + id + sourceUrl + altText + mediaDetails { + width + height + } + } + } + } + } +`; diff --git a/examples/astro/kitchen-sink/src/queries/getPost.js b/examples/astro/kitchen-sink/src/queries/getPost.js new file mode 100644 index 000000000..30ba7b8d7 --- /dev/null +++ b/examples/astro/kitchen-sink/src/queries/getPost.js @@ -0,0 +1,27 @@ +import gql from 'graphql-tag'; + +export const GET_POST = gql` + query GetPost($databaseId: ID!, $asPreview: Boolean = false) { + post(id: $databaseId, idType: DATABASE_ID, asPreview: $asPreview) { + title + content + date + author { + node { + name + } + } + featuredImage { + node { + id + sourceUrl + altText + mediaDetails { + width + height + } + } + } + } + } +`; diff --git a/examples/astro/kitchen-sink/src/queries/getPosts.js b/examples/astro/kitchen-sink/src/queries/getPosts.js new file mode 100644 index 000000000..9cfcdd4e1 --- /dev/null +++ b/examples/astro/kitchen-sink/src/queries/getPosts.js @@ -0,0 +1,33 @@ +import gql from 'graphql-tag'; + +export const GET_POSTS = gql` + query GetPosts { + posts { + edges { + node { + id + title + content + date + uri + featuredImage { + node { + id + sourceUrl + altText + mediaDetails { + width + height + } + } + } + author { + node { + name + } + } + } + } + } + } +`; diff --git a/examples/astro/kitchen-sink/src/utils/getAuthString.js b/examples/astro/kitchen-sink/src/utils/getAuthString.js new file mode 100644 index 000000000..666bc6721 --- /dev/null +++ b/examples/astro/kitchen-sink/src/utils/getAuthString.js @@ -0,0 +1,8 @@ +// Forming the authentication string for WordPress App Password +// More info: https://make.wordpress.org/core/2020/11/05/application-passwords-integration-guide/ + +export const getAuthString = () => + 'Basic ' + + Buffer.from( + import.meta.env.WP_USERNAME + ':' + import.meta.env.WP_APP_PASSWORD, + ).toString('base64'); diff --git a/examples/astro/kitchen-sink/tsconfig.json b/examples/astro/kitchen-sink/tsconfig.json new file mode 100644 index 000000000..547e4a6ce --- /dev/null +++ b/examples/astro/kitchen-sink/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "astro/tsconfigs/strict", + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/packages/astro/templateHierarchy.js b/packages/astro/templateHierarchy.js index a0e471f20..5ef364d02 100644 --- a/packages/astro/templateHierarchy.js +++ b/packages/astro/templateHierarchy.js @@ -20,7 +20,7 @@ console.log( * @param {import('@faustjs/template-hierarchy').UriToTemplateBaseParams & { graphqlClient?: import('./types.js').GraphQLClient }} options - Resolution options * @returns {Promise} The resolved template data */ -export async function uriToTemplate({ uri, graphqlClient }) { +export async function uriToTemplate({ uri, graphqlClient, id, asPreview }) { /** @type {import('@faustjs/template-hierarchy').TemplateData} */ const returnData = { uri, @@ -28,20 +28,30 @@ export async function uriToTemplate({ uri, graphqlClient }) { availableTemplates: undefined, possibleTemplates: undefined, template: undefined, + seedNode: undefined, }; // Get the GraphQL client - use provided one or get configured one const client = getGraphQLClient(graphqlClient); - const { data, error } = await getSeedQuery({ uri, graphqlClient: client }); + const { data, error } = await getSeedQuery({ + uri, + graphqlClient: client, + id, + asPreview, + }); returnData.seedQuery = { data, error }; + const seedNode = data?.nodeByUri || data?.contentNode; + + returnData.seedNode = seedNode ?? error; + if (error) { console.error('Error fetching seedQuery:', error); return returnData; } - if (!data.nodeByUri) { + if (!seedNode) { console.error('HTTP/404 - Not Found in WordPress:', uri); returnData.template = { id: '404 Not Found', path: '/404' }; @@ -58,7 +68,7 @@ export async function uriToTemplate({ uri, graphqlClient }) { return returnData; } - const possibleTemplates = getPossibleTemplates(data.nodeByUri); + const possibleTemplates = getPossibleTemplates(seedNode); returnData.possibleTemplates = possibleTemplates; diff --git a/packages/graphql/client.js b/packages/graphql/client.js index 87552e174..e438e3b5f 100644 --- a/packages/graphql/client.js +++ b/packages/graphql/client.js @@ -59,6 +59,8 @@ export function createDefaultGraphQLClient(wordpressUrl, headers = {}) { body: JSON.stringify({ query, variables }), }); + console.log('RRR', response); + if (!response.ok) { const message = await response.json(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dad6121ba..bc0f0e950 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -66,6 +66,31 @@ importers: specifier: ^3.0.2 version: 3.0.2 + examples/astro/kitchen-sink: + dependencies: + '@faustjs/astro': + specifier: workspace:* + version: link:../../../packages/astro + '@faustjs/data-fetching': + specifier: workspace:* + version: link:../../../packages/data-fetching + '@faustjs/template-hierarchy': + specifier: workspace:* + version: link:../../../packages/template-hierarchy + astro: + specifier: ^5.1.1 + version: 5.12.8(jiti@2.5.1)(lightningcss@1.30.1)(rollup@4.46.2)(typescript@5.8.3) + graphql: + specifier: ^16.9.0 + version: 16.11.0 + graphql-tag: + specifier: ^2.12.6 + version: 2.12.6(graphql@16.11.0) + devDependencies: + typescript: + specifier: ^5.6.3 + version: 5.8.3 + examples/astro/template-hierarchy: dependencies: '@faustjs/astro': @@ -88,7 +113,7 @@ importers: specifier: ^5.6.3 version: 5.8.3 - examples/nextjs/previews: + examples/nextjs/kitchen-sink: dependencies: '@faustjs/data-fetching': specifier: workspace:* @@ -6021,7 +6046,7 @@ snapshots: '@typescript-eslint/parser': 5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) eslint: 9.33.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.33.0(jiti@2.5.1)) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.33.0(jiti@2.5.1)) eslint-plugin-react: 7.37.5(eslint@9.33.0(jiti@2.5.1)) @@ -6080,7 +6105,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.33.0(jiti@2.5.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1 @@ -6106,14 +6131,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) eslint: 9.33.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.33.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color @@ -6157,7 +6182,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.33.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 From bb8966a22635e38e86993796813093ab17e20da9 Mon Sep 17 00:00:00 2001 From: ahuseyn Date: Fri, 22 Aug 2025 13:54:47 +0200 Subject: [PATCH 12/24] update templates, add data fetching --- examples/astro/kitchen-sink/astro.config.mjs | 2 + examples/astro/kitchen-sink/package.json | 6 +- .../src/components/BlogPostItem.astro | 35 ++++ .../kitchen-sink/src/components/Header.astro | 18 ++ .../kitchen-sink/src/components/Layout.astro | 23 ++ .../kitchen-sink/src/components/Nav.astro | 50 ----- .../components/TemplateHierarchyInfo.astro | 52 ----- .../kitchen-sink/src/layouts/Layout.astro | 36 ---- .../src/layouts/WordPressLayout.astro | 66 ------ .../astro/kitchen-sink/src/pages/404.astro | 32 +-- .../src/pages/[...identifier].astro | 18 +- .../astro/kitchen-sink/src/pages/index.astro | 198 ------------------ .../src/pages/wp-templates/archive.astro | 129 +++++------- .../src/pages/wp-templates/home.astro | 99 ++------- .../src/pages/wp-templates/index.astro | 115 ++++------ .../src/pages/wp-templates/page.astro | 51 +++++ .../src/pages/wp-templates/single.astro | 6 +- .../kitchen-sink/src/queries/getLayout.js | 2 +- .../astro/kitchen-sink/src/styles/globals.css | 1 + pnpm-lock.yaml | 182 +++++++++++++++- 20 files changed, 453 insertions(+), 668 deletions(-) create mode 100644 examples/astro/kitchen-sink/src/components/BlogPostItem.astro create mode 100644 examples/astro/kitchen-sink/src/components/Header.astro create mode 100644 examples/astro/kitchen-sink/src/components/Layout.astro delete mode 100644 examples/astro/kitchen-sink/src/components/Nav.astro delete mode 100644 examples/astro/kitchen-sink/src/components/TemplateHierarchyInfo.astro delete mode 100644 examples/astro/kitchen-sink/src/layouts/Layout.astro delete mode 100644 examples/astro/kitchen-sink/src/layouts/WordPressLayout.astro delete mode 100644 examples/astro/kitchen-sink/src/pages/index.astro create mode 100644 examples/astro/kitchen-sink/src/pages/wp-templates/page.astro create mode 100644 examples/astro/kitchen-sink/src/styles/globals.css diff --git a/examples/astro/kitchen-sink/astro.config.mjs b/examples/astro/kitchen-sink/astro.config.mjs index b1ccfb253..2ec71dc14 100644 --- a/examples/astro/kitchen-sink/astro.config.mjs +++ b/examples/astro/kitchen-sink/astro.config.mjs @@ -1,4 +1,5 @@ import { defineConfig } from 'astro/config'; +import tailwindcss from '@tailwindcss/vite'; export default defineConfig({ output: 'server', @@ -27,5 +28,6 @@ export default defineConfig({ '@faustjs/graphql', ], }, + plugins: [tailwindcss()], }, }); diff --git a/examples/astro/kitchen-sink/package.json b/examples/astro/kitchen-sink/package.json index 7f52963b1..664a4cec2 100644 --- a/examples/astro/kitchen-sink/package.json +++ b/examples/astro/kitchen-sink/package.json @@ -13,11 +13,13 @@ }, "dependencies": { "@faustjs/astro": "workspace:*", - "@faustjs/template-hierarchy": "workspace:*", "@faustjs/data-fetching": "workspace:*", + "@faustjs/template-hierarchy": "workspace:*", + "@tailwindcss/vite": "^4.1.12", "astro": "^5.1.1", "graphql": "^16.9.0", - "graphql-tag": "^2.12.6" + "graphql-tag": "^2.12.6", + "tailwindcss": "^4.1.11" }, "devDependencies": { "typescript": "^5.6.3" diff --git a/examples/astro/kitchen-sink/src/components/BlogPostItem.astro b/examples/astro/kitchen-sink/src/components/BlogPostItem.astro new file mode 100644 index 000000000..420595e43 --- /dev/null +++ b/examples/astro/kitchen-sink/src/components/BlogPostItem.astro @@ -0,0 +1,35 @@ +--- +const { title, date, excerpt, uri, featuredImage } = Astro.props?.post ?? {}; +--- + +
+ + + { + featuredImage && ( + + ) + } + +

+ + {title} + +

+ +
diff --git a/examples/astro/kitchen-sink/src/components/Header.astro b/examples/astro/kitchen-sink/src/components/Header.astro new file mode 100644 index 000000000..a22290c8b --- /dev/null +++ b/examples/astro/kitchen-sink/src/components/Header.astro @@ -0,0 +1,18 @@ +--- +import { GET_LAYOUT } from '@/queries/getLayout'; +import { print } from 'graphql'; + +const { data } = await Astro.locals.client?.request(print(GET_LAYOUT)); +--- + +
+ +
diff --git a/examples/astro/kitchen-sink/src/components/Layout.astro b/examples/astro/kitchen-sink/src/components/Layout.astro new file mode 100644 index 000000000..01f18537a --- /dev/null +++ b/examples/astro/kitchen-sink/src/components/Layout.astro @@ -0,0 +1,23 @@ +--- +import Header from './Header.astro'; +import '../styles/globals.css'; + +const pageTitle = Astro.props.pageTitle; +--- + + + + + + + + + {pageTitle} + + +
+
+ +
+ + diff --git a/examples/astro/kitchen-sink/src/components/Nav.astro b/examples/astro/kitchen-sink/src/components/Nav.astro deleted file mode 100644 index d35cab7de..000000000 --- a/examples/astro/kitchen-sink/src/components/Nav.astro +++ /dev/null @@ -1,50 +0,0 @@ ---- -/** - * We've hard coded this but you can use the client to fetch this data from WP Menus! - * import { client, gql } from "../lib/client"; - */ ---- - - - diff --git a/examples/astro/kitchen-sink/src/components/TemplateHierarchyInfo.astro b/examples/astro/kitchen-sink/src/components/TemplateHierarchyInfo.astro deleted file mode 100644 index 3bbaafe89..000000000 --- a/examples/astro/kitchen-sink/src/components/TemplateHierarchyInfo.astro +++ /dev/null @@ -1,52 +0,0 @@ ---- -const template = Astro.locals.templateData; - -if (!template) { - return null; -} ---- - - diff --git a/examples/astro/kitchen-sink/src/layouts/Layout.astro b/examples/astro/kitchen-sink/src/layouts/Layout.astro deleted file mode 100644 index f078b89be..000000000 --- a/examples/astro/kitchen-sink/src/layouts/Layout.astro +++ /dev/null @@ -1,36 +0,0 @@ ---- -// import Nav from '../components/Nav.astro'; -// import TemplateHierarchyInfo from "../components/TemplateHierarchyInfo.astro"; ---- - - - - - - - - - Astro Basics - - - - -
- -
- - - - diff --git a/examples/astro/kitchen-sink/src/layouts/WordPressLayout.astro b/examples/astro/kitchen-sink/src/layouts/WordPressLayout.astro deleted file mode 100644 index 2c95b429f..000000000 --- a/examples/astro/kitchen-sink/src/layouts/WordPressLayout.astro +++ /dev/null @@ -1,66 +0,0 @@ ---- -export interface Props { - title: string; -} - -const { title } = Astro.props; ---- - - - - - - - - {title} - - -
- -
- - - - diff --git a/examples/astro/kitchen-sink/src/pages/404.astro b/examples/astro/kitchen-sink/src/pages/404.astro index b220b4f32..048321584 100644 --- a/examples/astro/kitchen-sink/src/pages/404.astro +++ b/examples/astro/kitchen-sink/src/pages/404.astro @@ -1,24 +1,26 @@ --- -import WordPressLayout from '@/layouts/WordPressLayout.astro'; +import Layout from '@/components/Layout.astro'; // Set the response status to 404 Astro.response.status = 404; --- - -

404 - Page Not Found

-

Sorry, the page you're looking for doesn't exist.

-
-

This could happen if:

-
    -
  • The page has been moved or deleted
  • -
  • You typed the URL incorrectly
  • -
  • The WordPress content couldn't be found
  • -
-

- + +

- + diff --git a/examples/astro/kitchen-sink/src/pages/[...identifier].astro b/examples/astro/kitchen-sink/src/pages/[...identifier].astro index 6d7384cc2..8e8fd1a02 100644 --- a/examples/astro/kitchen-sink/src/pages/[...identifier].astro +++ b/examples/astro/kitchen-sink/src/pages/[...identifier].astro @@ -16,7 +16,6 @@ const headers = isPreview // Set up GraphQL client using environment variable const client = createDefaultClient(import.meta.env.WORDPRESS_URL, headers); - setGraphQLClient(client); // URI comes as a string from Astro params, ensure it starts with / @@ -29,12 +28,8 @@ const variables = isPreview } : { uri: identifier }; -console.log('VVV', variables); - const results = await uriToTemplate(variables); -console.log(results); - Astro.locals.templateData = results; Astro.locals.isPreview = isPreview || false; Astro.locals.client = client; @@ -42,15 +37,6 @@ Astro.locals.client = client; if (results.template) { return Astro.rewrite(results.template.path); } ---- -

Oops! You shouldn't be here, something went wrong!

-
-	
-	  {JSON.stringify(
-		results,
-		null,
-		2
-	  )}
-	
-  
+return Astro.rewrite('/404'); +--- diff --git a/examples/astro/kitchen-sink/src/pages/index.astro b/examples/astro/kitchen-sink/src/pages/index.astro deleted file mode 100644 index 9859cf006..000000000 --- a/examples/astro/kitchen-sink/src/pages/index.astro +++ /dev/null @@ -1,198 +0,0 @@ ---- -// Demo page showing template discovery -import { getCollection } from 'astro:content'; - -// Get all templates from our content collection -const templates = await getCollection('templates'); ---- - - - - - - - - @faustjs/astro Example - - -
-

@faustjs/astro Template Hierarchy Example

-

- This example demonstrates how the @faustjs/astro package discovers - WordPress templates in your Astro project using the WordPress template - hierarchy. -

- -

Discovered Templates

-

- The following templates were automatically discovered from the wp-templates directory: -

- -
- { - templates.map((template) => ( -
-

{template.data.name}

-

- Type: {template.data.type} -

-

- Priority: {template.data.priority} -

-

- Path: {template.data.path} -

- {template.data.description && ( -

- Description: {template.data.description} -

- )} -
- )) - } -
- -

How It Works

-
-
    -
  1. - Templates are placed in the src/pages/wp-templates/ directory -
  2. -
  3. - The createTemplateCollection() function automatically discovers - them -
  4. -
  5. Templates are organized by WordPress template hierarchy rules
  6. -
  7. Each template gets metadata like type, priority, and path
  8. -
  9. You can query templates using Astro's content collections API
  10. -
-
- -

Live Template Router Demo

-

- See the template hierarchy in action with our catch-all route that - resolves WordPress URLs to templates: -

- -
- - - - diff --git a/examples/astro/kitchen-sink/src/pages/wp-templates/archive.astro b/examples/astro/kitchen-sink/src/pages/wp-templates/archive.astro index 89eb63d38..5603eebfe 100644 --- a/examples/astro/kitchen-sink/src/pages/wp-templates/archive.astro +++ b/examples/astro/kitchen-sink/src/pages/wp-templates/archive.astro @@ -1,86 +1,65 @@ --- -import Layout from "../../layouts/Layout.astro"; -import { authHeaders, client, gql } from "../../lib/client"; -/** - * This is a template for the WordPress template hierarchy. - * It will be used to render the WordPress templates. - * It should never be used directly. - * This check confirms that the template is being used in the correct context. - * If the template is being used directly, it will redirect to the 404 page. - */ +import Layout from '@/components/Layout.astro'; +import { GET_ARCHIVE } from '@/queries/getArchive'; +import { print } from 'graphql'; + if (Astro.url.pathname === Astro.originPathname) { - return Astro.rewrite("/404"); + return Astro.rewrite('/404'); } -const isPreview = Astro.locals.isPreview; +const uri = Astro.locals.templateData?.seedNode?.uri; -const { data, error } = await client.query( - gql` - query ArchiveTemplateNodeQuery($uri: String!) { - node: nodeByUri(uri: $uri) { - __typename - ... on TermNode { - name - description - } - ... on Tag { - contentNodes(where: { stati: [DRAFT, PUBLISH, FUTURE, PENDING] }) { - nodes { - ... on NodeWithTitle { - title - } - uri - } - } - } - ... on Category { - contentNodes(where: { stati: [DRAFT, PUBLISH, FUTURE, PENDING] }) { - nodes { - ... on NodeWithTitle { - title - } - uri - } - } - } - } - terms { - nodes { - uri - } - } - } - `, - { - uri: Astro.originPathname, - }, - { - fetchOptions: { - headers: { - ...authHeaders(isPreview), - }, - }, - } -); +const { data, error } = await Astro.locals.client.request(print(GET_ARCHIVE), { + uri, +}); if (error) { - console.error("Error fetching data:", error); - return Astro.rewrite("/500"); + console.error('Error fetching data:', error); + return Astro.rewrite('/500'); +} + +if (!data?.nodeByUri) { + console.error('HTTP/404 - Not Found in WordPress:', uri); + return Astro.rewrite('/404'); } + +const categoryData = data?.nodeByUri; +const { posts, name } = categoryData || {}; --- - -
-

{data.node.name}

-

-

    - { - data.node.contentNodes.nodes.map((content) => ( -
  1. - -
  2. - )) - } -
-
+ +
+

{name}

+ + { + posts?.edges?.map((item: any) => { + const post = item.node; + + return ( +
+

+ + {post.title} + +

+ + {post.featuredImage && ( + {post.featuredImage.node.altText + )} + +
+ By {post.author.node.name} on{' '} + {new Date(post.date).toLocaleDateString()} +
+ +
+
+ ); + }) + } +
diff --git a/examples/astro/kitchen-sink/src/pages/wp-templates/home.astro b/examples/astro/kitchen-sink/src/pages/wp-templates/home.astro index 816aada23..a64c950c4 100644 --- a/examples/astro/kitchen-sink/src/pages/wp-templates/home.astro +++ b/examples/astro/kitchen-sink/src/pages/wp-templates/home.astro @@ -1,88 +1,31 @@ --- -import Layout from "../../layouts/Layout.astro"; -import { authHeaders, client, gql } from "../../lib/client"; +import Layout from '@/components/Layout.astro'; +import BlogPostItem from '@/components/BlogPostItem.astro'; +import { GET_POSTS } from '@/queries/getPosts'; +import { print } from 'graphql'; -/** - * This is a template for the WordPress template hierarchy. - * It will be used to render the WordPress templates. - * It should never be used directly. - * This check confirms that the template is being used in the correct context. - * If the template is being used directly, it will redirect to the 404 page. - */ -if (Astro.url.pathname === Astro.originPathname) { - return Astro.rewrite("/404"); -} - -const isPreview = Astro.locals.isPreview; +const databaseId = Astro.locals.templateData?.seedNode?.databaseId; -const { data, error } = await client.query( - gql` - query homeTemplatePostQuery { - posts(first: 6, where: { stati: [DRAFT, PUBLISH, FUTURE, PENDING] }) { - nodes { - id - title - uri - excerpt - } - } - } - `, - {}, - { - fetchOptions: { - headers: { - ...authHeaders(isPreview), - }, - }, - } -); +const { data, error } = await Astro.locals.client.request(print(GET_POSTS)); if (error) { - console.error("Error fetching data:", error); - return Astro.rewrite("/500"); + console.error('Error fetching data:', error); + return Astro.rewrite('/500'); } ---- - -
-

My WP + Astro Blog!

+if (!data.posts) { + console.error('HTTP/404 - Not Found in WordPress:', databaseId); + return Astro.rewrite('/404'); +} -

I like sharing my life!

+const posts = data?.posts; +--- -
-

Recent Posts

-
- { - data.posts.nodes.map((post) => { - return ( -
-

{post.title}

-
- - Read more... - -
- ); - }) - } -
-
-
+ + { + posts?.edges?.map((item: any) => { + const post = item.node; + return ; + }) + } - - diff --git a/examples/astro/kitchen-sink/src/pages/wp-templates/index.astro b/examples/astro/kitchen-sink/src/pages/wp-templates/index.astro index 2c0cca05d..9bd4d8452 100644 --- a/examples/astro/kitchen-sink/src/pages/wp-templates/index.astro +++ b/examples/astro/kitchen-sink/src/pages/wp-templates/index.astro @@ -1,80 +1,53 @@ --- +import Layout from '@/components/Layout.astro'; + /** - * This is a template for the WordPress template hierarchy. - * It will be used to render the WordPress templates. - * It should never be used directly. - * This check confirms that the template is being used in the correct context. - * If the template is being used directly, it will redirect to the 404 page. + * This is the index template for the WordPress template hierarchy. + * It serves as the fallback template for any content that doesn't have + * a more specific template (like single, page, archive, etc.). + * This template should never be accessed directly. */ if (Astro.url.pathname === Astro.originPathname) { - return Astro.rewrite("/404"); + return Astro.rewrite('/404'); } -import Layout from "../../layouts/Layout.astro"; -import { authHeaders, client, gql } from "../../lib/client"; - -const databaseId = Astro.locals.templateData?.databaseId; -const isPreview = Astro.locals.isPreview; - -const query = gql` - query indexTemplateNodeQuery($id: ID!, $isPreview: Boolean = false) { - node: contentNode(id: $id, idType: DATABASE_ID, asPreview: $isPreview) { - __typename - uri - id - ... on NodeWithTitle { - title - } - ... on NodeWithContentEditor { - content - } - } - } -`; - -const { data, error } = await client.query( - query, - { - id: databaseId, - isPreview, - }, - { - fetchOptions: { - headers: { - ...authHeaders(isPreview), - }, - }, - } -); - -if (error) { - console.error("Error fetching data:", error); - return Astro.rewrite("/500"); -} +const templateData = Astro.locals.templateData; +const seedNode = templateData?.seedNode; +const contentType = seedNode?.__typename || 'content'; --- - -

- This is the index template for the WordPress template hierarchy. - It will be used to render the WordPress content if no more appropriate template - is provided (e.g. front-page, single, singular, archive, etc). It should never - be used directly. -

- - {data.node.title &&

} - {data.node.content &&
} - { - !data.node.content && !data.node.title && ( -
-        {JSON.stringify(data ?? {}, null, 2)}
-      
- ) - } + +
+
+

+ Template Not Found +

+

+ No specific template was found for the {contentType} type. + This content is being displayed using the default fallback template. +

+
+ + + +
+

+ Debug Information +

+
+
{JSON.stringify(
+					templateData,
+					null,
+					2
+				)}
+
+
+
- - diff --git a/examples/astro/kitchen-sink/src/pages/wp-templates/page.astro b/examples/astro/kitchen-sink/src/pages/wp-templates/page.astro new file mode 100644 index 000000000..e26837114 --- /dev/null +++ b/examples/astro/kitchen-sink/src/pages/wp-templates/page.astro @@ -0,0 +1,51 @@ +--- +import Layout from '@/components/Layout.astro'; +import { GET_PAGE } from '@/queries/getPage'; +import { print } from 'graphql'; + +if (Astro.url.pathname === Astro.originPathname) { + return Astro.rewrite('/404'); +} + +const isPreview = Astro.locals.isPreview; +const databaseId = Astro.locals.templateData?.seedNode?.databaseId; + +const { data, error } = await Astro.locals.client.request(print(GET_PAGE), { + databaseId, + asPreview: isPreview, +}); + +if (error) { + console.error('Error fetching data:', error); + return Astro.rewrite('/500'); +} + +if (!data.page) { + console.error('HTTP/404 - Not Found in WordPress:', databaseId); + return Astro.rewrite('/404'); +} + +const { title, content, featuredImage } = data?.page || {}; +--- + + +
+
+

+ {title} +

+
+ + { + featuredImage && ( + + ) + } + +
+
+
diff --git a/examples/astro/kitchen-sink/src/pages/wp-templates/single.astro b/examples/astro/kitchen-sink/src/pages/wp-templates/single.astro index e9b772414..c00431300 100644 --- a/examples/astro/kitchen-sink/src/pages/wp-templates/single.astro +++ b/examples/astro/kitchen-sink/src/pages/wp-templates/single.astro @@ -1,6 +1,6 @@ --- -import Layout from '../../layouts/Layout.astro'; -import { GET_POST } from '../../queries/getPost.js'; +import Layout from '@/components/Layout.astro'; +import { GET_POST } from '@/queries/getPost.js'; import { print } from 'graphql'; /** @@ -35,7 +35,7 @@ if (!data.post) { const { title, content, featuredImage } = data?.post || {}; --- - +

diff --git a/examples/astro/kitchen-sink/src/queries/getLayout.js b/examples/astro/kitchen-sink/src/queries/getLayout.js index 7f1b7f012..f278ee1af 100644 --- a/examples/astro/kitchen-sink/src/queries/getLayout.js +++ b/examples/astro/kitchen-sink/src/queries/getLayout.js @@ -1,4 +1,4 @@ -const { default: gql } = require('graphql-tag'); +import { gql } from 'graphql-tag'; export const GET_LAYOUT = gql` query GetLayout { diff --git a/examples/astro/kitchen-sink/src/styles/globals.css b/examples/astro/kitchen-sink/src/styles/globals.css new file mode 100644 index 000000000..d4b507858 --- /dev/null +++ b/examples/astro/kitchen-sink/src/styles/globals.css @@ -0,0 +1 @@ +@import 'tailwindcss'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bc0f0e950..7e18df114 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -77,6 +77,9 @@ importers: '@faustjs/template-hierarchy': specifier: workspace:* version: link:../../../packages/template-hierarchy + '@tailwindcss/vite': + specifier: ^4.1.12 + version: 4.1.12(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)) astro: specifier: ^5.1.1 version: 5.12.8(jiti@2.5.1)(lightningcss@1.30.1)(rollup@4.46.2)(typescript@5.8.3) @@ -86,6 +89,9 @@ importers: graphql-tag: specifier: ^2.12.6 version: 2.12.6(graphql@16.11.0) + tailwindcss: + specifier: ^4.1.11 + version: 4.1.11 devDependencies: typescript: specifier: ^5.6.3 @@ -1298,60 +1304,117 @@ packages: '@tailwindcss/node@4.1.11': resolution: {integrity: sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==} + '@tailwindcss/node@4.1.12': + resolution: {integrity: sha512-3hm9brwvQkZFe++SBt+oLjo4OLDtkvlE8q2WalaD/7QWaeM7KEJbAiY/LJZUaCs7Xa8aUu4xy3uoyX4q54UVdQ==} + '@tailwindcss/oxide-android-arm64@4.1.11': resolution: {integrity: sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==} engines: {node: '>= 10'} cpu: [arm64] os: [android] + '@tailwindcss/oxide-android-arm64@4.1.12': + resolution: {integrity: sha512-oNY5pq+1gc4T6QVTsZKwZaGpBb2N1H1fsc1GD4o7yinFySqIuRZ2E4NvGasWc6PhYJwGK2+5YT1f9Tp80zUQZQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + '@tailwindcss/oxide-darwin-arm64@4.1.11': resolution: {integrity: sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] + '@tailwindcss/oxide-darwin-arm64@4.1.12': + resolution: {integrity: sha512-cq1qmq2HEtDV9HvZlTtrj671mCdGB93bVY6J29mwCyaMYCP/JaUBXxrQQQm7Qn33AXXASPUb2HFZlWiiHWFytw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + '@tailwindcss/oxide-darwin-x64@4.1.11': resolution: {integrity: sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] + '@tailwindcss/oxide-darwin-x64@4.1.12': + resolution: {integrity: sha512-6UCsIeFUcBfpangqlXay9Ffty9XhFH1QuUFn0WV83W8lGdX8cD5/+2ONLluALJD5+yJ7k8mVtwy3zMZmzEfbLg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + '@tailwindcss/oxide-freebsd-x64@4.1.11': resolution: {integrity: sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==} engines: {node: '>= 10'} cpu: [x64] os: [freebsd] + '@tailwindcss/oxide-freebsd-x64@4.1.12': + resolution: {integrity: sha512-JOH/f7j6+nYXIrHobRYCtoArJdMJh5zy5lr0FV0Qu47MID/vqJAY3r/OElPzx1C/wdT1uS7cPq+xdYYelny1ww==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11': resolution: {integrity: sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==} engines: {node: '>= 10'} cpu: [arm] os: [linux] + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.12': + resolution: {integrity: sha512-v4Ghvi9AU1SYgGr3/j38PD8PEe6bRfTnNSUE3YCMIRrrNigCFtHZ2TCm8142X8fcSqHBZBceDx+JlFJEfNg5zQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + '@tailwindcss/oxide-linux-arm64-gnu@4.1.11': resolution: {integrity: sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + '@tailwindcss/oxide-linux-arm64-gnu@4.1.12': + resolution: {integrity: sha512-YP5s1LmetL9UsvVAKusHSyPlzSRqYyRB0f+Kl/xcYQSPLEw/BvGfxzbH+ihUciePDjiXwHh+p+qbSP3SlJw+6g==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + '@tailwindcss/oxide-linux-arm64-musl@4.1.11': resolution: {integrity: sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + '@tailwindcss/oxide-linux-arm64-musl@4.1.12': + resolution: {integrity: sha512-V8pAM3s8gsrXcCv6kCHSuwyb/gPsd863iT+v1PGXC4fSL/OJqsKhfK//v8P+w9ThKIoqNbEnsZqNy+WDnwQqCA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + '@tailwindcss/oxide-linux-x64-gnu@4.1.11': resolution: {integrity: sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + '@tailwindcss/oxide-linux-x64-gnu@4.1.12': + resolution: {integrity: sha512-xYfqYLjvm2UQ3TZggTGrwxjYaLB62b1Wiysw/YE3Yqbh86sOMoTn0feF98PonP7LtjsWOWcXEbGqDL7zv0uW8Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + '@tailwindcss/oxide-linux-x64-musl@4.1.11': resolution: {integrity: sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + '@tailwindcss/oxide-linux-x64-musl@4.1.12': + resolution: {integrity: sha512-ha0pHPamN+fWZY7GCzz5rKunlv9L5R8kdh+YNvP5awe3LtuXb5nRi/H27GeL2U+TdhDOptU7T6Is7mdwh5Ar3A==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + '@tailwindcss/oxide-wasm32-wasi@4.1.11': resolution: {integrity: sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==} engines: {node: '>=14.0.0'} @@ -1364,25 +1427,58 @@ packages: - '@emnapi/wasi-threads' - tslib + '@tailwindcss/oxide-wasm32-wasi@4.1.12': + resolution: {integrity: sha512-4tSyu3dW+ktzdEpuk6g49KdEangu3eCYoqPhWNsZgUhyegEda3M9rG0/j1GV/JjVVsj+lG7jWAyrTlLzd/WEBg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + '@tailwindcss/oxide-win32-arm64-msvc@4.1.11': resolution: {integrity: sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] + '@tailwindcss/oxide-win32-arm64-msvc@4.1.12': + resolution: {integrity: sha512-iGLyD/cVP724+FGtMWslhcFyg4xyYyM+5F4hGvKA7eifPkXHRAUDFaimu53fpNg9X8dfP75pXx/zFt/jlNF+lg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + '@tailwindcss/oxide-win32-x64-msvc@4.1.11': resolution: {integrity: sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==} engines: {node: '>= 10'} cpu: [x64] os: [win32] + '@tailwindcss/oxide-win32-x64-msvc@4.1.12': + resolution: {integrity: sha512-NKIh5rzw6CpEodv/++r0hGLlfgT/gFN+5WNdZtvh6wpU2BpGNgdjvj6H2oFc8nCM839QM1YOhjpgbAONUb4IxA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + '@tailwindcss/oxide@4.1.11': resolution: {integrity: sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==} engines: {node: '>= 10'} + '@tailwindcss/oxide@4.1.12': + resolution: {integrity: sha512-gM5EoKHW/ukmlEtphNwaGx45fGoEmP10v51t9unv55voWh6WrOL19hfuIdo2FjxIaZzw776/BUQg7Pck++cIVw==} + engines: {node: '>= 10'} + '@tailwindcss/postcss@4.1.11': resolution: {integrity: sha512-q/EAIIpF6WpLhKEuQSEVMZNMIY8KhWoAemZ9eylNAih9jxMGAYPPWBn3I9QL/2jZ+e7OEz/tZkX5HwbBR4HohA==} + '@tailwindcss/vite@4.1.12': + resolution: {integrity: sha512-4pt0AMFDx7gzIrAOIYgYP0KCBuKWqyW8ayrdiLEjoJTT4pKTjrzG/e4uzWtTLDziC+66R9wbUqZBccJalSE5vQ==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + '@tybys/wasm-util@0.10.0': resolution: {integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==} @@ -3725,6 +3821,9 @@ packages: tailwindcss@4.1.11: resolution: {integrity: sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==} + tailwindcss@4.1.12: + resolution: {integrity: sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==} + tapable@2.2.2: resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} engines: {node: '>=6'} @@ -5054,42 +5153,88 @@ snapshots: source-map-js: 1.2.1 tailwindcss: 4.1.11 + '@tailwindcss/node@4.1.12': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.18.3 + jiti: 2.5.1 + lightningcss: 1.30.1 + magic-string: 0.30.17 + source-map-js: 1.2.1 + tailwindcss: 4.1.12 + '@tailwindcss/oxide-android-arm64@4.1.11': optional: true + '@tailwindcss/oxide-android-arm64@4.1.12': + optional: true + '@tailwindcss/oxide-darwin-arm64@4.1.11': optional: true + '@tailwindcss/oxide-darwin-arm64@4.1.12': + optional: true + '@tailwindcss/oxide-darwin-x64@4.1.11': optional: true + '@tailwindcss/oxide-darwin-x64@4.1.12': + optional: true + '@tailwindcss/oxide-freebsd-x64@4.1.11': optional: true + '@tailwindcss/oxide-freebsd-x64@4.1.12': + optional: true + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11': optional: true + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.12': + optional: true + '@tailwindcss/oxide-linux-arm64-gnu@4.1.11': optional: true + '@tailwindcss/oxide-linux-arm64-gnu@4.1.12': + optional: true + '@tailwindcss/oxide-linux-arm64-musl@4.1.11': optional: true + '@tailwindcss/oxide-linux-arm64-musl@4.1.12': + optional: true + '@tailwindcss/oxide-linux-x64-gnu@4.1.11': optional: true + '@tailwindcss/oxide-linux-x64-gnu@4.1.12': + optional: true + '@tailwindcss/oxide-linux-x64-musl@4.1.11': optional: true + '@tailwindcss/oxide-linux-x64-musl@4.1.12': + optional: true + '@tailwindcss/oxide-wasm32-wasi@4.1.11': optional: true + '@tailwindcss/oxide-wasm32-wasi@4.1.12': + optional: true + '@tailwindcss/oxide-win32-arm64-msvc@4.1.11': optional: true + '@tailwindcss/oxide-win32-arm64-msvc@4.1.12': + optional: true + '@tailwindcss/oxide-win32-x64-msvc@4.1.11': optional: true + '@tailwindcss/oxide-win32-x64-msvc@4.1.12': + optional: true + '@tailwindcss/oxide@4.1.11': dependencies: detect-libc: 2.0.4 @@ -5108,6 +5253,24 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.1.11 '@tailwindcss/oxide-win32-x64-msvc': 4.1.11 + '@tailwindcss/oxide@4.1.12': + dependencies: + detect-libc: 2.0.4 + tar: 7.4.3 + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.12 + '@tailwindcss/oxide-darwin-arm64': 4.1.12 + '@tailwindcss/oxide-darwin-x64': 4.1.12 + '@tailwindcss/oxide-freebsd-x64': 4.1.12 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.12 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.12 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.12 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.12 + '@tailwindcss/oxide-linux-x64-musl': 4.1.12 + '@tailwindcss/oxide-wasm32-wasi': 4.1.12 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.12 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.12 + '@tailwindcss/postcss@4.1.11': dependencies: '@alloc/quick-lru': 5.2.0 @@ -5116,6 +5279,13 @@ snapshots: postcss: 8.5.6 tailwindcss: 4.1.11 + '@tailwindcss/vite@4.1.12(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1))': + dependencies: + '@tailwindcss/node': 4.1.12 + '@tailwindcss/oxide': 4.1.12 + tailwindcss: 4.1.12 + vite: 6.3.5(jiti@2.5.1)(lightningcss@1.30.1) + '@tybys/wasm-util@0.10.0': dependencies: tslib: 2.8.1 @@ -6046,7 +6216,7 @@ snapshots: '@typescript-eslint/parser': 5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) eslint: 9.33.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.33.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.33.0(jiti@2.5.1)) eslint-plugin-react: 7.37.5(eslint@9.33.0(jiti@2.5.1)) @@ -6105,7 +6275,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.33.0(jiti@2.5.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1 @@ -6131,14 +6301,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) eslint: 9.33.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.33.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color @@ -6182,7 +6352,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.33.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -8299,6 +8469,8 @@ snapshots: tailwindcss@4.1.11: {} + tailwindcss@4.1.12: {} + tapable@2.2.2: {} tar@7.4.3: From 5272a829cdceff4a9f105085b4e0b22358a6dfce Mon Sep 17 00:00:00 2001 From: ahuseyn Date: Fri, 22 Aug 2025 15:44:56 +0200 Subject: [PATCH 13/24] remove logs, add comments, minor fixes --- .../src/pages/[...identifier].astro | 42 ++++++++++--------- .../src/pages/wp-templates/archive.astro | 7 ++++ .../src/pages/wp-templates/home.astro | 11 +++++ .../src/pages/wp-templates/page.astro | 7 ++++ packages/graphql/client.js | 2 - packages/nextjs/pages/templateHierarchy.js | 2 - .../template-hierarchy/seedQueryExecutor.js | 2 - 7 files changed, 47 insertions(+), 26 deletions(-) diff --git a/examples/astro/kitchen-sink/src/pages/[...identifier].astro b/examples/astro/kitchen-sink/src/pages/[...identifier].astro index 8e8fd1a02..81882980d 100644 --- a/examples/astro/kitchen-sink/src/pages/[...identifier].astro +++ b/examples/astro/kitchen-sink/src/pages/[...identifier].astro @@ -4,39 +4,41 @@ import { createDefaultClient, setGraphQLClient, } from '@faustjs/astro'; -import { getAuthString } from '../utils/getAuthString.js'; +import { getAuthString } from '@/utils/getAuthString.js'; -const isPreview = Astro.url.searchParams.get('preview') === 'true'; +// Determine if we are in preview mode based on the URL parameter and the secret +const isPreview = + Astro.url.searchParams.get('preview') === 'true' && + import.meta.env.WP_PREVIEW_SECRET === Astro.url.searchParams.get('secret'); -const headers = isPreview - ? { - Authorization: getAuthString(), - } - : null; +const headers = isPreview ? { Authorization: getAuthString() } : undefined; // Set up GraphQL client using environment variable const client = createDefaultClient(import.meta.env.WORDPRESS_URL, headers); setGraphQLClient(client); -// URI comes as a string from Astro params, ensure it starts with / const { identifier = '/' } = Astro.params; -const variables = isPreview - ? { - id: identifier, - asPreview: true, - } - : { uri: identifier }; - -const results = await uriToTemplate(variables); - -Astro.locals.templateData = results; +// Fetch template data based on the URI and preview mode +const templateData = await uriToTemplate( + isPreview + ? { + id: identifier, + asPreview: true, + } + : { uri: identifier }, +); + +// Make data available to other templates and components +Astro.locals.templateData = templateData; Astro.locals.isPreview = isPreview || false; Astro.locals.client = client; -if (results.template) { - return Astro.rewrite(results.template.path); +// If a template was found, rewrite to that template's path +if (templateData.template) { + return Astro.rewrite(templateData.template.path); } +// If no template was found, redirect to 404 return Astro.rewrite('/404'); --- diff --git a/examples/astro/kitchen-sink/src/pages/wp-templates/archive.astro b/examples/astro/kitchen-sink/src/pages/wp-templates/archive.astro index 5603eebfe..26ee4def3 100644 --- a/examples/astro/kitchen-sink/src/pages/wp-templates/archive.astro +++ b/examples/astro/kitchen-sink/src/pages/wp-templates/archive.astro @@ -3,6 +3,13 @@ import Layout from '@/components/Layout.astro'; import { GET_ARCHIVE } from '@/queries/getArchive'; import { print } from 'graphql'; +/** + * This is a template for the WordPress template hierarchy. + * It will be used to render the WordPress templates. + * It should never be used directly. + * This check confirms that the template is being used in the correct context. + * If the template is being used directly, it will redirect to the 404 page. + */ if (Astro.url.pathname === Astro.originPathname) { return Astro.rewrite('/404'); } diff --git a/examples/astro/kitchen-sink/src/pages/wp-templates/home.astro b/examples/astro/kitchen-sink/src/pages/wp-templates/home.astro index a64c950c4..ea5cdb354 100644 --- a/examples/astro/kitchen-sink/src/pages/wp-templates/home.astro +++ b/examples/astro/kitchen-sink/src/pages/wp-templates/home.astro @@ -4,6 +4,17 @@ import BlogPostItem from '@/components/BlogPostItem.astro'; import { GET_POSTS } from '@/queries/getPosts'; import { print } from 'graphql'; +/** + * This is a template for the WordPress template hierarchy. + * It will be used to render the WordPress templates. + * It should never be used directly. + * This check confirms that the template is being used in the correct context. + * If the template is being used directly, it will redirect to the 404 page. + */ +if (Astro.url.pathname === Astro.originPathname) { + return Astro.rewrite('/404'); +} + const databaseId = Astro.locals.templateData?.seedNode?.databaseId; const { data, error } = await Astro.locals.client.request(print(GET_POSTS)); diff --git a/examples/astro/kitchen-sink/src/pages/wp-templates/page.astro b/examples/astro/kitchen-sink/src/pages/wp-templates/page.astro index e26837114..1f59f8f05 100644 --- a/examples/astro/kitchen-sink/src/pages/wp-templates/page.astro +++ b/examples/astro/kitchen-sink/src/pages/wp-templates/page.astro @@ -3,6 +3,13 @@ import Layout from '@/components/Layout.astro'; import { GET_PAGE } from '@/queries/getPage'; import { print } from 'graphql'; +/** + * This is a template for the WordPress template hierarchy. + * It will be used to render the WordPress templates. + * It should never be used directly. + * This check confirms that the template is being used in the correct context. + * If the template is being used directly, it will redirect to the 404 page. + */ if (Astro.url.pathname === Astro.originPathname) { return Astro.rewrite('/404'); } diff --git a/packages/graphql/client.js b/packages/graphql/client.js index e438e3b5f..87552e174 100644 --- a/packages/graphql/client.js +++ b/packages/graphql/client.js @@ -59,8 +59,6 @@ export function createDefaultGraphQLClient(wordpressUrl, headers = {}) { body: JSON.stringify({ query, variables }), }); - console.log('RRR', response); - if (!response.ok) { const message = await response.json(); diff --git a/packages/nextjs/pages/templateHierarchy.js b/packages/nextjs/pages/templateHierarchy.js index 43e365e3f..e9afbc41b 100644 --- a/packages/nextjs/pages/templateHierarchy.js +++ b/packages/nextjs/pages/templateHierarchy.js @@ -70,8 +70,6 @@ export async function uriToTemplate({ const possibleTemplates = getPossibleTemplates(seedNode); - console.log(possibleTemplates); - returnData.possibleTemplates = possibleTemplates; if (!possibleTemplates || possibleTemplates.length === 0) { diff --git a/packages/template-hierarchy/seedQueryExecutor.js b/packages/template-hierarchy/seedQueryExecutor.js index 15a4a4dd5..9eec53d08 100644 --- a/packages/template-hierarchy/seedQueryExecutor.js +++ b/packages/template-hierarchy/seedQueryExecutor.js @@ -22,8 +22,6 @@ export async function getSeedQuery({ uri, id, asPreview, graphqlClient }) { asPreview, }); - console.log('Seed query result:', uri, id, asPreview, result); - return { data: result.data || result, error: result.error || null, From 87a4605260abf43ac951fd7ad4fa0654093698cd Mon Sep 17 00:00:00 2001 From: ahuseyn Date: Thu, 28 Aug 2025 14:33:13 +0200 Subject: [PATCH 14/24] add kitchen-sink example --- examples/sveltekit/kitchen-sink/.gitignore | 25 ++++ examples/sveltekit/kitchen-sink/package.json | 34 +++++ examples/sveltekit/kitchen-sink/src/app.html | 12 ++ .../src/components/BlogPostItem.svelte | 38 ++++++ .../kitchen-sink/src/components/Header.svelte | 15 +++ .../kitchen-sink/src/hooks.server.js | 14 +++ .../kitchen-sink/src/queries/getArchive.js | 68 ++++++++++ .../kitchen-sink/src/queries/getLayout.js | 10 ++ .../kitchen-sink/src/queries/getPage.js | 27 ++++ .../kitchen-sink/src/queries/getPost.js | 27 ++++ .../kitchen-sink/src/queries/getPosts.js | 33 +++++ .../src/queries/templateQueries/archive.js | 11 ++ .../src/queries/templateQueries/home.js | 8 ++ .../src/queries/templateQueries/index.js | 6 + .../src/queries/templateQueries/page.js | 12 ++ .../src/queries/templateQueries/single.js | 12 ++ .../src/routes/[...uri]/+layout.server.js | 25 ++++ .../src/routes/[...uri]/+layout.svelte | 11 ++ .../kitchen-sink/src/routes/[...uri]/+page.js | 10 ++ .../src/routes/[...uri]/+page.server.js | 47 +++++++ .../src/routes/[...uri]/+page.svelte | 5 + .../src/routes/api/templates/+server.js | 31 +++++ .../kitchen-sink/src/styles/globals.css | 1 + .../kitchen-sink/src/utils/getAuthString.js | 8 ++ .../src/wp-templates/archive.svelte | 32 +++++ .../kitchen-sink/src/wp-templates/home.svelte | 31 +++++ .../src/wp-templates/index.svelte | 39 ++++++ .../kitchen-sink/src/wp-templates/page.svelte | 31 +++++ .../src/wp-templates/single.svelte | 30 +++++ .../sveltekit/kitchen-sink/static/favicon.png | Bin 0 -> 1571 bytes .../sveltekit/kitchen-sink/svelte.config.js | 20 +++ .../sveltekit/kitchen-sink/vite.config.ts | 7 ++ pnpm-lock.yaml | 116 +++++++++++++----- 33 files changed, 767 insertions(+), 29 deletions(-) create mode 100644 examples/sveltekit/kitchen-sink/.gitignore create mode 100644 examples/sveltekit/kitchen-sink/package.json create mode 100644 examples/sveltekit/kitchen-sink/src/app.html create mode 100644 examples/sveltekit/kitchen-sink/src/components/BlogPostItem.svelte create mode 100644 examples/sveltekit/kitchen-sink/src/components/Header.svelte create mode 100644 examples/sveltekit/kitchen-sink/src/hooks.server.js create mode 100644 examples/sveltekit/kitchen-sink/src/queries/getArchive.js create mode 100644 examples/sveltekit/kitchen-sink/src/queries/getLayout.js create mode 100644 examples/sveltekit/kitchen-sink/src/queries/getPage.js create mode 100644 examples/sveltekit/kitchen-sink/src/queries/getPost.js create mode 100644 examples/sveltekit/kitchen-sink/src/queries/getPosts.js create mode 100644 examples/sveltekit/kitchen-sink/src/queries/templateQueries/archive.js create mode 100644 examples/sveltekit/kitchen-sink/src/queries/templateQueries/home.js create mode 100644 examples/sveltekit/kitchen-sink/src/queries/templateQueries/index.js create mode 100644 examples/sveltekit/kitchen-sink/src/queries/templateQueries/page.js create mode 100644 examples/sveltekit/kitchen-sink/src/queries/templateQueries/single.js create mode 100644 examples/sveltekit/kitchen-sink/src/routes/[...uri]/+layout.server.js create mode 100644 examples/sveltekit/kitchen-sink/src/routes/[...uri]/+layout.svelte create mode 100644 examples/sveltekit/kitchen-sink/src/routes/[...uri]/+page.js create mode 100644 examples/sveltekit/kitchen-sink/src/routes/[...uri]/+page.server.js create mode 100644 examples/sveltekit/kitchen-sink/src/routes/[...uri]/+page.svelte create mode 100644 examples/sveltekit/kitchen-sink/src/routes/api/templates/+server.js create mode 100644 examples/sveltekit/kitchen-sink/src/styles/globals.css create mode 100644 examples/sveltekit/kitchen-sink/src/utils/getAuthString.js create mode 100644 examples/sveltekit/kitchen-sink/src/wp-templates/archive.svelte create mode 100644 examples/sveltekit/kitchen-sink/src/wp-templates/home.svelte create mode 100644 examples/sveltekit/kitchen-sink/src/wp-templates/index.svelte create mode 100644 examples/sveltekit/kitchen-sink/src/wp-templates/page.svelte create mode 100644 examples/sveltekit/kitchen-sink/src/wp-templates/single.svelte create mode 100644 examples/sveltekit/kitchen-sink/static/favicon.png create mode 100644 examples/sveltekit/kitchen-sink/svelte.config.js create mode 100644 examples/sveltekit/kitchen-sink/vite.config.ts diff --git a/examples/sveltekit/kitchen-sink/.gitignore b/examples/sveltekit/kitchen-sink/.gitignore new file mode 100644 index 000000000..c7e35228d --- /dev/null +++ b/examples/sveltekit/kitchen-sink/.gitignore @@ -0,0 +1,25 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* + +package-lock.json \ No newline at end of file diff --git a/examples/sveltekit/kitchen-sink/package.json b/examples/sveltekit/kitchen-sink/package.json new file mode 100644 index 000000000..dba80e1ac --- /dev/null +++ b/examples/sveltekit/kitchen-sink/package.json @@ -0,0 +1,34 @@ +{ + "name": "@faustjs/sveltekit-template-hierarchy-example", + "private": true, + "version": "0.1.0", + "license": "0BSD", + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "preview": "vite preview", + "prepare": "svelte-kit sync || echo ''", + "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", + "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch" + }, + "devDependencies": { + "@faustjs/sveltekit": "workspace:*", + "@faustjs/template-hierarchy": "workspace:*", + "@faustjs/data-fetching": "workspace:*", + "@sveltejs/adapter-auto": "^6.0.0", + "@sveltejs/kit": "^2.16.0", + "@sveltejs/vite-plugin-svelte": "^5.0.0", + "svelte": "^5.0.0", + "svelte-check": "^4.0.0", + "vite": "^6.2.6" + }, + "dependencies": { + "@tailwindcss/vite": "^4.1.12", + "@urql/core": "^5.1.1", + "@urql/exchange-persisted": "^4.3.1", + "deepmerge": "^4.3.1", + "graphql": "^16.11.0", + "tailwindcss": "^4.1.12" + } +} diff --git a/examples/sveltekit/kitchen-sink/src/app.html b/examples/sveltekit/kitchen-sink/src/app.html new file mode 100644 index 000000000..77a5ff52c --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/app.html @@ -0,0 +1,12 @@ + + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/examples/sveltekit/kitchen-sink/src/components/BlogPostItem.svelte b/examples/sveltekit/kitchen-sink/src/components/BlogPostItem.svelte new file mode 100644 index 000000000..5557310d5 --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/components/BlogPostItem.svelte @@ -0,0 +1,38 @@ + + +
+ + + {#if featuredImage} + + {/if} + +

+ + {title} + +

+ +
+ {@html excerpt} +
+ + Read more +
diff --git a/examples/sveltekit/kitchen-sink/src/components/Header.svelte b/examples/sveltekit/kitchen-sink/src/components/Header.svelte new file mode 100644 index 000000000..96c2878e4 --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/components/Header.svelte @@ -0,0 +1,15 @@ + + +
+ +
diff --git a/examples/sveltekit/kitchen-sink/src/hooks.server.js b/examples/sveltekit/kitchen-sink/src/hooks.server.js new file mode 100644 index 000000000..fd6a32612 --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/hooks.server.js @@ -0,0 +1,14 @@ +import { dev } from '$app/environment'; + +export const handle = async ({ event, resolve }) => { + if ( + dev && + event.url.pathname === '/.well-known/appspecific/com.chrome.devtools.json' + ) { + return new Response(undefined, { status: 404 }); + } + + return resolve(event, { + filterSerializedResponseHeaders: () => true, // basically get all headers + }); +}; diff --git a/examples/sveltekit/kitchen-sink/src/queries/getArchive.js b/examples/sveltekit/kitchen-sink/src/queries/getArchive.js new file mode 100644 index 000000000..fc59b7f14 --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/queries/getArchive.js @@ -0,0 +1,68 @@ +import gql from 'graphql-tag'; + +export const GET_ARCHIVE = gql` + query GetArchivePage($uri: String!) { + nodeByUri(uri: $uri) { + ... on Category { + name + posts { + edges { + node { + id + title + content + date + uri + featuredImage { + node { + id + sourceUrl + altText + mediaDetails { + width + height + } + } + } + author { + node { + name + } + } + } + } + } + } + ... on Tag { + name + posts { + edges { + node { + id + title + content + date + uri + featuredImage { + node { + id + sourceUrl + altText + mediaDetails { + width + height + } + } + } + author { + node { + name + } + } + } + } + } + } + } + } +`; diff --git a/examples/sveltekit/kitchen-sink/src/queries/getLayout.js b/examples/sveltekit/kitchen-sink/src/queries/getLayout.js new file mode 100644 index 000000000..f278ee1af --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/queries/getLayout.js @@ -0,0 +1,10 @@ +import { gql } from 'graphql-tag'; + +export const GET_LAYOUT = gql` + query GetLayout { + generalSettings { + title + description + } + } +`; diff --git a/examples/sveltekit/kitchen-sink/src/queries/getPage.js b/examples/sveltekit/kitchen-sink/src/queries/getPage.js new file mode 100644 index 000000000..76646f1a5 --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/queries/getPage.js @@ -0,0 +1,27 @@ +import gql from 'graphql-tag'; + +export const GET_PAGE = gql` + query GetPage($databaseId: ID!, $asPreview: Boolean = false) { + page(id: $databaseId, idType: DATABASE_ID, asPreview: $asPreview) { + title + content + date + author { + node { + name + } + } + featuredImage { + node { + id + sourceUrl + altText + mediaDetails { + width + height + } + } + } + } + } +`; diff --git a/examples/sveltekit/kitchen-sink/src/queries/getPost.js b/examples/sveltekit/kitchen-sink/src/queries/getPost.js new file mode 100644 index 000000000..30ba7b8d7 --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/queries/getPost.js @@ -0,0 +1,27 @@ +import gql from 'graphql-tag'; + +export const GET_POST = gql` + query GetPost($databaseId: ID!, $asPreview: Boolean = false) { + post(id: $databaseId, idType: DATABASE_ID, asPreview: $asPreview) { + title + content + date + author { + node { + name + } + } + featuredImage { + node { + id + sourceUrl + altText + mediaDetails { + width + height + } + } + } + } + } +`; diff --git a/examples/sveltekit/kitchen-sink/src/queries/getPosts.js b/examples/sveltekit/kitchen-sink/src/queries/getPosts.js new file mode 100644 index 000000000..9cfcdd4e1 --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/queries/getPosts.js @@ -0,0 +1,33 @@ +import gql from 'graphql-tag'; + +export const GET_POSTS = gql` + query GetPosts { + posts { + edges { + node { + id + title + content + date + uri + featuredImage { + node { + id + sourceUrl + altText + mediaDetails { + width + height + } + } + } + author { + node { + name + } + } + } + } + } + } +`; diff --git a/examples/sveltekit/kitchen-sink/src/queries/templateQueries/archive.js b/examples/sveltekit/kitchen-sink/src/queries/templateQueries/archive.js new file mode 100644 index 000000000..a1829ce0f --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/queries/templateQueries/archive.js @@ -0,0 +1,11 @@ +import { GET_ARCHIVE } from '../getArchive.js'; + +export const queries = [ + { + name: 'getCategory', + query: GET_ARCHIVE, + variables: ({ uri }) => ({ + uri, + }), + }, +]; diff --git a/examples/sveltekit/kitchen-sink/src/queries/templateQueries/home.js b/examples/sveltekit/kitchen-sink/src/queries/templateQueries/home.js new file mode 100644 index 000000000..c8bec2b39 --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/queries/templateQueries/home.js @@ -0,0 +1,8 @@ +import { GET_POSTS } from '../getPosts.js'; + +export const queries = [ + { + name: 'getPosts', + query: GET_POSTS, + }, +]; diff --git a/examples/sveltekit/kitchen-sink/src/queries/templateQueries/index.js b/examples/sveltekit/kitchen-sink/src/queries/templateQueries/index.js new file mode 100644 index 000000000..270d3efa9 --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/queries/templateQueries/index.js @@ -0,0 +1,6 @@ +import { queries as single } from './single.js'; +import { queries as page } from './page.js'; +import { queries as archive } from './archive.js'; +import { queries as home } from './home.js'; + +export default { single, page, archive, home }; diff --git a/examples/sveltekit/kitchen-sink/src/queries/templateQueries/page.js b/examples/sveltekit/kitchen-sink/src/queries/templateQueries/page.js new file mode 100644 index 000000000..80fac5bc6 --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/queries/templateQueries/page.js @@ -0,0 +1,12 @@ +import { GET_PAGE } from '../getPage.js'; + +export const queries = [ + { + name: 'getPage', + query: GET_PAGE, + variables: ({ databaseId }, ctx) => ({ + databaseId, + asPreview: ctx?.asPreview, + }), + }, +]; diff --git a/examples/sveltekit/kitchen-sink/src/queries/templateQueries/single.js b/examples/sveltekit/kitchen-sink/src/queries/templateQueries/single.js new file mode 100644 index 000000000..952390ad5 --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/queries/templateQueries/single.js @@ -0,0 +1,12 @@ +import { GET_POST } from '../getPost.js'; + +export const queries = [ + { + name: 'getPost', + query: GET_POST, + variables: ({ databaseId }, ctx) => ({ + databaseId, + asPreview: ctx?.asPreview, + }), + }, +]; diff --git a/examples/sveltekit/kitchen-sink/src/routes/[...uri]/+layout.server.js b/examples/sveltekit/kitchen-sink/src/routes/[...uri]/+layout.server.js new file mode 100644 index 000000000..2858bb8ba --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/routes/[...uri]/+layout.server.js @@ -0,0 +1,25 @@ +import { print } from 'graphql'; +import { createDefaultClient, setGraphQLClient } from '@faustjs/sveltekit'; +import { GET_LAYOUT } from '../../queries/getLayout.js'; +import { WORDPRESS_URL } from '$env/static/private'; + +export const load = async () => { + const client = createDefaultClient(WORDPRESS_URL); + setGraphQLClient(client); + + if (client) { + try { + const { data } = await client.request(print(GET_LAYOUT)); + + return { + layoutData: data, + }; + } catch (error) { + console.error('Error fetching layout data:', error); + } + } + + return { + layoutData: null, + }; +}; diff --git a/examples/sveltekit/kitchen-sink/src/routes/[...uri]/+layout.svelte b/examples/sveltekit/kitchen-sink/src/routes/[...uri]/+layout.svelte new file mode 100644 index 000000000..966cab52e --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/routes/[...uri]/+layout.svelte @@ -0,0 +1,11 @@ + + +
+
+ {@render children()} +
\ No newline at end of file diff --git a/examples/sveltekit/kitchen-sink/src/routes/[...uri]/+page.js b/examples/sveltekit/kitchen-sink/src/routes/[...uri]/+page.js new file mode 100644 index 000000000..ab81a48d0 --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/routes/[...uri]/+page.js @@ -0,0 +1,10 @@ +export const load = async (event) => { + const { data } = event; + + const template = await import(`$wp/${data.templateData.template.id}.svelte`); + + return { + ...data, + template: template.default, + }; +}; diff --git a/examples/sveltekit/kitchen-sink/src/routes/[...uri]/+page.server.js b/examples/sveltekit/kitchen-sink/src/routes/[...uri]/+page.server.js new file mode 100644 index 000000000..2e5bc98d9 --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/routes/[...uri]/+page.server.js @@ -0,0 +1,47 @@ +import { + createDefaultClient, + setGraphQLClient, + uriToTemplate, +} from '@faustjs/sveltekit'; +import { WORDPRESS_URL } from '$env/static/private'; +import availableQueries from '../../queries/templateQueries/index.js'; +import { fetchTemplateQueries } from '@faustjs/data-fetching'; + +export const load = async (event) => { + const { + params: { uri }, + fetch, + } = event; + + const workingUri = uri || '/'; + + const client = createDefaultClient(WORDPRESS_URL); + setGraphQLClient(client); + + const templateData = await uriToTemplate({ + fetch, + uri: workingUri, + graphqlClient: client, + }); + + // Fetch template-specific queries using the same mechanism as Next.js + let queriesData = null; + try { + queriesData = await fetchTemplateQueries({ + availableQueries, + templateData, + client, + locale: templateData?.seedNode?.locale, + }); + } catch (error) { + console.error('Error fetching template queries:', error); + // Don't throw error, just continue with null queriesData + queriesData = null; + } + + return { + uri: workingUri, + templateData, + queriesData, + }; +}; diff --git a/examples/sveltekit/kitchen-sink/src/routes/[...uri]/+page.svelte b/examples/sveltekit/kitchen-sink/src/routes/[...uri]/+page.svelte new file mode 100644 index 000000000..ebd777c90 --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/routes/[...uri]/+page.svelte @@ -0,0 +1,5 @@ + + + diff --git a/examples/sveltekit/kitchen-sink/src/routes/api/templates/+server.js b/examples/sveltekit/kitchen-sink/src/routes/api/templates/+server.js new file mode 100644 index 000000000..0bab26bae --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/routes/api/templates/+server.js @@ -0,0 +1,31 @@ +import { readdir } from 'node:fs/promises'; +import { join } from 'node:path'; +import { json } from '@sveltejs/kit'; +const TEMPLATE_PATH = 'wp-templates'; + +export const GET = async ({ url }) => { + const uri = url.searchParams.get('uri'); + + if (!uri) { + return new Response('Missing URI', { status: 400 }); + } + + const files = await readdir(join('src', TEMPLATE_PATH)); + + const templates = []; + + for (const file of files) { + if (file.startsWith('+')) { + continue; + } + + const slug = file.replace('.svelte', ''); + + templates.push({ + id: slug, + path: join('/', TEMPLATE_PATH, slug), + }); + } + + return json(templates); +}; diff --git a/examples/sveltekit/kitchen-sink/src/styles/globals.css b/examples/sveltekit/kitchen-sink/src/styles/globals.css new file mode 100644 index 000000000..d4b507858 --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/styles/globals.css @@ -0,0 +1 @@ +@import 'tailwindcss'; diff --git a/examples/sveltekit/kitchen-sink/src/utils/getAuthString.js b/examples/sveltekit/kitchen-sink/src/utils/getAuthString.js new file mode 100644 index 000000000..666bc6721 --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/utils/getAuthString.js @@ -0,0 +1,8 @@ +// Forming the authentication string for WordPress App Password +// More info: https://make.wordpress.org/core/2020/11/05/application-passwords-integration-guide/ + +export const getAuthString = () => + 'Basic ' + + Buffer.from( + import.meta.env.WP_USERNAME + ':' + import.meta.env.WP_APP_PASSWORD, + ).toString('base64'); diff --git a/examples/sveltekit/kitchen-sink/src/wp-templates/archive.svelte b/examples/sveltekit/kitchen-sink/src/wp-templates/archive.svelte new file mode 100644 index 000000000..04ab21085 --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/wp-templates/archive.svelte @@ -0,0 +1,32 @@ + + +
+

{name || 'Archive'}

+ + {#if posts?.edges?.length > 0} + {#each posts.edges as item (item.node.id)} + + {/each} + {:else if posts?.edges?.length === 0} +
+

No Posts Found

+

There are no posts to display at this time.

+
+ {:else} +
+

Loading Posts...

+

+ Fetching the latest posts from WordPress... +

+
+ {/if} + +
\ No newline at end of file diff --git a/examples/sveltekit/kitchen-sink/src/wp-templates/home.svelte b/examples/sveltekit/kitchen-sink/src/wp-templates/home.svelte new file mode 100644 index 000000000..37eab4804 --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/wp-templates/home.svelte @@ -0,0 +1,31 @@ + + + + Home + + +{#if posts?.edges?.length > 0} + {#each posts.edges as item (item.node.id)} + + {/each} +{:else if posts?.edges?.length === 0} +
+

No Posts Found

+

There are no posts to display at this time.

+
+{:else} +
+

Loading Posts...

+

+ Fetching the latest posts from WordPress... +

+
+{/if} + diff --git a/examples/sveltekit/kitchen-sink/src/wp-templates/index.svelte b/examples/sveltekit/kitchen-sink/src/wp-templates/index.svelte new file mode 100644 index 000000000..a6e144824 --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/wp-templates/index.svelte @@ -0,0 +1,39 @@ + + + + Template Not Found + + +
+
+

+ Template Not Found +

+

+ No specific template was found for the {contentType} type. + This content is being displayed using the default fallback template. +

+
+ + + +
+

+ Debug Information +

+
+
{JSON.stringify(templateData, null, 2)}
+
+
+
diff --git a/examples/sveltekit/kitchen-sink/src/wp-templates/page.svelte b/examples/sveltekit/kitchen-sink/src/wp-templates/page.svelte new file mode 100644 index 000000000..edea3ea30 --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/wp-templates/page.svelte @@ -0,0 +1,31 @@ + + + + {title || 'Page'} + + +
+
+

+ {title || 'Page Title'} +

+
+ + {#if featuredImage} + + {/if} + +
{@html content || 'Page content would appear here...'} +
+
diff --git a/examples/sveltekit/kitchen-sink/src/wp-templates/single.svelte b/examples/sveltekit/kitchen-sink/src/wp-templates/single.svelte new file mode 100644 index 000000000..27ae3d19a --- /dev/null +++ b/examples/sveltekit/kitchen-sink/src/wp-templates/single.svelte @@ -0,0 +1,30 @@ + + + + {title || 'Single Post'} + + +
+
+

+ {title || 'Post Title'} +

+
+ + {#if featuredImage} + + {/if} + +
{@html content || 'Post content would appear here...'} +
+
diff --git a/examples/sveltekit/kitchen-sink/static/favicon.png b/examples/sveltekit/kitchen-sink/static/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..825b9e65af7c104cfb07089bb28659393b4f2097 GIT binary patch literal 1571 zcmV+;2Hg3HP)Px)-AP12RCwC$UE6KzI1p6{F2N z1VK2vi|pOpn{~#djwYcWXTI_im_u^TJgMZ4JMOsSj!0ma>B?-(Hr@X&W@|R-$}W@Z zgj#$x=!~7LGqHW?IO8+*oE1MyDp!G=L0#^lUx?;!fXv@l^6SvTnf^ac{5OurzC#ZMYc20lI%HhX816AYVs1T3heS1*WaWH z%;x>)-J}YB5#CLzU@GBR6sXYrD>Vw(Fmt#|JP;+}<#6b63Ike{Fuo!?M{yEffez;| zp!PfsuaC)>h>-AdbnwN13g*1LowNjT5?+lFVd#9$!8Z9HA|$*6dQ8EHLu}U|obW6f z2%uGv?vr=KNq7YYa2Roj;|zooo<)lf=&2yxM@e`kM$CmCR#x>gI>I|*Ubr({5Y^rb zghxQU22N}F51}^yfDSt786oMTc!W&V;d?76)9KXX1 z+6Okem(d}YXmmOiZq$!IPk5t8nnS{%?+vDFz3BevmFNgpIod~R{>@#@5x9zJKEHLHv!gHeK~n)Ld!M8DB|Kfe%~123&Hz1Z(86nU7*G5chmyDe ziV7$pB7pJ=96hpxHv9rCR29%bLOXlKU<_13_M8x)6;P8E1Kz6G<&P?$P^%c!M5`2` zfY2zg;VK5~^>TJGQzc+33-n~gKt{{of8GzUkWmU110IgI0DLxRIM>0US|TsM=L|@F z0Bun8U!cRB7-2apz=y-7*UxOxz@Z0)@QM)9wSGki1AZ38ceG7Q72z5`i;i=J`ILzL z@iUO?SBBG-0cQuo+an4TsLy-g-x;8P4UVwk|D8{W@U1Zi z!M)+jqy@nQ$p?5tsHp-6J304Q={v-B>66$P0IDx&YT(`IcZ~bZfmn11#rXd7<5s}y zBi9eim&zQc0Dk|2>$bs0PnLmDfMP5lcXRY&cvJ=zKxI^f0%-d$tD!`LBf9^jMSYUA zI8U?CWdY@}cRq6{5~y+)#h1!*-HcGW@+gZ4B};0OnC~`xQOyH19z*TA!!BJ%9s0V3F?CAJ{hTd#*tf+ur-W9MOURF-@B77_-OshsY}6 zOXRY=5%C^*26z?l)1=$bz30!so5tfABdSYzO+H=CpV~aaUefmjvfZ3Ttu9W&W3Iu6 zROlh0MFA5h;my}8lB0tAV-Rvc2Zs_CCSJnx@d`**$idgy-iMob4dJWWw|21b4NB=LfsYp0Aeh{Ov)yztQi;eL4y5 zMi>8^SzKqk8~k?UiQK^^-5d8c%bV?$F8%X~czyiaKCI2=UH=18'} + yaml@2.8.1: + resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} + engines: {node: '>= 14.6'} + hasBin: true + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -5083,15 +5137,15 @@ snapshots: dependencies: acorn: 8.14.1 - '@sveltejs/adapter-auto@6.1.0(@sveltejs/kit@2.28.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)))(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)))': + '@sveltejs/adapter-auto@6.1.0(@sveltejs/kit@2.28.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1)))(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1)))': dependencies: - '@sveltejs/kit': 2.28.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)))(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)) + '@sveltejs/kit': 2.28.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1)))(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1)) - '@sveltejs/kit@2.28.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)))(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1))': + '@sveltejs/kit@2.28.0(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1)))(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1))': dependencies: '@standard-schema/spec': 1.0.0 '@sveltejs/acorn-typescript': 1.0.5(acorn@8.14.1) - '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)) + '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1)) '@types/cookie': 0.6.0 acorn: 8.14.1 cookie: 0.6.0 @@ -5104,27 +5158,27 @@ snapshots: set-cookie-parser: 2.7.1 sirv: 3.0.1 svelte: 5.38.1 - vite: 6.3.5(jiti@2.5.1)(lightningcss@1.30.1) + vite: 6.3.5(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1) - '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)))(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1))': + '@sveltejs/vite-plugin-svelte-inspector@4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1)))(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1))': dependencies: - '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)) + '@sveltejs/vite-plugin-svelte': 5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1)) debug: 4.4.1 svelte: 5.38.1 - vite: 6.3.5(jiti@2.5.1)(lightningcss@1.30.1) + vite: 6.3.5(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1))': + '@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)))(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)) + '@sveltejs/vite-plugin-svelte-inspector': 4.0.1(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1)))(svelte@5.38.1)(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1)) debug: 4.4.1 deepmerge: 4.3.1 kleur: 4.1.5 magic-string: 0.30.17 svelte: 5.38.1 - vite: 6.3.5(jiti@2.5.1)(lightningcss@1.30.1) - vitefu: 1.1.1(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)) + vite: 6.3.5(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1) + vitefu: 1.1.1(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1)) transitivePeerDependencies: - supports-color @@ -5279,12 +5333,12 @@ snapshots: postcss: 8.5.6 tailwindcss: 4.1.11 - '@tailwindcss/vite@4.1.12(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1))': + '@tailwindcss/vite@4.1.12(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1))': dependencies: '@tailwindcss/node': 4.1.12 '@tailwindcss/oxide': 4.1.12 tailwindcss: 4.1.12 - vite: 6.3.5(jiti@2.5.1)(lightningcss@1.30.1) + vite: 6.3.5(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1) '@tybys/wasm-util@0.10.0': dependencies: @@ -5665,7 +5719,7 @@ snapshots: ast-types-flow@0.0.8: {} - astro@5.12.8(jiti@2.5.1)(lightningcss@1.30.1)(rollup@4.46.2)(typescript@5.8.3): + astro@5.12.8(jiti@2.5.1)(lightningcss@1.30.1)(rollup@4.46.2)(typescript@5.8.3)(yaml@2.8.1): dependencies: '@astrojs/compiler': 2.12.2 '@astrojs/internal-helpers': 0.7.1 @@ -5721,8 +5775,8 @@ snapshots: unist-util-visit: 5.0.0 unstorage: 1.16.1 vfile: 6.0.3 - vite: 6.3.5(jiti@2.5.1)(lightningcss@1.30.1) - vitefu: 1.1.1(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)) + vite: 6.3.5(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1) + vitefu: 1.1.1(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1)) xxhash-wasm: 1.1.0 yargs-parser: 21.1.1 yocto-spinner: 0.2.3 @@ -8713,7 +8767,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1): + vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1): dependencies: esbuild: 0.25.8 fdir: 6.4.6(picomatch@4.0.3) @@ -8725,10 +8779,11 @@ snapshots: fsevents: 2.3.3 jiti: 2.5.1 lightningcss: 1.30.1 + yaml: 2.8.1 - vitefu@1.1.1(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)): + vitefu@1.1.1(vite@6.3.5(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1)): optionalDependencies: - vite: 6.3.5(jiti@2.5.1)(lightningcss@1.30.1) + vite: 6.3.5(jiti@2.5.1)(lightningcss@1.30.1)(yaml@2.8.1) web-namespaces@2.0.1: {} @@ -8823,6 +8878,9 @@ snapshots: yallist@5.0.0: {} + yaml@2.8.1: + optional: true + yargs-parser@21.1.1: {} yocto-queue@0.1.0: {} From 83e2f2589d1f931e0b240f4ca49621f917d2e360 Mon Sep 17 00:00:00 2001 From: ahuseyn Date: Thu, 28 Aug 2025 14:33:26 +0200 Subject: [PATCH 15/24] add seedNode to templateHierarchy --- packages/sveltekit/templateHierarchy.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/sveltekit/templateHierarchy.js b/packages/sveltekit/templateHierarchy.js index e9497fbe9..f32daa108 100644 --- a/packages/sveltekit/templateHierarchy.js +++ b/packages/sveltekit/templateHierarchy.js @@ -23,6 +23,7 @@ export async function uriToTemplate({ fetch, uri, graphqlClient }) { availableTemplates: undefined, possibleTemplates: undefined, template: undefined, + seedNode: undefined, }; // Get the GraphQL client - use provided one or get configured one @@ -34,6 +35,10 @@ export async function uriToTemplate({ fetch, uri, graphqlClient }) { returnData.seedQuery = { data, error: errorMessage }; + const seedNode = data?.nodeByUri || data?.contentNode; + + returnData.seedNode = seedNode ?? error; + if (errorMessage) { console.error('Error fetching seedQuery:', errorMessage); throw error(500, 'Error fetching seedQuery'); From 6e6365e3c5afdb36318c5eccd38c73c8010ee754 Mon Sep 17 00:00:00 2001 From: ahuseyn Date: Thu, 28 Aug 2025 16:00:53 +0200 Subject: [PATCH 16/24] add preview --- .../+layout.server.js | 0 .../+layout.svelte | 0 .../{[...uri] => [...identifier]}/+page.js | 0 .../+page.server.js | 26 +++++++++++++++---- .../+page.svelte | 0 .../kitchen-sink/src/utils/getAuthString.js | 7 ++--- 6 files changed, 25 insertions(+), 8 deletions(-) rename examples/sveltekit/kitchen-sink/src/routes/{[...uri] => [...identifier]}/+layout.server.js (100%) rename examples/sveltekit/kitchen-sink/src/routes/{[...uri] => [...identifier]}/+layout.svelte (100%) rename examples/sveltekit/kitchen-sink/src/routes/{[...uri] => [...identifier]}/+page.js (100%) rename examples/sveltekit/kitchen-sink/src/routes/{[...uri] => [...identifier]}/+page.server.js (58%) rename examples/sveltekit/kitchen-sink/src/routes/{[...uri] => [...identifier]}/+page.svelte (100%) diff --git a/examples/sveltekit/kitchen-sink/src/routes/[...uri]/+layout.server.js b/examples/sveltekit/kitchen-sink/src/routes/[...identifier]/+layout.server.js similarity index 100% rename from examples/sveltekit/kitchen-sink/src/routes/[...uri]/+layout.server.js rename to examples/sveltekit/kitchen-sink/src/routes/[...identifier]/+layout.server.js diff --git a/examples/sveltekit/kitchen-sink/src/routes/[...uri]/+layout.svelte b/examples/sveltekit/kitchen-sink/src/routes/[...identifier]/+layout.svelte similarity index 100% rename from examples/sveltekit/kitchen-sink/src/routes/[...uri]/+layout.svelte rename to examples/sveltekit/kitchen-sink/src/routes/[...identifier]/+layout.svelte diff --git a/examples/sveltekit/kitchen-sink/src/routes/[...uri]/+page.js b/examples/sveltekit/kitchen-sink/src/routes/[...identifier]/+page.js similarity index 100% rename from examples/sveltekit/kitchen-sink/src/routes/[...uri]/+page.js rename to examples/sveltekit/kitchen-sink/src/routes/[...identifier]/+page.js diff --git a/examples/sveltekit/kitchen-sink/src/routes/[...uri]/+page.server.js b/examples/sveltekit/kitchen-sink/src/routes/[...identifier]/+page.server.js similarity index 58% rename from examples/sveltekit/kitchen-sink/src/routes/[...uri]/+page.server.js rename to examples/sveltekit/kitchen-sink/src/routes/[...identifier]/+page.server.js index 2e5bc98d9..161c788b0 100644 --- a/examples/sveltekit/kitchen-sink/src/routes/[...uri]/+page.server.js +++ b/examples/sveltekit/kitchen-sink/src/routes/[...identifier]/+page.server.js @@ -4,24 +4,41 @@ import { uriToTemplate, } from '@faustjs/sveltekit'; import { WORDPRESS_URL } from '$env/static/private'; +import { WP_PREVIEW_SECRET } from '$env/static/private'; import availableQueries from '../../queries/templateQueries/index.js'; import { fetchTemplateQueries } from '@faustjs/data-fetching'; +import { getAuthString } from '../../utils/getAuthString.js'; export const load = async (event) => { const { - params: { uri }, + params: { identifier }, + url, fetch, } = event; - const workingUri = uri || '/'; + const searchParams = url?.searchParams; - const client = createDefaultClient(WORDPRESS_URL); + // Determine if we are in preview mode based on the URL parameter and the secret + const isPreview = + searchParams.get('preview') === 'true' && + WP_PREVIEW_SECRET === searchParams.get('secret'); + + const headers = isPreview ? { Authorization: getAuthString() } : undefined; + + const client = createDefaultClient(WORDPRESS_URL, headers); setGraphQLClient(client); + const variables = isPreview + ? { + id: identifier, + asPreview: true, + } + : { uri: identifier || '/' }; + const templateData = await uriToTemplate({ fetch, - uri: workingUri, graphqlClient: client, + ...variables, }); // Fetch template-specific queries using the same mechanism as Next.js @@ -40,7 +57,6 @@ export const load = async (event) => { } return { - uri: workingUri, templateData, queriesData, }; diff --git a/examples/sveltekit/kitchen-sink/src/routes/[...uri]/+page.svelte b/examples/sveltekit/kitchen-sink/src/routes/[...identifier]/+page.svelte similarity index 100% rename from examples/sveltekit/kitchen-sink/src/routes/[...uri]/+page.svelte rename to examples/sveltekit/kitchen-sink/src/routes/[...identifier]/+page.svelte diff --git a/examples/sveltekit/kitchen-sink/src/utils/getAuthString.js b/examples/sveltekit/kitchen-sink/src/utils/getAuthString.js index 666bc6721..81c9e2214 100644 --- a/examples/sveltekit/kitchen-sink/src/utils/getAuthString.js +++ b/examples/sveltekit/kitchen-sink/src/utils/getAuthString.js @@ -1,8 +1,9 @@ +import { WP_APP_PASSWORD } from '$env/static/private'; +import { WP_USERNAME } from '$env/static/private'; + // Forming the authentication string for WordPress App Password // More info: https://make.wordpress.org/core/2020/11/05/application-passwords-integration-guide/ export const getAuthString = () => 'Basic ' + - Buffer.from( - import.meta.env.WP_USERNAME + ':' + import.meta.env.WP_APP_PASSWORD, - ).toString('base64'); + Buffer.from(WP_USERNAME + ':' + WP_APP_PASSWORD).toString('base64'); From d25ab18d21c813f1680b72f27cfc2ad8be0407ba Mon Sep 17 00:00:00 2001 From: ahuseyn Date: Thu, 28 Aug 2025 16:01:19 +0200 Subject: [PATCH 17/24] add preview to sveltekit templateHierarchy --- packages/sveltekit/templateHierarchy.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/sveltekit/templateHierarchy.js b/packages/sveltekit/templateHierarchy.js index f32daa108..a82cae0e3 100644 --- a/packages/sveltekit/templateHierarchy.js +++ b/packages/sveltekit/templateHierarchy.js @@ -15,7 +15,13 @@ import { error } from '@sveltejs/kit'; * @param {import('../template-hierarchy/types.js').UriToTemplateOptions} options - The options object * @returns {Promise} The resolved template data */ -export async function uriToTemplate({ fetch, uri, graphqlClient }) { +export async function uriToTemplate({ + fetch, + uri, + graphqlClient, + id, + asPreview, +}) { /** @type {import('../template-hierarchy/types.js').TemplateData} */ const returnData = { uri, @@ -31,6 +37,8 @@ export async function uriToTemplate({ fetch, uri, graphqlClient }) { const { data, error: errorMessage } = await getSeedQuery({ uri, graphqlClient: client, + id, + asPreview, }); returnData.seedQuery = { data, error: errorMessage }; @@ -44,7 +52,7 @@ export async function uriToTemplate({ fetch, uri, graphqlClient }) { throw error(500, 'Error fetching seedQuery'); } - if (!data?.nodeByUri) { + if (!seedNode) { console.error('HTTP/404 - Not Found in WordPress:', uri); throw error(404, 'Not Found'); } @@ -65,7 +73,7 @@ export async function uriToTemplate({ fetch, uri, graphqlClient }) { throw error(500, 'No available templates'); } - const possibleTemplates = getPossibleTemplates(data.nodeByUri); + const possibleTemplates = getPossibleTemplates(seedNode); returnData.possibleTemplates = possibleTemplates; From a93275c27d3a6d9973cdbbe140032c296524fcff Mon Sep 17 00:00:00 2001 From: ahuseyn Date: Fri, 12 Sep 2025 11:42:18 +0200 Subject: [PATCH 18/24] add auth package and example usage --- examples/nextjs/kitchen-sink/package.json | 1 + .../src/components/BlogPostItem.js | 58 ++- .../kitchen-sink/src/components/Header.js | 101 +++- .../kitchen-sink/src/components/Login.js | 61 +++ .../src/constants/sessionConfig.js | 7 + .../src/pages/api/session/[action].js | 19 + packages/auth/README.md | 0 packages/auth/config/defaults.js | 3 + packages/auth/handlers/AuthHandler.js | 485 ++++++++++++++++++ packages/auth/index.js | 1 + packages/auth/package.json | 24 + packages/graphql/client.js | 3 +- packages/nextjs/package.json | 3 +- packages/nextjs/pages/auth/hooks/index.js | 3 + packages/nextjs/pages/auth/hooks/useLogin.js | 55 ++ packages/nextjs/pages/auth/hooks/useLogout.js | 49 ++ packages/nextjs/pages/auth/hooks/useUser.js | 76 +++ packages/nextjs/pages/auth/index.js | 2 + packages/nextjs/pages/index.js | 4 +- pnpm-lock.yaml | 375 +++++++++++++- 20 files changed, 1290 insertions(+), 40 deletions(-) create mode 100644 examples/nextjs/kitchen-sink/src/components/Login.js create mode 100644 examples/nextjs/kitchen-sink/src/constants/sessionConfig.js create mode 100644 examples/nextjs/kitchen-sink/src/pages/api/session/[action].js create mode 100644 packages/auth/README.md create mode 100644 packages/auth/config/defaults.js create mode 100644 packages/auth/handlers/AuthHandler.js create mode 100644 packages/auth/index.js create mode 100644 packages/auth/package.json create mode 100644 packages/nextjs/pages/auth/hooks/index.js create mode 100644 packages/nextjs/pages/auth/hooks/useLogin.js create mode 100644 packages/nextjs/pages/auth/hooks/useLogout.js create mode 100644 packages/nextjs/pages/auth/hooks/useUser.js create mode 100644 packages/nextjs/pages/auth/index.js diff --git a/examples/nextjs/kitchen-sink/package.json b/examples/nextjs/kitchen-sink/package.json index 3faedb491..54fef3006 100644 --- a/examples/nextjs/kitchen-sink/package.json +++ b/examples/nextjs/kitchen-sink/package.json @@ -12,6 +12,7 @@ "@faustjs/nextjs": "workspace:*", "@faustjs/template-hierarchy": "workspace:*", "@faustjs/data-fetching": "workspace:*", + "@faustjs/auth": "workspace:*", "graphql": "^16.11.0", "graphql-tag": "^2.12.6", "next": "15.2.4", diff --git a/examples/nextjs/kitchen-sink/src/components/BlogPostItem.js b/examples/nextjs/kitchen-sink/src/components/BlogPostItem.js index 58740f7f0..ce23c70eb 100644 --- a/examples/nextjs/kitchen-sink/src/components/BlogPostItem.js +++ b/examples/nextjs/kitchen-sink/src/components/BlogPostItem.js @@ -1,32 +1,42 @@ -import Link from "next/link"; +import Link from 'next/link'; export function BlogPostItem({ post }) { - const { title, date, excerpt, uri, featuredImage } = post ?? {}; + const { title, date, excerpt, uri, featuredImage } = post ?? {}; - return ( -
- + return ( +
+ - {featuredImage && ( - - )} + {featuredImage && ( + + )} -

- - {title} - -

+

+ + {title} + +

-
+
- - Read more - -
- ); + + Read more + +
+ ); } diff --git a/examples/nextjs/kitchen-sink/src/components/Header.js b/examples/nextjs/kitchen-sink/src/components/Header.js index 2d6ad7137..a97cb5c58 100644 --- a/examples/nextjs/kitchen-sink/src/components/Header.js +++ b/examples/nextjs/kitchen-sink/src/components/Header.js @@ -1,20 +1,97 @@ -/* eslint-disable @next/next/no-html-link-for-pages */ import Link from 'next/link'; +import { useState } from 'react'; +import Login from './Login'; +import { useLogout, useUser } from '@faustjs/nextjs/pages'; export default function Header() { + const { user, refetch, isAuthenticated } = useUser(); + const { logout } = useLogout(); + const [isLoginModalOpen, setIsLoginModalOpen] = useState(false); + + const openLoginModal = () => setIsLoginModalOpen(true); + const closeLoginModal = () => setIsLoginModalOpen(false); + + const onLoggedIn = () => { + closeLoginModal(); + refetch(); + }; + + const logoutUser = () => { + logout({ + onSuccess: () => { + refetch(); + }, + }); + }; + return ( -
-
-
- Headless + <> +
+
+
+ Headless +
+ +
+
- -
-
+ {isLoginModalOpen && ( +
+
+ +

Login

+ + +
+
+ )} + ); } diff --git a/examples/nextjs/kitchen-sink/src/components/Login.js b/examples/nextjs/kitchen-sink/src/components/Login.js new file mode 100644 index 000000000..acbc68d23 --- /dev/null +++ b/examples/nextjs/kitchen-sink/src/components/Login.js @@ -0,0 +1,61 @@ +import { useLogin } from '@faustjs/nextjs/pages'; +import { useState } from 'react'; + +export default function Login({ closeModal = () => {} }) { + const [usernameEmail, setUsernameEmail] = useState(''); + const [password, setPassword] = useState(''); + + const { login, isLoading, error } = useLogin(); + + const submitForm = (e) => { + e.preventDefault(); + + login({ + input: { + credentials: { + password, + username: usernameEmail, + }, + }, + onSuccess: closeModal, + }); + }; + + return ( +
+
+ setUsernameEmail(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + required + /> +
+
+ setPassword(e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + required + /> +
+ {error && ( +
+ {error.message || 'Login failed. Please try again.'} +
+ )} + +
+ ); +} diff --git a/examples/nextjs/kitchen-sink/src/constants/sessionConfig.js b/examples/nextjs/kitchen-sink/src/constants/sessionConfig.js new file mode 100644 index 000000000..08451c28b --- /dev/null +++ b/examples/nextjs/kitchen-sink/src/constants/sessionConfig.js @@ -0,0 +1,7 @@ +import { defaultIronOptions } from '@faustjs/auth'; + +export const sessionConfig = { + ...defaultIronOptions, + password: process.env.SESSION_PASSWORD, + cookieName: 'faust-auth', +}; diff --git a/examples/nextjs/kitchen-sink/src/pages/api/session/[action].js b/examples/nextjs/kitchen-sink/src/pages/api/session/[action].js new file mode 100644 index 000000000..7d0ceb980 --- /dev/null +++ b/examples/nextjs/kitchen-sink/src/pages/api/session/[action].js @@ -0,0 +1,19 @@ +import { sessionConfig } from '@/constants/sessionConfig'; +import { authRouter } from '@faustjs/auth'; +import { createDefaultClient, setGraphQLClient } from '@faustjs/nextjs/pages'; + +export default function handler(req, res) { + const { action } = req.query; + + const client = createDefaultClient(process.env.NEXT_PUBLIC_WORDPRESS_URL); + setGraphQLClient(client); + + return authRouter({ + client, + ironOptions: sessionConfig, + action, + loginProvider: 'PASSWORD', + req, + res, + }); +} diff --git a/packages/auth/README.md b/packages/auth/README.md new file mode 100644 index 000000000..e69de29bb diff --git a/packages/auth/config/defaults.js b/packages/auth/config/defaults.js new file mode 100644 index 000000000..b55662863 --- /dev/null +++ b/packages/auth/config/defaults.js @@ -0,0 +1,3 @@ +export const defaultIronOptions = { + cookieName: 'faust-auth-session', +}; diff --git a/packages/auth/handlers/AuthHandler.js b/packages/auth/handlers/AuthHandler.js new file mode 100644 index 000000000..0ea2e0a26 --- /dev/null +++ b/packages/auth/handlers/AuthHandler.js @@ -0,0 +1,485 @@ +import { getIronSession } from 'iron-session'; +import { defaultIronOptions } from '../config/defaults'; + +// Default configuration +const defaultConfig = { + ironOptions: defaultIronOptions, + tokenExpirationBuffer: 60000, // 1 minute buffer + errorMessages: { + notLoggedIn: 'User is not logged in.', + tokenExpired: 'Token has expired.', + invalidCredentials: 'Invalid credentials.', + serverError: 'Internal server error.', + unknownAction: 'Unknown action', + missingPassword: + 'Cookie password is not set. Please set it to a secure password of at least 32 characters.', + }, + supportedActions: ['login', 'logout', 'me', 'introspect', 'refresh', 'token'], +}; + +// Pure utility functions +const validateIronOptions = (ironOptions, config) => { + if (!ironOptions?.password) { + throw new Error(config.errorMessages.missingPassword); + } + return true; +}; + +const sendError = (res, statusCode, message, details = null) => + res.status(statusCode).json({ + error: true, + message, + details, + timestamp: new Date().toISOString(), + }); + +const sendSuccess = (res, data = null, message = 'Success') => + res.status(200).json({ + success: true, + message, + data, + timestamp: new Date().toISOString(), + }); + +const isTokenExpired = ( + token, + bufferMs = defaultConfig.tokenExpirationBuffer, +) => { + try { + const decodedToken = JSON.parse( + Buffer.from(token.split('.')[1], 'base64').toString(), + ); + + if (!decodedToken?.exp) { + return false; + } + + const expiresAt = new Date(decodedToken.exp * 1000); + const now = new Date(); + + return now.getTime() > expiresAt.getTime() - bufferMs; + } catch (error) { + return true; // If we can't decode, consider it expired + } +}; + +// Async utility functions +const refreshAuthToken = async (client, refreshToken) => { + const query = ` + mutation GetAuthToken($refreshToken: String!) { + refreshToken(input: { refreshToken: $refreshToken }) { + authToken + authTokenExpiration + success + } + } + `; + + try { + const res = await client.request(query, { + refreshToken, + }); + + if (res?.errors) { + throw new Error(res.errors[0].message); + } + + return res?.data?.refreshToken; + } catch (error) { + throw new Error(`Token refresh failed: ${error.message}`); + } +}; + +const getOrRefreshSession = async ({ + req, + res, + client, + ironOptions, + config = defaultConfig, +}) => { + const session = await getIronSession(req, res, ironOptions); + const { refreshToken, authToken } = session ?? {}; + + // No refresh token means not logged in + if (!refreshToken) { + session.destroy(); + return { success: false, error: 'NO_REFRESH_TOKEN' }; + } + + // Check if auth token needs refresh + if (!authToken || isTokenExpired(authToken, config.tokenExpirationBuffer)) { + try { + const refreshRes = await refreshAuthToken(client, refreshToken); + + if (!refreshRes?.authToken) { + session.destroy(); + return { success: false, error: 'TOKEN_REFRESH_FAILED' }; + } + + session.authToken = refreshRes.authToken; + await session.save(); + + return { success: true, session, refreshRes }; + } catch (error) { + // Keep session but remove auth token + delete session.authToken; + await session.save(); + return { + success: false, + error: 'TOKEN_REFRESH_ERROR', + details: error.message, + }; + } + } + + return { success: true, session }; +}; + +// Higher-order function for error handling +const withErrorHandling = + (handler, config = defaultConfig) => + async (params) => { + try { + return await handler(params); + } catch (error) { + return sendError( + params.res, + 500, + config.errorMessages.serverError, + error.message, + ); + } + }; + +// Individual handler functions +const loginHandler = async ({ + client, + ironOptions, + loginProvider = 'PASSWORD', + req, + res, + config = defaultConfig, +}) => { + const session = await getIronSession(req, res, ironOptions); + + const query = ` + mutation Login($input: LoginInput!) { + login(input: $input) { + authToken + refreshToken + } + } + `; + + try { + const response = await client.request(query, { + input: { ...req.body, provider: loginProvider }, + }); + + if (response?.errors) { + throw new Error(response.errors[0].message); + } + + const data = response?.data?.login; + + session.authToken = data.authToken; + session.refreshToken = data.refreshToken; + await session.save(); + + return sendSuccess( + res, + { authToken: data.authToken }, + 'Logged in successfully', + ); + } catch (error) { + return sendError( + res, + 401, + config.errorMessages.invalidCredentials, + error.message, + ); + } +}; + +const logoutHandler = async ({ + ironOptions, + req, + res, + config = defaultConfig, +}) => { + try { + const session = await getIronSession(req, res, ironOptions); + session.destroy(); + return sendSuccess(res, null, 'Logged out successfully'); + } catch (error) { + return sendError(res, 500, config.errorMessages.serverError, error.message); + } +}; + +const meHandler = async ({ + client, + ironOptions, + req, + res, + config = defaultConfig, +}) => { + const result = await getOrRefreshSession({ + req, + res, + client, + ironOptions, + config, + }); + + if (!result.success) { + return sendError(res, 401, config.errorMessages.notLoggedIn); + } + + // Fetch user data from GraphQL API + try { + // TODO make customizable + const query = ` + query GetCurrentUser { + viewer { + id + databaseId + email + name + firstName + lastName + username + } + }`; + + const response = await client.request( + query, + {}, + { + Authorization: `Bearer ${result.session.authToken}`, + }, + ); + + return sendSuccess( + res, + { + isAuthenticated: true, + user: response?.data?.viewer || null, + }, + 'User information retrieved', + ); + } catch (error) { + return sendError(res, 500, 'Failed to fetch user data', error.message); + } +}; + +// Token handler - provides the authToken, refreshing it if expired +const tokenHandler = async ({ + client, + ironOptions, + req, + res, + config = defaultConfig, +}) => { + const result = await getOrRefreshSession({ + req, + res, + client, + ironOptions, + config, + }); + + if (!result.success) { + return sendError(res, 401, config.errorMessages.notLoggedIn); + } + + // Get the token (either from existing session or newly refreshed) + const { authToken } = result.session; + const wasRefreshed = !!result.refreshRes; + + return sendSuccess(res, { + authToken, + expiresAt: wasRefreshed ? result.refreshRes.authTokenExpiration : null, + }); +}; + +// Pure introspect handler - no side effects, just checks current auth state +const introspectHandler = async ({ + client, + ironOptions, + req, + res, + config = defaultConfig, +}) => { + const session = await getIronSession(req, res, ironOptions); + const { refreshToken, authToken } = session ?? {}; + + // No refresh token means not logged in + if (!refreshToken) { + return sendError(res, 401, config.errorMessages.notLoggedIn, { + isAuthenticated: false, + hasRefreshToken: false, + hasAuthToken: false, + tokenExpired: null, + }); + } + + // Check if auth token exists and if it's expired + const tokenExpired = authToken + ? isTokenExpired(authToken, config.tokenExpirationBuffer) + : null; + const hasValidToken = authToken && !tokenExpired; + + // If not authenticated, use sendError for consistency + if (!hasValidToken) { + return sendError(res, 401, config.errorMessages.tokenExpired, { + isAuthenticated: false, + hasRefreshToken: true, + hasAuthToken: !!authToken, + tokenExpired, + }); + } + + // If authenticated, use sendSuccess + return sendSuccess( + res, + { + isAuthenticated: true, + hasRefreshToken: true, + hasAuthToken: true, + tokenExpired: false, + }, + 'User is authenticated', + ); +}; + +// Dedicated refresh handler - only refreshes tokens +const refreshHandler = async ({ + client, + ironOptions, + req, + res, + config = defaultConfig, +}) => { + const session = await getIronSession(req, res, ironOptions); + const { refreshToken } = session ?? {}; + + // No refresh token means can't refresh + if (!refreshToken) { + session.destroy(); + return sendError(res, 401, config.errorMessages.notLoggedIn); + } + + try { + const refreshRes = await refreshAuthToken(client, refreshToken); + + if (!refreshRes?.authToken) { + session.destroy(); + return sendError(res, 401, 'Failed to refresh authentication token'); + } + + session.authToken = refreshRes.authToken; + await session.save(); + + return sendSuccess( + res, + { + authToken: refreshRes.authToken, + refreshed: true, + }, + 'Tokens refreshed successfully', + ); + } catch (error) { + // Keep session but remove auth token + delete session.authToken; + await session.save(); + return sendError(res, 401, 'Failed to refresh token', error.message); + } +}; + +// Action handler mapping +const actionHandlers = { + login: loginHandler, + logout: logoutHandler, + me: meHandler, + introspect: introspectHandler, + refresh: refreshHandler, + token: tokenHandler, +}; + +// Main routing function with currying for configuration +const createAuthRouter = (userConfig = {}) => { + const config = { ...defaultConfig, ...userConfig }; + + return async ({ + client, + ironOptions = config.ironOptions, + action, + loginProvider = 'PASSWORD', + req, + res, + }) => { + // Validate iron options + validateIronOptions(ironOptions, config); + + // Check if action is supported + if (!config.supportedActions.includes(action)) { + return sendError( + res, + 400, + `${config.errorMessages.unknownAction}: ${action}`, + ); + } + + const handler = actionHandlers[action]; + if (!handler) { + return sendError( + res, + 400, + `${config.errorMessages.unknownAction}: ${action}`, + ); + } + + // Apply error handling wrapper and call handler + const safeHandler = withErrorHandling(handler, config); + + // All handlers now use the same named parameter structure + return safeHandler({ + client, + ironOptions, + loginProvider, + req, + res, + config, + }); + }; +}; + +// Create default auth router +const authRouter = createAuthRouter(); + +// Export everything +export { + // Main functions + createAuthRouter, + authRouter, + + // Individual handlers + loginHandler, + logoutHandler, + meHandler, + introspectHandler, + refreshHandler, + tokenHandler, + + // Utilities + validateIronOptions, + sendError, + sendSuccess, + isTokenExpired, + refreshAuthToken, + getOrRefreshSession, + withErrorHandling, + + // Configuration + defaultConfig, +}; diff --git a/packages/auth/index.js b/packages/auth/index.js new file mode 100644 index 000000000..d76cedba1 --- /dev/null +++ b/packages/auth/index.js @@ -0,0 +1 @@ +export * from './handlers/AuthHandler'; diff --git a/packages/auth/package.json b/packages/auth/package.json new file mode 100644 index 000000000..f7acef262 --- /dev/null +++ b/packages/auth/package.json @@ -0,0 +1,24 @@ +{ + "name": "@faustjs/auth", + "version": "4.0.0-alpha.0", + "description": "Authentication utilities for Faust.js", + "type": "module", + "exports": { + ".": "./index.js" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [ + "graphql", + "wordpress", + "faustjs", + "client" + ], + "license": "MIT", + "packageManager": "pnpm@9.15.0", + "peerDependencies": { + "iron-session": "^6.3.1", + "jsonwebtoken": "^9.0.0" + } +} diff --git a/packages/graphql/client.js b/packages/graphql/client.js index 87552e174..48bf76ba6 100644 --- a/packages/graphql/client.js +++ b/packages/graphql/client.js @@ -48,13 +48,14 @@ export function createDefaultGraphQLClient(wordpressUrl, headers = {}) { const endpoint = buildGraphQLEndpoint(wordpressUrl); return { - async request(query, variables = {}) { + async request(query, variables = {}, requestHeaders = {}) { try { const response = await fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', ...headers, + ...requestHeaders, }, body: JSON.stringify({ query, variables }), }); diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 62c394320..79f0906ba 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -26,7 +26,8 @@ }, "dependencies": { "@faustjs/template-hierarchy": "workspace:*", - "@faustjs/graphql": "workspace:*" + "@faustjs/graphql": "workspace:*", + "react": "^18.0.0 || ^19.0.0" }, "peerDependencies": { "next": "^13.0.0 || ^14.0.0 || ^15.0.0", diff --git a/packages/nextjs/pages/auth/hooks/index.js b/packages/nextjs/pages/auth/hooks/index.js new file mode 100644 index 000000000..74ad63b5b --- /dev/null +++ b/packages/nextjs/pages/auth/hooks/index.js @@ -0,0 +1,3 @@ +export { useLogin } from './useLogin.js'; +export { useLogout } from './useLogout.js'; +export { useUser } from './useUser.js'; diff --git a/packages/nextjs/pages/auth/hooks/useLogin.js b/packages/nextjs/pages/auth/hooks/useLogin.js new file mode 100644 index 000000000..078a738de --- /dev/null +++ b/packages/nextjs/pages/auth/hooks/useLogin.js @@ -0,0 +1,55 @@ +import { useState } from 'react'; + +const DEFAULT_LOGIN_URL = '/api/session/login'; + +function useLogin() { + const [error, setError] = useState(); + const [data, setData] = useState(); + const [isLoading, setIsLoading] = useState(false); + + async function login({ + loginUrl = DEFAULT_LOGIN_URL, + input, + onSuccess = () => {}, + }) { + if (!input) { + // TODO further input validation + + // TODO consistent error format + setError(['Input is required']); + return; + } + + setIsLoading(true); + + // TODO add try catch + const res = await fetch(loginUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(input), + }); + + const data = await res.json(); + + if (!res.ok) { + setError(data); + setIsLoading(false); + return; + } + + setIsLoading(false); + setData(data); // TODO get data shape same as Faust + onSuccess(data); + } + + return { + login, + isLoading, + data, + error, + }; +} + +export { useLogin }; diff --git a/packages/nextjs/pages/auth/hooks/useLogout.js b/packages/nextjs/pages/auth/hooks/useLogout.js new file mode 100644 index 000000000..0c0f536be --- /dev/null +++ b/packages/nextjs/pages/auth/hooks/useLogout.js @@ -0,0 +1,49 @@ +import { useState } from 'react'; + +const DEFAULT_LOGOUT_URL = '/api/session/logout'; + +export function useLogout() { + const [error, setError] = useState(); + const [data, setData] = useState(); + const [isLoading, setIsLoading] = useState(false); + + async function logout({ + logoutUrl = DEFAULT_LOGOUT_URL, + onSuccess = () => {}, + } = {}) { + setIsLoading(true); + setError(undefined); + + try { + const res = await fetch(logoutUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + }); + + const responseData = await res.json(); + + if (!res.ok) { + setError(responseData); + return; + } + + setData(responseData); + onSuccess(responseData); + } catch (err) { + setError( + err instanceof Error ? err.message : 'An unknown error occurred', + ); + } finally { + setIsLoading(false); + } + } + + return { + logout, + isLoading, + data, + error, + }; +} diff --git a/packages/nextjs/pages/auth/hooks/useUser.js b/packages/nextjs/pages/auth/hooks/useUser.js new file mode 100644 index 000000000..90e26c38a --- /dev/null +++ b/packages/nextjs/pages/auth/hooks/useUser.js @@ -0,0 +1,76 @@ +import { useState, useEffect } from 'react'; + +const DEFAULT_ME_URL = '/api/session/me'; + +function useUser({ autoFetch = true } = {}) { + const [error, setError] = useState(); + const [data, setData] = useState(); + const [isLoading, setIsLoading] = useState(false); + const [isAuthenticated, setIsAuthenticated] = useState(false); + + async function fetchUser({ + meUrl = DEFAULT_ME_URL, + onSuccess = () => {}, + onError = () => {}, + } = {}) { + setIsLoading(true); + setError(null); + + try { + const res = await fetch(meUrl, { + method: 'GET', + credentials: 'include', // Include cookies for session + headers: { + 'Content-Type': 'application/json', + }, + }); + + const responseData = await res.json(); + + if (!res.ok) { + setError(responseData); + setIsAuthenticated(false); + setData(null); + onError(responseData); + setIsLoading(false); + return; + } + + setIsAuthenticated(responseData.data?.isAuthenticated || false); + setData(responseData.data?.user || null); + onSuccess(responseData.data); + } catch (err) { + const errorData = { + error: true, + message: 'Network error or server unavailable', + details: err.message, + }; + setError(errorData); + setIsAuthenticated(false); + setData(null); + onError(errorData); + } finally { + setIsLoading(false); + } + } + + // Auto-fetch user data on mount if enabled + useEffect(() => { + if (autoFetch) { + fetchUser(); + } + }, [autoFetch]); + + const refetch = () => fetchUser(); + + return { + fetchUser, + refetch, + isLoading, + user: data, + error, + isAuthenticated, + }; +} + +export { useUser }; diff --git a/packages/nextjs/pages/auth/index.js b/packages/nextjs/pages/auth/index.js new file mode 100644 index 000000000..e8506b981 --- /dev/null +++ b/packages/nextjs/pages/auth/index.js @@ -0,0 +1,2 @@ +// Hooks +export * from './hooks/index.js'; diff --git a/packages/nextjs/pages/index.js b/packages/nextjs/pages/index.js index 12278c47f..c815f302f 100644 --- a/packages/nextjs/pages/index.js +++ b/packages/nextjs/pages/index.js @@ -11,10 +11,12 @@ export { uriToTemplate } from './templateHierarchy.js'; // Export Next.js preview enabling api handler export { enablePreview } from './enablePreview.js'; -// Export GraphQL client configuration // Export GraphQL client configuration export { setGraphQLClient, getGraphQLClient, createDefaultGraphQLClient as createDefaultClient, } from '@faustjs/graphql'; + +// Export authentication hooks and utilities +export * from './auth/index.js'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3d1ecb3d0..ca788a56a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -121,6 +121,9 @@ importers: examples/nextjs/kitchen-sink: dependencies: + '@faustjs/auth': + specifier: workspace:* + version: link:../../../packages/auth '@faustjs/data-fetching': specifier: workspace:* version: link:../../../packages/data-fetching @@ -297,6 +300,15 @@ importers: specifier: ^16.8.1 version: 16.11.0 + packages/auth: + dependencies: + iron-session: + specifier: ^6.3.1 + version: 6.3.1(next@15.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)) + jsonwebtoken: + specifier: ^9.0.0 + version: 9.0.2 + packages/data-fetching: dependencies: graphql: @@ -324,7 +336,7 @@ importers: version: 14.2.31(react-dom@19.1.1(react@18.3.1))(react@18.3.1) react: specifier: ^18.0.0 || ^19.0.0 - version: 18.3.1 + version: 19.1.1 packages/sveltekit: dependencies: @@ -1162,6 +1174,17 @@ packages: '@oslojs/encoding@1.1.0': resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} + '@peculiar/asn1-schema@2.4.0': + resolution: {integrity: sha512-umbembjIWOrPSOzEGG5vxFLkeM8kzIhLkgigtsOrfLKnuzxWxejAcUX+q/SoZCdemlODOcr5WiYa7+dIEzBXZQ==} + + '@peculiar/json-schema@1.1.12': + resolution: {integrity: sha512-coUfuoMeIB7B8/NMekxaDzLhaYmp0HZNPEjYRm9goRou8UZIC3z21s0sL9AWoCw4EG876QyO3kYrc61WNF9B/w==} + engines: {node: '>=8.0.0'} + + '@peculiar/webcrypto@1.5.0': + resolution: {integrity: sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg==} + engines: {node: '>=10.12.0'} + '@polka/url@1.0.0-next.29': resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==} @@ -1531,30 +1554,72 @@ packages: '@tybys/wasm-util@0.10.0': resolution: {integrity: sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ==} + '@types/accepts@1.3.7': + resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==} + + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/content-disposition@0.5.9': + resolution: {integrity: sha512-8uYXI3Gw35MhiVYhG3s295oihrxRyytcRHjSjqnqZVDDy/xcGBRny7+Xj1Wgfhv5QzRtN2hB2dVRBUX9XW3UcQ==} + + '@types/cookie@0.5.4': + resolution: {integrity: sha512-7z/eR6O859gyWIAjuvBWFzNURmf2oPBmJlfVWkwehU5nzIyjwBsTh7WMmEEV4JFnHuQ3ex4oyTvfKzcyJVDBNA==} + '@types/cookie@0.6.0': resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + '@types/cookies@0.9.1': + resolution: {integrity: sha512-E/DPgzifH4sM1UMadJMWd6mO2jOd4g1Ejwzx8/uRCDpJis1IrlyQEcGAYEomtAqRYmD5ORbNXMeI9U0RiVGZbg==} + '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/express-serve-static-core@4.19.6': + resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} + + '@types/express@4.17.23': + resolution: {integrity: sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==} + '@types/fontkit@2.0.8': resolution: {integrity: sha512-wN+8bYxIpJf+5oZdrdtaX04qUuWHcKxcDEgRS9Qm9ZClSHjzEn13SxUC+5eRM+4yXIeTYk8mTzLAWGF64847ew==} '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/http-assert@1.5.6': + resolution: {integrity: sha512-TTEwmtjgVbYAzZYWyeHPrrtWnfVkm8tQkP8P21uQifPgMRgjrow3XDEYqucuC8SKZJT7pUnhU/JymvjggxO9vw==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/keygrip@1.0.6': + resolution: {integrity: sha512-lZuNAY9xeJt7Bx4t4dx0rYCDqGPW8RXhQZK1td7d4H6E9zYbLoOtjBvfwdTKpsyxQI/2jv+armjX/RW+ZNpXOQ==} + + '@types/koa-compose@3.2.8': + resolution: {integrity: sha512-4Olc63RY+MKvxMwVknCUDhRQX1pFQoBZ/lXcRLP69PQkEpze/0cr8LNqJQe5NFb/b19DWi2a5bTi2VAlQzhJuA==} + + '@types/koa@2.15.0': + resolution: {integrity: sha512-7QFsywoE5URbuVnG3loe03QXuGajrnotr3gQkXcEBShORai23MePfFYdhz90FEtBBpkyIYQbVD+evKtloCgX3g==} + '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} @@ -1564,9 +1629,24 @@ packages: '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} + '@types/node@17.0.45': + resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} + + '@types/qs@6.14.0': + resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/semver@7.7.0': resolution: {integrity: sha512-k107IF4+Xr7UHjwDc7Cfd6PRQfbdkiRabXGRjo07b4WyPahFBZCZ1sE+BNxYIJPPg73UkfOsVOLwqVc/6ETrIA==} + '@types/send@0.17.5': + resolution: {integrity: sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==} + + '@types/serve-static@1.15.8': + resolution: {integrity: sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==} + '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} @@ -1832,6 +1912,10 @@ packages: resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} + asn1js@3.0.6: + resolution: {integrity: sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==} + engines: {node: '>=12.0.0'} + ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} @@ -1889,6 +1973,12 @@ packages: brotli@1.3.3: resolution: {integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==} + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -2003,6 +2093,10 @@ packages: cookie-es@1.2.2: resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} + cookie@0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + cookie@0.6.0: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} @@ -2141,6 +2235,9 @@ packages: duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + emoji-regex@10.4.0: resolution: {integrity: sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==} @@ -2682,6 +2779,9 @@ packages: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -2708,6 +2808,24 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + iron-session@6.3.1: + resolution: {integrity: sha512-3UJ7y2vk/WomAtEySmPgM6qtYF1cZ3tXuWX5GsVX4PJXAcs5y/sV9HuSfpjKS6HkTL/OhZcTDWJNLZ7w+Erx3A==} + engines: {node: '>=12'} + peerDependencies: + express: '>=4' + koa: '>=2' + next: '>=10' + peerDependenciesMeta: + express: + optional: true + koa: + optional: true + next: + optional: true + + iron-webcrypto@0.2.8: + resolution: {integrity: sha512-YPdCvjFMOBjXaYuDj5tiHst5CEk6Xw84Jo8Y2+jzhMceclAnb3+vNPP/CTtb5fO2ZEuXEaO4N+w62Vfko757KA==} + iron-webcrypto@1.2.1: resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} @@ -2891,10 +3009,20 @@ packages: jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + jsonwebtoken@9.0.2: + resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} + engines: {node: '>=12', npm: '>=6'} + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} + jwa@1.4.2: + resolution: {integrity: sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==} + + jws@3.2.2: + resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -2992,9 +3120,30 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} @@ -3499,6 +3648,13 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + pvtsutils@1.3.6: + resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==} + + pvutils@1.1.3: + resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==} + engines: {node: '>=6.0.0'} + quansync@0.2.10: resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} @@ -3649,6 +3805,9 @@ packages: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-push-apply@1.0.0: resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} engines: {node: '>= 0.4'} @@ -4162,6 +4321,9 @@ packages: web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + webcrypto-core@1.8.1: + resolution: {integrity: sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A==} + webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -5024,6 +5186,24 @@ snapshots: '@oslojs/encoding@1.1.0': {} + '@peculiar/asn1-schema@2.4.0': + dependencies: + asn1js: 3.0.6 + pvtsutils: 1.3.6 + tslib: 2.8.1 + + '@peculiar/json-schema@1.1.12': + dependencies: + tslib: 2.8.1 + + '@peculiar/webcrypto@1.5.0': + dependencies: + '@peculiar/asn1-schema': 2.4.0 + '@peculiar/json-schema': 1.1.12 + pvtsutils: 1.3.6 + tslib: 2.8.1 + webcrypto-core: 1.8.1 + '@polka/url@1.0.0-next.29': {} '@rollup/pluginutils@5.2.0(rollup@4.46.2)': @@ -5345,14 +5525,52 @@ snapshots: tslib: 2.8.1 optional: true + '@types/accepts@1.3.7': + dependencies: + '@types/node': 17.0.45 + + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 17.0.45 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 17.0.45 + + '@types/content-disposition@0.5.9': {} + + '@types/cookie@0.5.4': {} + '@types/cookie@0.6.0': {} + '@types/cookies@0.9.1': + dependencies: + '@types/connect': 3.4.38 + '@types/express': 4.17.23 + '@types/keygrip': 1.0.6 + '@types/node': 17.0.45 + '@types/debug@4.1.12': dependencies: '@types/ms': 2.1.0 '@types/estree@1.0.8': {} + '@types/express-serve-static-core@4.19.6': + dependencies: + '@types/node': 17.0.45 + '@types/qs': 6.14.0 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.5 + + '@types/express@4.17.23': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 4.19.6 + '@types/qs': 6.14.0 + '@types/serve-static': 1.15.8 + '@types/fontkit@2.0.8': dependencies: '@types/node': 12.20.55 @@ -5361,14 +5579,37 @@ snapshots: dependencies: '@types/unist': 3.0.3 + '@types/http-assert@1.5.6': {} + + '@types/http-errors@2.0.5': {} + '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} + '@types/keygrip@1.0.6': {} + + '@types/koa-compose@3.2.8': + dependencies: + '@types/koa': 2.15.0 + + '@types/koa@2.15.0': + dependencies: + '@types/accepts': 1.3.7 + '@types/content-disposition': 0.5.9 + '@types/cookies': 0.9.1 + '@types/http-assert': 1.5.6 + '@types/http-errors': 2.0.5 + '@types/keygrip': 1.0.6 + '@types/koa-compose': 3.2.8 + '@types/node': 17.0.45 + '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 + '@types/mime@1.3.5': {} + '@types/ms@2.1.0': {} '@types/nlcst@2.0.3': @@ -5377,8 +5618,25 @@ snapshots: '@types/node@12.20.55': {} + '@types/node@17.0.45': {} + + '@types/qs@6.14.0': {} + + '@types/range-parser@1.2.7': {} + '@types/semver@7.7.0': {} + '@types/send@0.17.5': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 17.0.45 + + '@types/serve-static@1.15.8': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 17.0.45 + '@types/send': 0.17.5 + '@types/unist@3.0.3': {} '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3)': @@ -5717,6 +5975,12 @@ snapshots: get-intrinsic: 1.3.0 is-array-buffer: 3.0.5 + asn1js@3.0.6: + dependencies: + pvtsutils: 1.3.6 + pvutils: 1.1.3 + tslib: 2.8.1 + ast-types-flow@0.0.8: {} astro@5.12.8(jiti@2.5.1)(lightningcss@1.30.1)(rollup@4.46.2)(typescript@5.8.3)(yaml@2.8.1): @@ -5868,6 +6132,13 @@ snapshots: dependencies: base64-js: 1.5.1 + buffer-equal-constant-time@1.0.1: {} + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + busboy@1.6.0: dependencies: streamsearch: 1.1.0 @@ -5960,6 +6231,8 @@ snapshots: cookie-es@1.2.2: {} + cookie@0.5.0: {} + cookie@0.6.0: {} cookie@1.0.2: {} @@ -6083,6 +6356,10 @@ snapshots: duplexer@0.1.2: {} + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + emoji-regex@10.4.0: {} emoji-regex@8.0.0: {} @@ -7017,6 +7294,8 @@ snapshots: dependencies: safer-buffer: 2.1.2 + ieee754@1.2.1: {} + ignore@5.3.2: {} import-fresh@3.3.1: @@ -7041,6 +7320,22 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + iron-session@6.3.1(next@15.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)): + dependencies: + '@peculiar/webcrypto': 1.5.0 + '@types/cookie': 0.5.4 + '@types/express': 4.17.23 + '@types/koa': 2.15.0 + '@types/node': 17.0.45 + cookie: 0.5.0 + iron-webcrypto: 0.2.8 + optionalDependencies: + next: 15.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + + iron-webcrypto@0.2.8: + dependencies: + buffer: 6.0.3 + iron-webcrypto@1.2.1: {} is-array-buffer@3.0.5: @@ -7221,6 +7516,19 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jsonwebtoken@9.0.2: + dependencies: + jws: 3.2.2 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.7.2 + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.8 @@ -7228,6 +7536,17 @@ snapshots: object.assign: 4.1.7 object.values: 1.2.1 + jwa@1.4.2: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@3.2.2: + dependencies: + jwa: 1.4.2 + safe-buffer: 5.2.1 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -7302,8 +7621,22 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + lodash.merge@4.6.2: {} + lodash.once@4.1.1: {} + lodash.startcase@4.4.0: {} lodash@4.17.21: {} @@ -7755,6 +8088,30 @@ snapshots: - '@babel/core' - babel-plugin-macros + next@15.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + dependencies: + '@next/env': 15.4.5 + '@swc/helpers': 0.5.15 + caniuse-lite: 1.0.30001731 + postcss: 8.4.31 + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + styled-jsx: 5.1.6(react@19.1.1) + optionalDependencies: + '@next/swc-darwin-arm64': 15.4.5 + '@next/swc-darwin-x64': 15.4.5 + '@next/swc-linux-arm64-gnu': 15.4.5 + '@next/swc-linux-arm64-musl': 15.4.5 + '@next/swc-linux-x64-gnu': 15.4.5 + '@next/swc-linux-x64-musl': 15.4.5 + '@next/swc-win32-arm64-msvc': 15.4.5 + '@next/swc-win32-x64-msvc': 15.4.5 + sharp: 0.34.3 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + optional: true + nlcst-to-string@4.0.0: dependencies: '@types/nlcst': 2.0.3 @@ -7971,6 +8328,12 @@ snapshots: punycode@2.3.1: {} + pvtsutils@1.3.6: + dependencies: + tslib: 2.8.1 + + pvutils@1.1.3: {} + quansync@0.2.10: {} queue-microtask@1.2.3: {} @@ -8203,6 +8566,8 @@ snapshots: has-symbols: 1.1.0 isarray: 2.0.5 + safe-buffer@5.2.1: {} + safe-push-apply@1.0.0: dependencies: es-errors: 1.3.0 @@ -8787,6 +9152,14 @@ snapshots: web-namespaces@2.0.1: {} + webcrypto-core@1.8.1: + dependencies: + '@peculiar/asn1-schema': 2.4.0 + '@peculiar/json-schema': 1.1.12 + asn1js: 3.0.6 + pvtsutils: 1.3.6 + tslib: 2.8.1 + webidl-conversions@3.0.1: {} webpack-bundle-analyzer@4.7.0: From 21f19de6fa35ec1edb168230cd8b44d2a4d80342 Mon Sep 17 00:00:00 2001 From: ahuseyn Date: Fri, 19 Sep 2025 11:55:11 +0200 Subject: [PATCH 19/24] add alternative preview with auth package, add login page, client updates --- examples/nextjs/kitchen-sink/package.json | 5 +- .../kitchen-sink/src/components/Header.js | 8 ++- .../kitchen-sink/src/components/Login.js | 7 ++- .../kitchen-sink/src/lib/resolveWpRoute.js | 52 ++++++++++++++++ .../src/pages/[[...identifier]].js | 55 +++-------------- .../kitchen-sink/src/pages/api/preview.js | 3 + .../nextjs/kitchen-sink/src/pages/login.js | 17 ++++++ .../src/pages/preview/[...identifier].js | 38 ++++++++++++ packages/auth/handlers/AuthHandler.js | 60 ++++++++++++++++++- packages/graphql/client.js | 16 +++-- pnpm-lock.yaml | 47 +++++++++------ 11 files changed, 230 insertions(+), 78 deletions(-) create mode 100644 examples/nextjs/kitchen-sink/src/lib/resolveWpRoute.js create mode 100644 examples/nextjs/kitchen-sink/src/pages/login.js create mode 100644 examples/nextjs/kitchen-sink/src/pages/preview/[...identifier].js diff --git a/examples/nextjs/kitchen-sink/package.json b/examples/nextjs/kitchen-sink/package.json index 54fef3006..c0242df51 100644 --- a/examples/nextjs/kitchen-sink/package.json +++ b/examples/nextjs/kitchen-sink/package.json @@ -9,12 +9,13 @@ "lint": "next lint" }, "dependencies": { + "@faustjs/auth": "workspace:*", + "@faustjs/data-fetching": "workspace:*", "@faustjs/nextjs": "workspace:*", "@faustjs/template-hierarchy": "workspace:*", - "@faustjs/data-fetching": "workspace:*", - "@faustjs/auth": "workspace:*", "graphql": "^16.11.0", "graphql-tag": "^2.12.6", + "iron-session": "^8.0.4", "next": "15.2.4", "react": "^19.0.0", "react-dom": "^19.0.0" diff --git a/examples/nextjs/kitchen-sink/src/components/Header.js b/examples/nextjs/kitchen-sink/src/components/Header.js index a97cb5c58..644999fb9 100644 --- a/examples/nextjs/kitchen-sink/src/components/Header.js +++ b/examples/nextjs/kitchen-sink/src/components/Header.js @@ -2,11 +2,13 @@ import Link from 'next/link'; import { useState } from 'react'; import Login from './Login'; import { useLogout, useUser } from '@faustjs/nextjs/pages'; +import { useRouter } from 'next/router'; export default function Header() { const { user, refetch, isAuthenticated } = useUser(); const { logout } = useLogout(); const [isLoginModalOpen, setIsLoginModalOpen] = useState(false); + const route = useRouter(); const openLoginModal = () => setIsLoginModalOpen(true); const closeLoginModal = () => setIsLoginModalOpen(false); @@ -32,7 +34,7 @@ export default function Header() { Headless

-

- ) : ( + ) : route.asPath !== '/login' ? ( - )} + ) : null}

diff --git a/examples/nextjs/kitchen-sink/src/components/Login.js b/examples/nextjs/kitchen-sink/src/components/Login.js index acbc68d23..d6fb9c152 100644 --- a/examples/nextjs/kitchen-sink/src/components/Login.js +++ b/examples/nextjs/kitchen-sink/src/components/Login.js @@ -1,7 +1,7 @@ import { useLogin } from '@faustjs/nextjs/pages'; import { useState } from 'react'; -export default function Login({ closeModal = () => {} }) { +export default function Login({ closeModal = () => {}, onSuccess = () => {} }) { const [usernameEmail, setUsernameEmail] = useState(''); const [password, setPassword] = useState(''); @@ -17,7 +17,10 @@ export default function Login({ closeModal = () => {} }) { username: usernameEmail, }, }, - onSuccess: closeModal, + onSuccess: () => { + onSuccess(); + closeModal(); + }, }); }; diff --git a/examples/nextjs/kitchen-sink/src/lib/resolveWpRoute.js b/examples/nextjs/kitchen-sink/src/lib/resolveWpRoute.js new file mode 100644 index 000000000..62521b7a4 --- /dev/null +++ b/examples/nextjs/kitchen-sink/src/lib/resolveWpRoute.js @@ -0,0 +1,52 @@ +import availableTemplates from '@/wp-templates'; +import availableQueries from '@/wp-templates/templateQueries'; +import { fetchTemplateQueries } from '@faustjs/data-fetching'; +import { uriToTemplate } from '@faustjs/nextjs/pages'; + +// Resolves the WordPress route based on the identifier (slug or ID) and fetches the necessary data +// for the corresponding template. It returns the props needed for rendering the page. +// If the route or template is not found, it returns a 404 response. + +export async function resolveWpRoute(identifier, isPreview, client) { + const uri = identifier ? `/${identifier.join('/')}/` : '/'; + + const variables = isPreview + ? { + id: identifier?.[0], + asPreview: true, + } + : { uri }; + + try { + const templateData = await uriToTemplate({ + ...variables, + availableTemplates: Object.keys(availableTemplates), + wordpressUrl: process.env.NEXT_PUBLIC_WORDPRESS_URL, + }); + + if ( + !templateData?.template?.id || + templateData?.template?.id === '404 Not Found' + ) { + return { notFound: true }; + } + + const queriesData = await fetchTemplateQueries({ + availableQueries, + templateData, + client, + locale: templateData?.seedNode?.locale, + }); + + return { + props: { + uri, + templateData: JSON.parse(JSON.stringify(templateData)), + queriesData, + }, + }; + } catch (error) { + console.error('Error resolving template:', error); + return { notFound: true }; + } +} diff --git a/examples/nextjs/kitchen-sink/src/pages/[[...identifier]].js b/examples/nextjs/kitchen-sink/src/pages/[[...identifier]].js index 3eea8f967..24f05c52d 100644 --- a/examples/nextjs/kitchen-sink/src/pages/[[...identifier]].js +++ b/examples/nextjs/kitchen-sink/src/pages/[[...identifier]].js @@ -1,12 +1,11 @@ +import { resolveWpRoute } from '@/lib/resolveWpRoute'; import { getAuthString } from '@/utils/getAuthString'; import availableTemplates from '@/wp-templates'; -import availableQueries from '@/wp-templates/templateQueries'; -import { - createDefaultClient, - setGraphQLClient, - uriToTemplate, -} from '@faustjs/nextjs/pages'; -import { fetchTemplateQueries } from '@faustjs/data-fetching'; +import { createDefaultClient, setGraphQLClient } from '@faustjs/nextjs/pages'; + +// This is a catch-all dynamic route to handle all WordPress pages and posts. +// It uses getStaticProps and getStaticPaths for SSG with fallback blocking. +// It also supports Draft Mode previews with application passwords. export default function Page(props) { const { templateData } = props; @@ -34,47 +33,7 @@ export async function getStaticProps({ setGraphQLClient(client); - const uri = params?.identifier ? `/${params.identifier.join('/')}/` : '/'; - - const variables = isDraftModeEnabled - ? { - id: params.identifier?.[0], - asPreview: true, - } - : { uri }; - - try { - const templateData = await uriToTemplate({ - ...variables, - availableTemplates: Object.keys(availableTemplates), - wordpressUrl: process.env.NEXT_PUBLIC_WORDPRESS_URL, - }); - - if ( - !templateData?.template?.id || - templateData?.template?.id === '404 Not Found' - ) { - return { notFound: true }; - } - - const queriesData = await fetchTemplateQueries({ - availableQueries, - templateData, - client, - locale: templateData?.seedNode?.locale, - }); - - return { - props: { - uri, - templateData: JSON.parse(JSON.stringify(templateData)), - queriesData, - }, - }; - } catch (error) { - console.error('Error resolving template:', error); - return { notFound: true }; - } + return await resolveWpRoute(params?.identifier, isDraftModeEnabled, client); } export async function getStaticPaths() { diff --git a/examples/nextjs/kitchen-sink/src/pages/api/preview.js b/examples/nextjs/kitchen-sink/src/pages/api/preview.js index 7cbca238b..e8d7c4773 100644 --- a/examples/nextjs/kitchen-sink/src/pages/api/preview.js +++ b/examples/nextjs/kitchen-sink/src/pages/api/preview.js @@ -1,6 +1,9 @@ import { getAuthString } from '@/utils/getAuthString'; import { enablePreview } from '@faustjs/nextjs/pages'; +// This is a preview approach using Next.js Draft Mode with application passwords. +// To enable Draft Mode previews, set the HWP Previews setting to: http://your.frontend/api/preview?secret=YOURSECRET&id={ID} + export default enablePreview({ wordpressUrl: process.env.NEXT_PUBLIC_WORDPRESS_URL, expectedSecret: process.env.WP_PREVIEW_SECRET, diff --git a/examples/nextjs/kitchen-sink/src/pages/login.js b/examples/nextjs/kitchen-sink/src/pages/login.js new file mode 100644 index 000000000..8b90b18ab --- /dev/null +++ b/examples/nextjs/kitchen-sink/src/pages/login.js @@ -0,0 +1,17 @@ +import { default as LoginComponent } from '@/components/Login'; +import { useRouter } from 'next/router'; + +export default function Login() { + const router = useRouter(); + + return ( +
+

Login

+ { + router.push('/'); + }} + /> +
+ ); +} diff --git a/examples/nextjs/kitchen-sink/src/pages/preview/[...identifier].js b/examples/nextjs/kitchen-sink/src/pages/preview/[...identifier].js new file mode 100644 index 000000000..ff4c6b0bb --- /dev/null +++ b/examples/nextjs/kitchen-sink/src/pages/preview/[...identifier].js @@ -0,0 +1,38 @@ +import { sessionConfig } from '@/constants/sessionConfig'; +import { resolveWpRoute } from '@/lib/resolveWpRoute'; +import availableTemplates from '@/wp-templates'; +import { createDefaultClient, setGraphQLClient } from '@faustjs/nextjs/pages'; +import { getIronSession } from 'iron-session'; + +// This is an alternative preview approach to use the user credentials stored in the session +// instead of the Next.js Draft Mode with application passwords. +// To enable previews, set the HWP Previews plugin setting to: http://your.frontend/preview/{ID} + +export default function Preview(props) { + const { templateData } = props; + + const PageTemplate = availableTemplates[templateData?.template?.id]; + + return ; +} + +export async function getServerSideProps({ req, res, params }) { + const session = await getIronSession(req, res, sessionConfig); + + if (!session?.authToken) { + return { + redirect: { + destination: '/login', + permanent: false, + }, + }; + } + + const client = createDefaultClient(process.env.NEXT_PUBLIC_WORDPRESS_URL, { + Authorization: `Bearer ${session?.authToken}`, + }); + + setGraphQLClient(client); + + return await resolveWpRoute(params?.identifier, true, client); +} diff --git a/packages/auth/handlers/AuthHandler.js b/packages/auth/handlers/AuthHandler.js index 0ea2e0a26..1ae0c0a4c 100644 --- a/packages/auth/handlers/AuthHandler.js +++ b/packages/auth/handlers/AuthHandler.js @@ -14,7 +14,15 @@ const defaultConfig = { missingPassword: 'Cookie password is not set. Please set it to a secure password of at least 32 characters.', }, - supportedActions: ['login', 'logout', 'me', 'introspect', 'refresh', 'token'], + supportedActions: [ + 'login', + 'logout', + 'me', + 'introspect', + 'refresh', + 'token', + 'query', + ], }; // Pure utility functions @@ -272,6 +280,54 @@ const meHandler = async ({ } }; +// Authenticated query handler - forwards GQL queries with auth token +const authenticatedQueryHandler = async ({ + client, + ironOptions, + req, + res, + config = defaultConfig, +}) => { + // First verify and refresh session if needed + const result = await getOrRefreshSession({ + req, + res, + client, + ironOptions, + config, + }); + + if (!result.success) { + return sendError(res, 401, config.errorMessages.notLoggedIn); + } + + try { + // Extract the GraphQL query, variables and operationName from the request + const { query, variables, operationName } = req.body; + + if (!query) { + return sendError(res, 400, 'GraphQL query is required'); + } + + // Forward the query to the GraphQL API with the auth token + const response = await client.request(query, variables || {}, { + Authorization: `Bearer ${result.session.authToken}`, + ...(req.headers?.['content-type'] && { + 'Content-Type': req.headers['content-type'], + }), + }); + + return res.status(200).json(response); + } catch (error) { + return sendError( + res, + 500, + 'Failed to execute authenticated query', + error.message, + ); + } +}; + // Token handler - provides the authToken, refreshing it if expired const tokenHandler = async ({ client, @@ -404,6 +460,7 @@ const actionHandlers = { introspect: introspectHandler, refresh: refreshHandler, token: tokenHandler, + query: authenticatedQueryHandler, }; // Main routing function with currying for configuration @@ -470,6 +527,7 @@ export { introspectHandler, refreshHandler, tokenHandler, + authenticatedQueryHandler, // Utilities validateIronOptions, diff --git a/packages/graphql/client.js b/packages/graphql/client.js index 48bf76ba6..29bb902ed 100644 --- a/packages/graphql/client.js +++ b/packages/graphql/client.js @@ -34,18 +34,24 @@ export function buildGraphQLEndpoint(wordpressUrl) { /** * Create a default GraphQL client using fetch - * @param {string} wordpressUrl - WordPress URL to use (required) + * @param {string} requestUrl - WordPress URL to use (required) * @returns {import('./types.js').GraphQLClient} A basic GraphQL client * @throws {Error} If no WordPress URL is provided */ -export function createDefaultGraphQLClient(wordpressUrl, headers = {}) { - if (!wordpressUrl) { +export function createDefaultGraphQLClient( + requestUrl, + headers = {}, + options = {}, +) { + if (!requestUrl) { throw new Error( - 'WordPress URL is required to create a default GraphQL client.', + 'Request URL is required to create a default GraphQL client.', ); } - const endpoint = buildGraphQLEndpoint(wordpressUrl); + const endpoint = options.useRawUrl + ? requestUrl + : buildGraphQLEndpoint(requestUrl); return { async request(query, variables = {}, requestHeaders = {}) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ca788a56a..fddea913e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -139,6 +139,9 @@ importers: graphql-tag: specifier: ^2.12.6 version: 2.12.6(graphql@16.11.0) + iron-session: + specifier: ^8.0.4 + version: 8.0.4 next: specifier: 15.2.4 version: 15.2.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1) @@ -333,7 +336,7 @@ importers: version: 16.11.0 next: specifier: ^13.0.0 || ^14.0.0 || ^15.0.0 - version: 14.2.31(react-dom@19.1.1(react@18.3.1))(react@18.3.1) + version: 14.2.31(react-dom@19.1.1(react@19.1.1))(react@19.1.1) react: specifier: ^18.0.0 || ^19.0.0 version: 19.1.1 @@ -2101,6 +2104,10 @@ packages: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + cookie@1.0.2: resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} engines: {node: '>=18'} @@ -2823,6 +2830,9 @@ packages: next: optional: true + iron-session@8.0.4: + resolution: {integrity: sha512-9ivNnaKOd08osD0lJ3i6If23GFS2LsxyMU8Gf/uBUEgm8/8CC1hrrCHFDpMo3IFbpBgwoo/eairRsaD3c5itxA==} + iron-webcrypto@0.2.8: resolution: {integrity: sha512-YPdCvjFMOBjXaYuDj5tiHst5CEk6Xw84Jo8Y2+jzhMceclAnb3+vNPP/CTtb5fO2ZEuXEaO4N+w62Vfko757KA==} @@ -6235,6 +6245,8 @@ snapshots: cookie@0.6.0: {} + cookie@0.7.2: {} + cookie@1.0.2: {} cross-fetch@3.2.0: @@ -6547,7 +6559,7 @@ snapshots: '@typescript-eslint/parser': 5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) eslint: 9.33.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.33.0(jiti@2.5.1)) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.33.0(jiti@2.5.1)) eslint-plugin-react: 7.37.5(eslint@9.33.0(jiti@2.5.1)) @@ -6606,7 +6618,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.33.0(jiti@2.5.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1 @@ -6632,14 +6644,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) eslint: 9.33.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.33.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color @@ -6683,7 +6695,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.33.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -7332,6 +7344,12 @@ snapshots: optionalDependencies: next: 15.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + iron-session@8.0.4: + dependencies: + cookie: 0.7.2 + iron-webcrypto: 1.2.1 + uncrypto: 0.1.3 + iron-webcrypto@0.2.8: dependencies: buffer: 6.0.3 @@ -8015,7 +8033,7 @@ snapshots: neotraverse@0.6.18: {} - next@14.2.31(react-dom@19.1.1(react@18.3.1))(react@18.3.1): + next@14.2.31(react-dom@19.1.1(react@19.1.1))(react@19.1.1): dependencies: '@next/env': 14.2.31 '@swc/helpers': 0.5.5 @@ -8023,9 +8041,9 @@ snapshots: caniuse-lite: 1.0.30001731 graceful-fs: 4.2.11 postcss: 8.4.31 - react: 18.3.1 - react-dom: 19.1.1(react@18.3.1) - styled-jsx: 5.1.1(react@18.3.1) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + styled-jsx: 5.1.1(react@19.1.1) optionalDependencies: '@next/swc-darwin-arm64': 14.2.31 '@next/swc-darwin-x64': 14.2.31 @@ -8346,11 +8364,6 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 - react-dom@19.1.1(react@18.3.1): - dependencies: - react: 18.3.1 - scheduler: 0.26.0 - react-dom@19.1.1(react@19.1.1): dependencies: react: 19.1.1 @@ -8836,10 +8849,10 @@ snapshots: strip-json-comments@3.1.1: {} - styled-jsx@5.1.1(react@18.3.1): + styled-jsx@5.1.1(react@19.1.1): dependencies: client-only: 0.0.1 - react: 18.3.1 + react: 19.1.1 styled-jsx@5.1.6(react@18.3.1): dependencies: From 5feafd4abb6fc0dcdcf9dafa59590e11f9699fbb Mon Sep 17 00:00:00 2001 From: ahuseyn Date: Tue, 23 Sep 2025 11:26:06 +0200 Subject: [PATCH 20/24] remove token handler --- packages/auth/handlers/AuthHandler.js | 42 +-------------------------- 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/packages/auth/handlers/AuthHandler.js b/packages/auth/handlers/AuthHandler.js index 1ae0c0a4c..9869e6e66 100644 --- a/packages/auth/handlers/AuthHandler.js +++ b/packages/auth/handlers/AuthHandler.js @@ -14,15 +14,7 @@ const defaultConfig = { missingPassword: 'Cookie password is not set. Please set it to a secure password of at least 32 characters.', }, - supportedActions: [ - 'login', - 'logout', - 'me', - 'introspect', - 'refresh', - 'token', - 'query', - ], + supportedActions: ['login', 'logout', 'me', 'introspect', 'refresh', 'query'], }; // Pure utility functions @@ -328,36 +320,6 @@ const authenticatedQueryHandler = async ({ } }; -// Token handler - provides the authToken, refreshing it if expired -const tokenHandler = async ({ - client, - ironOptions, - req, - res, - config = defaultConfig, -}) => { - const result = await getOrRefreshSession({ - req, - res, - client, - ironOptions, - config, - }); - - if (!result.success) { - return sendError(res, 401, config.errorMessages.notLoggedIn); - } - - // Get the token (either from existing session or newly refreshed) - const { authToken } = result.session; - const wasRefreshed = !!result.refreshRes; - - return sendSuccess(res, { - authToken, - expiresAt: wasRefreshed ? result.refreshRes.authTokenExpiration : null, - }); -}; - // Pure introspect handler - no side effects, just checks current auth state const introspectHandler = async ({ client, @@ -459,7 +421,6 @@ const actionHandlers = { me: meHandler, introspect: introspectHandler, refresh: refreshHandler, - token: tokenHandler, query: authenticatedQueryHandler, }; @@ -526,7 +487,6 @@ export { meHandler, introspectHandler, refreshHandler, - tokenHandler, authenticatedQueryHandler, // Utilities From efcbfb6b8d332453446c10e3dfda3ddccd78a8bb Mon Sep 17 00:00:00 2001 From: Huseyn Aghayev Date: Tue, 23 Sep 2025 14:40:22 +0200 Subject: [PATCH 21/24] Update examples/nextjs/kitchen-sink/src/components/Header.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- examples/nextjs/kitchen-sink/src/components/Header.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/nextjs/kitchen-sink/src/components/Header.js b/examples/nextjs/kitchen-sink/src/components/Header.js index 644999fb9..685d28150 100644 --- a/examples/nextjs/kitchen-sink/src/components/Header.js +++ b/examples/nextjs/kitchen-sink/src/components/Header.js @@ -43,7 +43,7 @@ export default function Header() {
-
- ); -} diff --git a/examples/nextjs/kitchen-sink/src/pages/[[...identifier]].js b/examples/nextjs/kitchen-sink/src/pages/[[...identifier]].js index 24f05c52d..8f3815de1 100644 --- a/examples/nextjs/kitchen-sink/src/pages/[[...identifier]].js +++ b/examples/nextjs/kitchen-sink/src/pages/[[...identifier]].js @@ -1,18 +1,57 @@ import { resolveWpRoute } from '@/lib/resolveWpRoute'; +import { Toolbar } from '@/toolbar'; import { getAuthString } from '@/utils/getAuthString'; import availableTemplates from '@/wp-templates'; -import { createDefaultClient, setGraphQLClient } from '@faustjs/nextjs/pages'; +import { + createDefaultClient, + setGraphQLClient, + useUser, +} from '@faustjs/nextjs/pages'; +import { useRouter } from 'next/router'; // This is a catch-all dynamic route to handle all WordPress pages and posts. // It uses getStaticProps and getStaticPaths for SSG with fallback blocking. // It also supports Draft Mode previews with application passwords. export default function Page(props) { - const { templateData } = props; + const router = useRouter(); + const { templateData, queriesData } = props; + const { user = {}, isAuthenticated } = useUser(); + + const { getPage, getPost } = queriesData || {}; + const result = getPage || getPost; + const content = result?.data?.page || result?.data?.post; const PageTemplate = availableTemplates[templateData?.template?.id]; - return ; + return ( + <> + {isAuthenticated && ( + + )} + + + ); } export async function getStaticProps({ diff --git a/examples/nextjs/kitchen-sink/src/pages/_app.js b/examples/nextjs/kitchen-sink/src/pages/_app.js index af8ade7e2..ac53fddec 100644 --- a/examples/nextjs/kitchen-sink/src/pages/_app.js +++ b/examples/nextjs/kitchen-sink/src/pages/_app.js @@ -1,5 +1,6 @@ import Layout from '@/components/Layout'; import '@/styles/globals.css'; +import '@/toolbar/default.css'; export default function App({ Component, pageProps }) { return ( diff --git a/examples/nextjs/kitchen-sink/src/queries/getPage.js b/examples/nextjs/kitchen-sink/src/queries/getPage.js index 76646f1a5..0091f0d7d 100644 --- a/examples/nextjs/kitchen-sink/src/queries/getPage.js +++ b/examples/nextjs/kitchen-sink/src/queries/getPage.js @@ -3,9 +3,13 @@ import gql from 'graphql-tag'; export const GET_PAGE = gql` query GetPage($databaseId: ID!, $asPreview: Boolean = false) { page(id: $databaseId, idType: DATABASE_ID, asPreview: $asPreview) { + databaseId title content date + slug + status + contentTypeName author { node { name diff --git a/examples/nextjs/kitchen-sink/src/queries/getPost.js b/examples/nextjs/kitchen-sink/src/queries/getPost.js index 30ba7b8d7..70e8e7674 100644 --- a/examples/nextjs/kitchen-sink/src/queries/getPost.js +++ b/examples/nextjs/kitchen-sink/src/queries/getPost.js @@ -3,9 +3,13 @@ import gql from 'graphql-tag'; export const GET_POST = gql` query GetPost($databaseId: ID!, $asPreview: Boolean = false) { post(id: $databaseId, idType: DATABASE_ID, asPreview: $asPreview) { + databaseId title content date + slug + status + contentTypeName author { node { name diff --git a/examples/nextjs/kitchen-sink/src/toolbar/Toolbar.jsx b/examples/nextjs/kitchen-sink/src/toolbar/Toolbar.jsx new file mode 100644 index 000000000..f7cf4bfc3 --- /dev/null +++ b/examples/nextjs/kitchen-sink/src/toolbar/Toolbar.jsx @@ -0,0 +1,87 @@ +import { useToolbar } from '@wpengine/hwp-toolbar/react'; +import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react'; +import { createToolbar } from './createToolbar'; +import { registerDefaultNodes } from './registerNodes'; +import { renderNode } from './renderNode'; + +export const Toolbar = forwardRef(function Toolbar({ user, post, site, config = {}, onToolbarReady, isPreview, disablePreviewUrl }, ref) { + const toolbar = useMemo(() => createToolbar(config), []); + const { nodes } = useToolbar(toolbar); + const [position, setPosition] = useState(config.position || 'bottom'); + const [activePanel, setActivePanel] = useState(null); + + // Expose toolbar instance to parent components + useImperativeHandle(ref, () => ({ + toolbar, + register: (id, nodeConfig) => toolbar.register(id, nodeConfig), + unregister: (id) => toolbar.unregister(id), + getNode: (id) => toolbar.getNode(id), + update: (id, updates) => toolbar.update(id, updates), + setConfig: (config) => toolbar.setConfig(config), + getConfig: () => toolbar.getConfig() + }), [toolbar]); + + // Notify parent when toolbar is ready + useEffect(() => { + if (onToolbarReady && typeof onToolbarReady === 'function') { + onToolbarReady(toolbar); + } + }, [toolbar, onToolbarReady]); + + useEffect(() => { + if (user || post || site) { + toolbar.setWordPressContext({ user, site, post }); + } + }, [toolbar, user, post, site]); + + useEffect(() => { + const currentPosition = toolbar.getConfig()?.position || 'top'; + setPosition(currentPosition); + + const cleanup = registerDefaultNodes(toolbar, { position, setPosition, user, site, isPreview, disablePreviewUrl }); + return cleanup; + }, [toolbar, position, user, site]); + + useEffect(() => { + const currentPosition = toolbar.getConfig()?.position || 'bottom'; + document.body.classList.add(`faust-toolbar-has-toolbar-${currentPosition}`); + + const unsubscribe = toolbar.subscribe(() => { + const config = toolbar.getConfig(); + const newPosition = config?.position || 'bottom'; + if (currentPosition !== newPosition) { + document.body.classList.remove(`faust-toolbar-has-toolbar-${currentPosition}`); + document.body.classList.add(`faust-toolbar-has-toolbar-${newPosition}`); + } + }); + + return () => { + unsubscribe(); + document.body.classList.remove(`faust-toolbar-has-toolbar-${currentPosition}`); + }; + }, [toolbar]); + + return ( +
+
+
+
+ {nodes.filter(node => node.position !== 'right').map(node => renderNode(node, { activePanel, setActivePanel }))} +
+
+ {nodes.filter(node => node.position === 'right').map(node => renderNode(node, { activePanel, setActivePanel }))} +
+
+
+ + {activePanel && ( +
+ {(() => { + const activeNode = nodes.find(node => node.panel === activePanel); + return activeNode?.panelComponent ||
No content
; + })()} +
+ )} +
+ ); +}); diff --git a/examples/nextjs/kitchen-sink/src/toolbar/assets/faust-logo.svg b/examples/nextjs/kitchen-sink/src/toolbar/assets/faust-logo.svg new file mode 100644 index 000000000..ac1e4c102 --- /dev/null +++ b/examples/nextjs/kitchen-sink/src/toolbar/assets/faust-logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/examples/nextjs/kitchen-sink/src/toolbar/assets/icons/edit.svg b/examples/nextjs/kitchen-sink/src/toolbar/assets/icons/edit.svg new file mode 100644 index 000000000..86e69b263 --- /dev/null +++ b/examples/nextjs/kitchen-sink/src/toolbar/assets/icons/edit.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/nextjs/kitchen-sink/src/toolbar/assets/icons/eye-off.svg b/examples/nextjs/kitchen-sink/src/toolbar/assets/icons/eye-off.svg new file mode 100644 index 000000000..bf0df607b --- /dev/null +++ b/examples/nextjs/kitchen-sink/src/toolbar/assets/icons/eye-off.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/nextjs/kitchen-sink/src/toolbar/assets/icons/eye.svg b/examples/nextjs/kitchen-sink/src/toolbar/assets/icons/eye.svg new file mode 100644 index 000000000..3b43a992f --- /dev/null +++ b/examples/nextjs/kitchen-sink/src/toolbar/assets/icons/eye.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/nextjs/kitchen-sink/src/toolbar/assets/icons/settings.svg b/examples/nextjs/kitchen-sink/src/toolbar/assets/icons/settings.svg new file mode 100644 index 000000000..6274b15be --- /dev/null +++ b/examples/nextjs/kitchen-sink/src/toolbar/assets/icons/settings.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/nextjs/kitchen-sink/src/toolbar/assets/icons/wordpress.svg b/examples/nextjs/kitchen-sink/src/toolbar/assets/icons/wordpress.svg new file mode 100644 index 000000000..a0f2b691f --- /dev/null +++ b/examples/nextjs/kitchen-sink/src/toolbar/assets/icons/wordpress.svg @@ -0,0 +1,3 @@ + + + diff --git a/examples/nextjs/kitchen-sink/src/toolbar/createToolbar.js b/examples/nextjs/kitchen-sink/src/toolbar/createToolbar.js new file mode 100644 index 000000000..60e6cb8f9 --- /dev/null +++ b/examples/nextjs/kitchen-sink/src/toolbar/createToolbar.js @@ -0,0 +1,21 @@ +import { Toolbar as BaseToolbar } from '@wpengine/hwp-toolbar'; + +/** + * Creates and configures the toolbar instance + */ +export function createToolbar(config = {}) { + return new BaseToolbar({ + position: 'top', + onPreviewChange: (enabled) => { + // Emit event for Next.js integration + if (typeof window !== 'undefined') { + window.dispatchEvent( + new CustomEvent('faust:preview-change', { + detail: { enabled }, + }), + ); + } + }, + ...config, + }); +} diff --git a/examples/nextjs/kitchen-sink/src/toolbar/default.css b/examples/nextjs/kitchen-sink/src/toolbar/default.css new file mode 100644 index 000000000..6a83d2ce3 --- /dev/null +++ b/examples/nextjs/kitchen-sink/src/toolbar/default.css @@ -0,0 +1,158 @@ +/* CSS Custom Properties (Variables) */ +:root { + --faust-toolbar-text-color: #ffffff; + --faust-toolbar-border-color: #555962; + --faust-toolbar-bg-primary: #23262F; + --faust-toolbar-btn-hover: #383b43; +} + +/* Body spacing when toolbar is active */ +body.faust-toolbar-has-toolbar-bottom { + margin-bottom: 36px; +} + +body.faust-toolbar-has-toolbar-top { + margin-top: 36px; +} + +/* Faust toolbar button styles */ +.faust-toolbar-btn { + display: flex; + align-items: center; + padding: 0 12px; + height: 36px; + background: none; + border: none; + cursor: pointer; + font-size: 14px; + color: var(--faust-toolbar-text-color); + transition: background-color 0.2s ease; +} + +.faust-toolbar-btn:hover { + background-color: var(--faust-toolbar-btn-hover); +} + +.faust-toolbar-btn-right { + border-left: 1px solid var(--faust-toolbar-border-color); +} + +.faust-toolbar-btn:not(.faust-toolbar-btn-right) { + border-right: 1px solid var(--faust-toolbar-border-color); +} + +.faust-toolbar-btn-icon { + width: 16px; + height: 16px; +} + +.faust-toolbar-btn-icon-faust { + width: 22px; + height: 22px; +} + +.faust-toolbar-btn-label { + margin-left: 6px; +} + +/* Settings panel styles */ +.faust-toolbar-settings-panel { + padding: 15px; + min-width: 200px; +} + +.faust-toolbar-settings-row { + display: flex; + align-items: center; + gap: 10px; + color: var(--faust-toolbar-text-color); +} + +.faust-toolbar-settings-label { + font-weight: 500; + min-width: 80px; +} + +.faust-toolbar-settings-select { + padding: 6px 8px; + border: 1px solid var(--faust-toolbar-border-color); + border-radius: 4px; + background: var(--faust-toolbar-bg-primary); + font-size: 14px; + +} + +/* User link styles */ +.faust-toolbar-user-link { + display: flex; + align-items: center; + padding: 0 12px; + height: 36px; + text-decoration: none; + color: var(--faust-toolbar-text-color); + font-size: 14px; +} + +.faust-toolbar-user-avatar { + width: 20px; + height: 20px; + border-radius: 50%; + margin-right: 6px; +} + +/* Main toolbar container */ +.faust-toolbar { + position: fixed; + left: 0; + right: 0; + z-index: 9999; + background: var(--faust-toolbar-bg-primary); + font-size: 14px; +} + +.faust-toolbar-top { + top: 0; +} + +.faust-toolbar-bottom { + bottom: 0; +} + +/* Toolbar header */ +.faust-toolbar-header { + display: flex; + justify-content: space-between; + align-items: center; + height: 36px; + background: var(--faust-toolbar-bg-primary); + border-bottom: 1px solid var(--faust-toolbar-border-color); +} + +.faust-toolbar-content { + display: flex; + align-items: center; + width: 100%; + justify-content: space-between; +} + +.faust-toolbar-left { + display: flex; + align-items: center; +} + +.faust-toolbar-right { + display: flex; + align-items: center; +} + +/* Panel container */ +.faust-toolbar-panel-container { + background: var(--faust-toolbar-bg-primary); + border-bottom: 1px solid var(--faust-toolbar-border-color); + max-height: 300px; + overflow-y: auto; +} + +.faust-toolbar-no-content { + padding: 15px; +} diff --git a/examples/nextjs/kitchen-sink/src/toolbar/index.js b/examples/nextjs/kitchen-sink/src/toolbar/index.js new file mode 100644 index 000000000..8a2a32015 --- /dev/null +++ b/examples/nextjs/kitchen-sink/src/toolbar/index.js @@ -0,0 +1,9 @@ +// Main toolbar component +export { Toolbar } from './Toolbar'; + +// Utilities +export { createToolbar } from './createToolbar'; +export { registerDefaultNodes } from './registerNodes'; +export { renderNode } from './renderNode'; + + diff --git a/examples/nextjs/kitchen-sink/src/toolbar/registerNodes.js b/examples/nextjs/kitchen-sink/src/toolbar/registerNodes.js new file mode 100644 index 000000000..0a40e055d --- /dev/null +++ b/examples/nextjs/kitchen-sink/src/toolbar/registerNodes.js @@ -0,0 +1,82 @@ +import React from 'react'; +import FaustLogo from './assets/faust-logo.svg'; +import SettingsIcon from './assets/icons/settings.svg'; +import EyeOffIcon from './assets/icons/eye-off.svg'; + +/** + * Registers default Faust.js nodes to the toolbar + */ +export function registerDefaultNodes(toolbar, { position, setPosition, user, site, isPreview, disablePreviewUrl }) { + // Unregister default preview node + toolbar.unregister('preview'); + + if (isPreview) { + toolbar.register('preview', { + label:"Exit preview mode", + icon: EyeOffIcon.src, + order: 3, + onClick: ()=> { + disablePreviewUrl && (window.location.href = disablePreviewUrl); + }, + }); + } + + // Register Faust branding + toolbar.register('faust-brand', { + icon: {"Faust.js"}, + order: 0, + onClick: ()=> { + window.open("https://faustjs.org" , "_blank") + }, + }); + + // Register settings panel + toolbar.register('settings', { + icon: SettingsIcon.src, + panel: 'settings', + position: 'right', + order: 99, + panelComponent: ( +
+
+ + +
+
+ ) + }); + + // Register WordPress user node if available + if (user) { + toolbar.register('wp-user', { + label: user.name, + icon: user.avatar, + position: 'right', + order: 98, + onClick: ()=> { + window.open(`${site?.adminUrl}/profile.php`, "_blank") + } + }); + } + + // Return cleanup function + return () => { + toolbar.unregister('faust-brand'); + toolbar.unregister('settings'); + if (user) toolbar.unregister('wp-user'); + }; +} diff --git a/examples/nextjs/kitchen-sink/src/toolbar/renderNode.js b/examples/nextjs/kitchen-sink/src/toolbar/renderNode.js new file mode 100644 index 000000000..e75a4e867 --- /dev/null +++ b/examples/nextjs/kitchen-sink/src/toolbar/renderNode.js @@ -0,0 +1,59 @@ +import React from 'react'; + +/** + * Renders a toolbar node with proper handling for components and default buttons + */ +export function renderNode(node, { activePanel, setActivePanel }) { + // Generate className for consistent styling across all node types + const nodeClassName = `faust-toolbar-btn ${ + node.position ? `faust-toolbar-btn-${node.position}` : '' + }`; + + if (node.component) { + // If it's a React component, render it + if (React.isValidElement(node.component)) { + return React.cloneElement(node.component, { + key: node.id, + className: nodeClassName, + }); + } + const Component = node.component; + return ; + } + + // Default tab rendering + return ( + + ); +} diff --git a/packages/auth/handlers/AuthHandler.js b/packages/auth/handlers/AuthHandler.js index a1011f496..8b45c7cfc 100644 --- a/packages/auth/handlers/AuthHandler.js +++ b/packages/auth/handlers/AuthHandler.js @@ -250,6 +250,9 @@ const meHandler = async ({ firstName lastName username + avatar { + url + } } }`; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fddea913e..26c141150 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -130,9 +130,12 @@ importers: '@faustjs/nextjs': specifier: workspace:* version: link:../../../packages/nextjs - '@faustjs/template-hierarchy': - specifier: workspace:* - version: link:../../../packages/template-hierarchy + '@wpengine/faust-next-toolbar': + specifier: file:/../../../packages/faust-next-toolbar + version: file:packages/faust-next-toolbar(@wpengine/hwp-toolkit@https://codeload.github.com/wpengine/hwptoolkit/tar.gz/085b4399fc62abb7707b12116fd3c89c6da593ea)(esbuild@0.25.8)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@wpengine/hwp-toolbar': + specifier: github:wpengine/hwptoolkit#toolbar + version: '@wpengine/hwp-toolkit@https://codeload.github.com/wpengine/hwptoolkit/tar.gz/085b4399fc62abb7707b12116fd3c89c6da593ea' graphql: specifier: ^16.11.0 version: 16.11.0 @@ -307,7 +310,7 @@ importers: dependencies: iron-session: specifier: ^6.3.1 - version: 6.3.1(next@15.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)) + version: 6.3.1(next@15.4.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0)) jsonwebtoken: specifier: ^9.0.0 version: 9.0.2 @@ -321,6 +324,31 @@ importers: specifier: ^2.12.0 version: 2.12.6(graphql@16.11.0) + packages/faust-next-toolbar: + dependencies: + '@wpengine/hwp-toolbar': + specifier: github:wpengine/hwptoolkit + version: '@wpengine/hwp-toolkit@https://codeload.github.com/wpengine/hwptoolkit/tar.gz/35cd7edb8522a899bf90154db315f3fa78dde545' + esbuild-node-externals: + specifier: ^1.18.0 + version: 1.18.0(esbuild@0.24.2) + esbuild-plugin-glob: + specifier: ^2.2.3 + version: 2.2.3(esbuild@0.24.2) + react: + specifier: ^19.0.0 + version: 19.2.0 + react-dom: + specifier: ^19.0.0 + version: 19.2.0(react@19.2.0) + devDependencies: + esbuild: + specifier: ^0.24.0 + version: 0.24.2 + eslint: + specifier: ^9.37.0 + version: 9.37.0(jiti@2.5.1) + packages/graphql: {} packages/nextjs: @@ -336,7 +364,7 @@ importers: version: 16.11.0 next: specifier: ^13.0.0 || ^14.0.0 || ^15.0.0 - version: 14.2.31(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 14.2.31(react-dom@19.2.0(react@19.1.1))(react@19.1.1) react: specifier: ^18.0.0 || ^19.0.0 version: 19.1.1 @@ -424,6 +452,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.28.4': + resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/runtime@7.27.0': resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==} engines: {node: '>=6.9.0'} @@ -436,6 +469,10 @@ packages: resolution: {integrity: sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==} engines: {node: '>=6.9.0'} + '@babel/types@7.28.4': + resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} + engines: {node: '>=6.9.0'} + '@capsizecss/unpack@2.4.0': resolution: {integrity: sha512-GrSU71meACqcmIUxPYOJvGKF0yryjN/L1aCuE9DViCTJI7bfkjgYDPD1zbNDcINJwSSP6UaBZY9GAbYDO7re0Q==} @@ -503,126 +540,252 @@ packages: '@emnapi/wasi-threads@1.0.4': resolution: {integrity: sha512-PJR+bOmMOPH8AtcTGAyYNiuJ3/Fcoj2XN/gBEWzDIKh254XO+mM9XoXHk5GNEhodxeMznbg7BlRojVbKN+gC6g==} + '@esbuild/aix-ppc64@0.24.2': + resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.25.8': resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.24.2': + resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.25.8': resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm@0.24.2': + resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.25.8': resolution: {integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-x64@0.24.2': + resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.25.8': resolution: {integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.24.2': + resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.25.8': resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.24.2': + resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.25.8': resolution: {integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.24.2': + resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.25.8': resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.24.2': + resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.8': resolution: {integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.24.2': + resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.25.8': resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.24.2': + resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.25.8': resolution: {integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.24.2': + resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.25.8': resolution: {integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.24.2': + resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.25.8': resolution: {integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.24.2': + resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.25.8': resolution: {integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.24.2': + resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.25.8': resolution: {integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.24.2': + resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.25.8': resolution: {integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.24.2': + resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.25.8': resolution: {integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.24.2': + resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.25.8': resolution: {integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/netbsd-arm64@0.24.2': + resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-arm64@0.25.8': resolution: {integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-x64@0.24.2': + resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.8': resolution: {integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/openbsd-arm64@0.24.2': + resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-arm64@0.25.8': resolution: {integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-x64@0.24.2': + resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.8': resolution: {integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==} engines: {node: '>=18'} @@ -635,24 +798,48 @@ packages: cpu: [arm64] os: [openharmony] + '@esbuild/sunos-x64@0.24.2': + resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.25.8': resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.24.2': + resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.25.8': resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.24.2': + resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.25.8': resolution: {integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.24.2': + resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.25.8': resolution: {integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==} engines: {node: '>=18'} @@ -665,6 +852,12 @@ packages: peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + '@eslint-community/regexpp@4.12.1': resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} @@ -677,10 +870,18 @@ packages: resolution: {integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-helpers@0.4.0': + resolution: {integrity: sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@0.15.2': resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@0.16.0': + resolution: {integrity: sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/eslintrc@2.1.4': resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -697,6 +898,10 @@ packages: resolution: {integrity: sha512-5K1/mKhWaMfreBGJTwval43JJmkip0RmM+3+IuqupeSKNC/Th2Kc7ucaq5ovTSra/OOKB9c58CGSz3QMVbWt0A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@9.37.0': + resolution: {integrity: sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/object-schema@2.1.6': resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -705,6 +910,10 @@ packages: resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/plugin-kit@0.4.0': + resolution: {integrity: sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} engines: {node: '>=18.18.0'} @@ -1817,6 +2026,23 @@ packages: peerDependencies: '@urql/core': ^5.0.0 + '@wpengine/faust-next-toolbar@file:packages/faust-next-toolbar': + resolution: {directory: packages/faust-next-toolbar, type: directory} + peerDependencies: + '@wpengine/hwp-toolbar': 0.0.1 + react: ^19.0.0 + react-dom: ^19.0.0 + + '@wpengine/hwp-toolkit@https://codeload.github.com/wpengine/hwptoolkit/tar.gz/085b4399fc62abb7707b12116fd3c89c6da593ea': + resolution: {tarball: https://codeload.github.com/wpengine/hwptoolkit/tar.gz/085b4399fc62abb7707b12116fd3c89c6da593ea} + version: 1.0.0 + engines: {node: '>=18', pnpm: '>=10'} + + '@wpengine/hwp-toolkit@https://codeload.github.com/wpengine/hwptoolkit/tar.gz/35cd7edb8522a899bf90154db315f3fa78dde545': + resolution: {tarball: https://codeload.github.com/wpengine/hwptoolkit/tar.gz/35cd7edb8522a899bf90154db315f3fa78dde545} + version: 1.0.0 + engines: {node: '>=18', pnpm: '>=10'} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -1959,6 +2185,10 @@ packages: resolution: {integrity: sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==} engines: {node: '>=4'} + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + blob-to-buffer@1.2.9: resolution: {integrity: sha512-BF033y5fN6OCofD3vgHmNtwZWRcq9NLyyxyILx9hfMy1sXYy4ojFl765hJ2lP0YaN2fuxPaLO2Vzzoxy0FLFFA==} @@ -1969,6 +2199,9 @@ packages: brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -2032,6 +2265,10 @@ packages: chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -2301,6 +2538,24 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} + esbuild-node-externals@1.18.0: + resolution: {integrity: sha512-suFVX3SzZlXrGIS9Yqx+ZaHL4w1p0e/j7dQbOM9zk8SfFpnAGnDplHUKXIf9kcPEAfZRL66JuYeVSVlsSEQ5Eg==} + engines: {node: '>=12'} + peerDependencies: + esbuild: 0.12 - 0.25 + + esbuild-plugin-glob@2.2.3: + resolution: {integrity: sha512-Ee6clR2o8K1BIz94hyfRlHB7//UOeAT5JUPfaCSi1Sqx/Y3GDV2BcO0AOvnbxtZWVL8fHfG9WB92FWzwBZwy4Q==} + engines: {node: '>=14'} + deprecated: Esbuild natively supports glob-style imports since version 0.19 + peerDependencies: + esbuild: ^0.x.x + + esbuild@0.24.2: + resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} + engines: {node: '>=18'} + hasBin: true + esbuild@0.25.8: resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==} engines: {node: '>=18'} @@ -2475,6 +2730,16 @@ packages: jiti: optional: true + eslint@9.37.0: + resolution: {integrity: sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + esm-env@1.2.2: resolution: {integrity: sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==} @@ -2854,6 +3119,10 @@ packages: resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} engines: {node: '>= 0.4'} + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + is-boolean-object@1.2.2: resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} engines: {node: '>= 0.4'} @@ -3320,6 +3589,10 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -3449,6 +3722,10 @@ packages: node-mock-http@1.0.2: resolution: {integrity: sha512-zWaamgDUdo9SSLw47we78+zYw/bDr5gH8pH7oRRs8V3KmBtu8GLgGIbV2p/gRPd3LWpEOpjQj7X1FOU3VFMJ8g==} + normalize-path@2.1.1: + resolution: {integrity: sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==} + engines: {node: '>=0.10.0'} + normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -3684,6 +3961,11 @@ packages: peerDependencies: react: ^19.1.1 + react-dom@19.2.0: + resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==} + peerDependencies: + react: ^19.2.0 + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -3699,10 +3981,18 @@ packages: resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==} engines: {node: '>=0.10.0'} + react@19.2.0: + resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} + engines: {node: '>=0.10.0'} + read-yaml-file@1.1.0: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -3755,6 +4045,9 @@ packages: remark-stringify@11.0.0: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + remove-trailing-separator@1.1.0: + resolution: {integrity: sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -3835,6 +4128,9 @@ packages: scheduler@0.26.0: resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -4060,6 +4356,9 @@ packages: tiny-inflate@1.0.3: resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tinyexec@0.3.2: resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} @@ -4206,6 +4505,10 @@ packages: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} + unixify@1.0.0: + resolution: {integrity: sha512-6bc58dPYhCMHHuwxldQxO3RRNZ4eCogZ/st++0+fcC1nr0jiGUtAdBJ2qzmLQWSxbtz42pWt4QQMiZ9HvZf5cg==} + engines: {node: '>=0.10.0'} + unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} @@ -4530,6 +4833,10 @@ snapshots: dependencies: '@babel/types': 7.27.1 + '@babel/parser@7.28.4': + dependencies: + '@babel/types': 7.28.4 + '@babel/runtime@7.27.0': dependencies: regenerator-runtime: 0.14.1 @@ -4537,14 +4844,19 @@ snapshots: '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 - '@babel/parser': 7.27.2 - '@babel/types': 7.27.1 + '@babel/parser': 7.28.4 + '@babel/types': 7.28.4 '@babel/types@7.27.1': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.27.1 + '@babel/types@7.28.4': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@capsizecss/unpack@2.4.0': dependencies: blob-to-buffer: 1.2.9 @@ -4711,81 +5023,156 @@ snapshots: tslib: 2.8.1 optional: true + '@esbuild/aix-ppc64@0.24.2': + optional: true + '@esbuild/aix-ppc64@0.25.8': optional: true + '@esbuild/android-arm64@0.24.2': + optional: true + '@esbuild/android-arm64@0.25.8': optional: true + '@esbuild/android-arm@0.24.2': + optional: true + '@esbuild/android-arm@0.25.8': optional: true + '@esbuild/android-x64@0.24.2': + optional: true + '@esbuild/android-x64@0.25.8': optional: true + '@esbuild/darwin-arm64@0.24.2': + optional: true + '@esbuild/darwin-arm64@0.25.8': optional: true + '@esbuild/darwin-x64@0.24.2': + optional: true + '@esbuild/darwin-x64@0.25.8': optional: true + '@esbuild/freebsd-arm64@0.24.2': + optional: true + '@esbuild/freebsd-arm64@0.25.8': optional: true + '@esbuild/freebsd-x64@0.24.2': + optional: true + '@esbuild/freebsd-x64@0.25.8': optional: true + '@esbuild/linux-arm64@0.24.2': + optional: true + '@esbuild/linux-arm64@0.25.8': optional: true + '@esbuild/linux-arm@0.24.2': + optional: true + '@esbuild/linux-arm@0.25.8': optional: true + '@esbuild/linux-ia32@0.24.2': + optional: true + '@esbuild/linux-ia32@0.25.8': optional: true + '@esbuild/linux-loong64@0.24.2': + optional: true + '@esbuild/linux-loong64@0.25.8': optional: true + '@esbuild/linux-mips64el@0.24.2': + optional: true + '@esbuild/linux-mips64el@0.25.8': optional: true + '@esbuild/linux-ppc64@0.24.2': + optional: true + '@esbuild/linux-ppc64@0.25.8': optional: true + '@esbuild/linux-riscv64@0.24.2': + optional: true + '@esbuild/linux-riscv64@0.25.8': optional: true + '@esbuild/linux-s390x@0.24.2': + optional: true + '@esbuild/linux-s390x@0.25.8': optional: true + '@esbuild/linux-x64@0.24.2': + optional: true + '@esbuild/linux-x64@0.25.8': optional: true + '@esbuild/netbsd-arm64@0.24.2': + optional: true + '@esbuild/netbsd-arm64@0.25.8': optional: true + '@esbuild/netbsd-x64@0.24.2': + optional: true + '@esbuild/netbsd-x64@0.25.8': optional: true + '@esbuild/openbsd-arm64@0.24.2': + optional: true + '@esbuild/openbsd-arm64@0.25.8': optional: true + '@esbuild/openbsd-x64@0.24.2': + optional: true + '@esbuild/openbsd-x64@0.25.8': optional: true '@esbuild/openharmony-arm64@0.25.8': optional: true + '@esbuild/sunos-x64@0.24.2': + optional: true + '@esbuild/sunos-x64@0.25.8': optional: true + '@esbuild/win32-arm64@0.24.2': + optional: true + '@esbuild/win32-arm64@0.25.8': optional: true + '@esbuild/win32-ia32@0.24.2': + optional: true + '@esbuild/win32-ia32@0.25.8': optional: true + '@esbuild/win32-x64@0.24.2': + optional: true + '@esbuild/win32-x64@0.25.8': optional: true @@ -4799,6 +5186,11 @@ snapshots: eslint: 9.33.0(jiti@2.5.1) eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.9.0(eslint@9.37.0(jiti@2.5.1))': + dependencies: + eslint: 9.37.0(jiti@2.5.1) + eslint-visitor-keys: 3.4.3 + '@eslint-community/regexpp@4.12.1': {} '@eslint/config-array@0.21.0': @@ -4811,10 +5203,18 @@ snapshots: '@eslint/config-helpers@0.3.1': {} + '@eslint/config-helpers@0.4.0': + dependencies: + '@eslint/core': 0.16.0 + '@eslint/core@0.15.2': dependencies: '@types/json-schema': 7.0.15 + '@eslint/core@0.16.0': + dependencies: + '@types/json-schema': 7.0.15 + '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 @@ -4847,6 +5247,8 @@ snapshots: '@eslint/js@9.33.0': {} + '@eslint/js@9.37.0': {} + '@eslint/object-schema@2.1.6': {} '@eslint/plugin-kit@0.3.5': @@ -4854,6 +5256,11 @@ snapshots: '@eslint/core': 0.15.2 levn: 0.4.1 + '@eslint/plugin-kit@0.4.0': + dependencies: + '@eslint/core': 0.16.0 + levn: 0.4.1 + '@humanfs/core@0.19.1': {} '@humanfs/node@0.16.6': @@ -5864,6 +6271,20 @@ snapshots: '@urql/core': 5.2.0(graphql@16.11.0) wonka: 6.3.5 + '@wpengine/faust-next-toolbar@file:packages/faust-next-toolbar(@wpengine/hwp-toolkit@https://codeload.github.com/wpengine/hwptoolkit/tar.gz/085b4399fc62abb7707b12116fd3c89c6da593ea)(esbuild@0.25.8)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + dependencies: + '@wpengine/hwp-toolbar': '@wpengine/hwp-toolkit@https://codeload.github.com/wpengine/hwptoolkit/tar.gz/085b4399fc62abb7707b12116fd3c89c6da593ea' + esbuild-node-externals: 1.18.0(esbuild@0.25.8) + esbuild-plugin-glob: 2.2.3(esbuild@0.25.8) + react: 19.1.1 + react-dom: 19.1.1(react@19.1.1) + transitivePeerDependencies: + - esbuild + + '@wpengine/hwp-toolkit@https://codeload.github.com/wpengine/hwptoolkit/tar.gz/085b4399fc62abb7707b12116fd3c89c6da593ea': {} + + '@wpengine/hwp-toolkit@https://codeload.github.com/wpengine/hwptoolkit/tar.gz/35cd7edb8522a899bf90154db315f3fa78dde545': {} + acorn-jsx@5.3.2(acorn@8.14.1): dependencies: acorn: 8.14.1 @@ -6116,6 +6537,8 @@ snapshots: dependencies: is-windows: 1.0.2 + binary-extensions@2.3.0: {} + blob-to-buffer@1.2.9: {} boxen@8.0.1: @@ -6134,6 +6557,10 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -6193,6 +6620,18 @@ snapshots: chardet@0.7.0: {} + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + chokidar@4.0.3: dependencies: readdirp: 4.1.2 @@ -6490,6 +6929,62 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 + esbuild-node-externals@1.18.0(esbuild@0.24.2): + dependencies: + esbuild: 0.24.2 + find-up: 5.0.0 + + esbuild-node-externals@1.18.0(esbuild@0.25.8): + dependencies: + esbuild: 0.25.8 + find-up: 5.0.0 + + esbuild-plugin-glob@2.2.3(esbuild@0.24.2): + dependencies: + chokidar: 3.6.0 + esbuild: 0.24.2 + fast-glob: 3.3.3 + minimatch: 9.0.5 + tiny-invariant: 1.3.3 + unixify: 1.0.0 + + esbuild-plugin-glob@2.2.3(esbuild@0.25.8): + dependencies: + chokidar: 3.6.0 + esbuild: 0.25.8 + fast-glob: 3.3.3 + minimatch: 9.0.5 + tiny-invariant: 1.3.3 + unixify: 1.0.0 + + esbuild@0.24.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.24.2 + '@esbuild/android-arm': 0.24.2 + '@esbuild/android-arm64': 0.24.2 + '@esbuild/android-x64': 0.24.2 + '@esbuild/darwin-arm64': 0.24.2 + '@esbuild/darwin-x64': 0.24.2 + '@esbuild/freebsd-arm64': 0.24.2 + '@esbuild/freebsd-x64': 0.24.2 + '@esbuild/linux-arm': 0.24.2 + '@esbuild/linux-arm64': 0.24.2 + '@esbuild/linux-ia32': 0.24.2 + '@esbuild/linux-loong64': 0.24.2 + '@esbuild/linux-mips64el': 0.24.2 + '@esbuild/linux-ppc64': 0.24.2 + '@esbuild/linux-riscv64': 0.24.2 + '@esbuild/linux-s390x': 0.24.2 + '@esbuild/linux-x64': 0.24.2 + '@esbuild/netbsd-arm64': 0.24.2 + '@esbuild/netbsd-x64': 0.24.2 + '@esbuild/openbsd-arm64': 0.24.2 + '@esbuild/openbsd-x64': 0.24.2 + '@esbuild/sunos-x64': 0.24.2 + '@esbuild/win32-arm64': 0.24.2 + '@esbuild/win32-ia32': 0.24.2 + '@esbuild/win32-x64': 0.24.2 + esbuild@0.25.8: optionalDependencies: '@esbuild/aix-ppc64': 0.25.8 @@ -6559,7 +7054,7 @@ snapshots: '@typescript-eslint/parser': 5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) eslint: 9.33.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.33.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.33.0(jiti@2.5.1)) eslint-plugin-react: 7.37.5(eslint@9.33.0(jiti@2.5.1)) @@ -6618,7 +7113,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@9.33.0(jiti@2.5.1)): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.1 @@ -6644,14 +7139,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)): + eslint-module-utils@2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3) eslint: 9.33.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@9.33.0(jiti@2.5.1)) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)) transitivePeerDependencies: - supports-color @@ -6695,7 +7190,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.33.0(jiti@2.5.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.33.0(jiti@2.5.1)) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0(@typescript-eslint/parser@5.62.0(eslint@9.33.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)))(eslint@9.33.0(jiti@2.5.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -6915,6 +7410,48 @@ snapshots: transitivePeerDependencies: - supports-color + eslint@9.37.0(jiti@2.5.1): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0(jiti@2.5.1)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.21.0 + '@eslint/config-helpers': 0.4.0 + '@eslint/core': 0.16.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.37.0 + '@eslint/plugin-kit': 0.4.0 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.1 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.5.1 + transitivePeerDependencies: + - supports-color + esm-env@1.2.2: {} espree@10.4.0: @@ -7332,7 +7869,7 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 - iron-session@6.3.1(next@15.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)): + iron-session@6.3.1(next@15.4.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0)): dependencies: '@peculiar/webcrypto': 1.5.0 '@types/cookie': 0.5.4 @@ -7342,7 +7879,7 @@ snapshots: cookie: 0.5.0 iron-webcrypto: 0.2.8 optionalDependencies: - next: 15.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + next: 15.4.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0) iron-session@8.0.4: dependencies: @@ -7377,6 +7914,10 @@ snapshots: dependencies: has-bigints: 1.1.0 + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + is-boolean-object@1.2.2: dependencies: call-bound: 1.0.4 @@ -8005,6 +8546,10 @@ snapshots: dependencies: brace-expansion: 1.1.11 + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + minimist@1.2.8: {} minipass@7.1.2: {} @@ -8033,7 +8578,7 @@ snapshots: neotraverse@0.6.18: {} - next@14.2.31(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + next@14.2.31(react-dom@19.2.0(react@19.1.1))(react@19.1.1): dependencies: '@next/env': 14.2.31 '@swc/helpers': 0.5.5 @@ -8042,7 +8587,7 @@ snapshots: graceful-fs: 4.2.11 postcss: 8.4.31 react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react-dom: 19.2.0(react@19.1.1) styled-jsx: 5.1.1(react@19.1.1) optionalDependencies: '@next/swc-darwin-arm64': 14.2.31 @@ -8106,15 +8651,15 @@ snapshots: - '@babel/core' - babel-plugin-macros - next@15.4.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + next@15.4.5(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: '@next/env': 15.4.5 '@swc/helpers': 0.5.15 caniuse-lite: 1.0.30001731 postcss: 8.4.31 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - styled-jsx: 5.1.6(react@19.1.1) + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + styled-jsx: 5.1.6(react@19.2.0) optionalDependencies: '@next/swc-darwin-arm64': 15.4.5 '@next/swc-darwin-x64': 15.4.5 @@ -8142,6 +8687,10 @@ snapshots: node-mock-http@1.0.2: {} + normalize-path@2.1.1: + dependencies: + remove-trailing-separator: 1.1.0 + normalize-path@3.0.0: {} object-assign@4.1.1: {} @@ -8369,6 +8918,16 @@ snapshots: react: 19.1.1 scheduler: 0.26.0 + react-dom@19.2.0(react@19.1.1): + dependencies: + react: 19.1.1 + scheduler: 0.27.0 + + react-dom@19.2.0(react@19.2.0): + dependencies: + react: 19.2.0 + scheduler: 0.27.0 + react-is@16.13.1: {} react-refresh@0.14.2: {} @@ -8379,6 +8938,8 @@ snapshots: react@19.1.1: {} + react@19.2.0: {} + read-yaml-file@1.1.0: dependencies: graceful-fs: 4.2.11 @@ -8386,6 +8947,10 @@ snapshots: pify: 4.0.1 strip-bom: 3.0.0 + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + readdirp@4.1.2: {} reflect.getprototypeof@1.0.10: @@ -8486,6 +9051,8 @@ snapshots: mdast-util-to-markdown: 2.1.2 unified: 11.0.5 + remove-trailing-separator@1.1.0: {} + resolve-from@4.0.0: {} resolve-from@5.0.0: {} @@ -8600,6 +9167,8 @@ snapshots: scheduler@0.26.0: {} + scheduler@0.27.0: {} + semver@6.3.1: {} semver@7.7.2: {} @@ -8864,6 +9433,12 @@ snapshots: client-only: 0.0.1 react: 19.1.1 + styled-jsx@5.1.6(react@19.2.0): + dependencies: + client-only: 0.0.1 + react: 19.2.0 + optional: true + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -8920,6 +9495,8 @@ snapshots: tiny-inflate@1.0.3: {} + tiny-invariant@1.3.3: {} + tinyexec@0.3.2: {} tinyglobby@0.2.14: @@ -9091,6 +9668,10 @@ snapshots: universalify@0.1.2: {} + unixify@1.0.0: + dependencies: + normalize-path: 2.1.1 + unrs-resolver@1.11.1: dependencies: napi-postinstall: 0.3.2