From 0e47cd03309e6c513fae8201adc870d2d88daaff Mon Sep 17 00:00:00 2001 From: Nazar Kornienko Date: Mon, 30 Dec 2024 00:51:42 +0100 Subject: [PATCH] fix scaffolding using default flow and config --- .gitignore | 1 - .reliverse | 2 +- build.optim.ts | 30 +++- bun.lockb | Bin 474776 -> 497281 bytes cspell.json | 7 +- jsr.jsonc | 4 +- package.json | 20 ++- src/app/db/constants.ts | 3 + src/app/menu/askGithubName.ts | 72 ++++---- src/app/menu/askVercelName.ts | 37 ++--- src/app/menu/buildBrandNewThing.ts | 8 +- src/app/menu/compose-env-file/mod.ts | 5 +- src/app/menu/createWebProject.ts | 37 +++-- src/app/menu/generateProjectConfigs.ts | 6 +- .../menu/git-deploy-prompts/helpers/deploy.ts | 48 +++--- .../menu/git-deploy-prompts/helpers/git.ts | 89 +++++++--- .../menu/git-deploy-prompts/helpers/vercel.ts | 25 +-- src/app/menu/git-deploy-prompts/mod.ts | 155 ++++++++++++------ .../helpers/createProject.ts | 35 +++- .../helpers/installPackages.ts | 28 +++- .../helpers/logNextSteps.ts | 11 +- .../helpers/scaffoldProject.ts | 28 ++++ .../helpers/selectBoilerplate.ts | 7 +- .../helpers/setImportAlias.ts | 3 +- .../installers/dbContainer.ts | 7 +- .../show-composer-mode/installers/drizzle.ts | 12 +- .../show-composer-mode/installers/envVars.ts | 7 +- .../show-composer-mode/installers/eslint.ts | 42 ----- .../show-composer-mode/installers/nextAuth.ts | 11 +- .../show-composer-mode/installers/prisma.ts | 12 +- .../show-composer-mode/installers/tailwind.ts | 8 +- .../show-composer-mode/installers/trpc.ts | 11 +- src/app/menu/show-composer-mode/mod.ts | 2 +- src/app/menu/show-composer-mode/opts.ts | 20 ++- .../template/base/{package.json => pkg.json} | 0 .../template/base/src/env.js | 1 + .../template/base/src/styles/globals.css | 4 +- .../show-composer-mode/template/extras/.env | 2 + .../extras/config/drizzle-config-mysql.ts | 1 + .../extras/config/drizzle-config-postgres.ts | 1 + .../extras/config/drizzle-config-sqlite.ts | 1 + .../extras/config/react-router.config.ts | 6 + .../template/extras/config/tailwind.config.ts | 3 +- .../template/extras/config/vite.config.ts | 14 +- .../template/extras/eslint.ts | 34 ++++ .../template/extras/index.html | 16 ++ .../template/extras/pkg.json | 35 ++++ .../extras/src/app/_components/post-tw.tsx | 6 +- .../extras/src/app/_components/post.tsx | 18 +- .../src/app/api/auth/[...nextauth]/route.ts | 2 +- .../extras/src/app/api/trpc/[trpc]/route.ts | 12 +- .../template/extras/src/app/layout/base.tsx | 8 +- .../extras/src/app/layout/with-trpc-tw.tsx | 10 +- .../extras/src/app/layout/with-trpc.tsx | 7 +- .../extras/src/app/layout/with-tw.tsx | 7 +- .../template/extras/src/app/page/base.tsx | 28 ++-- .../extras/src/app/page/with-auth-trpc-tw.tsx | 6 +- .../extras/src/app/page/with-auth-trpc.tsx | 39 +++-- .../extras/src/app/page/with-trpc-tw.tsx | 5 +- .../extras/src/app/page/with-trpc.tsx | 32 ++-- .../template/extras/src/app/page/with-tw.tsx | 3 +- .../template/extras/src/env.d.ts | 10 ++ .../src/env/with-auth-db-planetscale.js | 3 +- .../template/extras/src/env/with-auth-db.js | 1 + .../template/extras/src/env/with-auth.js | 1 + .../extras/src/env/with-db-planetscale.js | 3 +- .../template/extras/src/env/with-db.js | 1 + .../template/extras/src/main.tsx | 31 ++++ .../template/extras/src/pages/_app/base.tsx | 13 +- .../src/pages/_app/with-auth-trpc-tw.tsx | 23 +-- .../extras/src/pages/_app/with-auth-trpc.tsx | 28 ++-- .../extras/src/pages/_app/with-auth-tw.tsx | 21 +-- .../extras/src/pages/_app/with-auth.tsx | 21 +-- .../extras/src/pages/_app/with-trpc-tw.tsx | 20 +-- .../extras/src/pages/_app/with-trpc.tsx | 20 +-- .../extras/src/pages/_app/with-tw.tsx | 13 +- .../extras/src/pages/api/trpc/[trpc].ts | 6 +- .../template/extras/src/pages/index.tsx | 40 +++++ .../template/extras/src/pages/index/base.tsx | 26 +-- .../src/pages/index/with-auth-trpc-tw.tsx | 2 + .../extras/src/pages/index/with-auth-trpc.tsx | 39 +++-- .../extras/src/pages/index/with-trpc-tw.tsx | 5 +- .../extras/src/pages/index/with-trpc.tsx | 30 ++-- .../extras/src/pages/index/with-tw.tsx | 4 +- .../extras/src/pages/with-auth-trpc-tw.tsx | 75 +++++++++ .../extras/src/pages/with-auth-trpc.tsx | 77 +++++++++ .../template/extras/src/pages/with-tw.tsx | 38 +++++ .../template/extras/src/providers/auth.tsx | 10 ++ .../template/extras/src/root.tsx | 26 +++ .../template/extras/src/server/api/root.ts | 23 +-- .../extras/src/server/api/routers/post.ts | 31 ++++ .../src/server/api/routers/post/base.ts | 9 +- .../api/routers/post/with-auth-drizzle.ts | 6 + .../api/routers/post/with-auth-prisma.ts | 6 +- .../src/server/api/routers/post/with-auth.ts | 5 +- .../server/api/routers/post/with-drizzle.ts | 6 + .../server/api/routers/post/with-prisma.ts | 6 +- .../extras/src/server/api/trpc-app/base.ts | 2 +- .../src/server/api/trpc-app/with-auth-db.ts | 4 +- .../src/server/api/trpc-app/with-auth.ts | 3 +- .../extras/src/server/api/trpc-app/with-db.ts | 3 +- .../src/server/api/trpc-pages/with-auth-db.ts | 2 + .../src/server/api/trpc-pages/with-auth.ts | 1 + .../src/server/api/trpc-pages/with-db.ts | 1 + .../template/extras/src/server/api/trpc.ts | 43 +++++ .../extras/src/server/auth/config/base.ts | 3 + .../src/server/auth/config/with-drizzle.ts | 7 +- .../src/server/auth/config/with-prisma.ts | 5 + .../template/extras/src/server/auth/index.ts | 2 + .../src/server/db/db-prisma-planetscale.ts | 2 + .../extras/src/server/db/db-prisma.ts | 2 + .../src/server/db/index-drizzle/with-mysql.ts | 3 + .../db/index-drizzle/with-planetscale.ts | 3 + .../server/db/index-drizzle/with-postgres.ts | 3 + .../server/db/index-drizzle/with-sqlite.ts | 3 + .../server/db/schema-drizzle/base-mysql.ts | 3 +- .../db/schema-drizzle/base-planetscale.ts | 3 +- .../server/db/schema-drizzle/base-postgres.ts | 5 +- .../server/db/schema-drizzle/base-sqlite.ts | 5 +- .../db/schema-drizzle/with-auth-mysql.ts | 9 +- .../schema-drizzle/with-auth-planetscale.ts | 9 +- .../db/schema-drizzle/with-auth-postgres.ts | 11 +- .../db/schema-drizzle/with-auth-sqlite.ts | 11 +- .../template/extras/src/trpc/react.tsx | 17 +- .../template/extras/src/trpc/server.ts | 11 +- .../template/extras/src/utils/api.ts | 62 ++----- .../template/extras/ts-config.json | 26 +++ .../template/extras/ts-config.node.json | 10 ++ .../utils/addPackageDependency.ts | 12 +- .../utils/getReliverseCliVersion.ts | 5 +- .../utils/getUserPkgManager.ts | 2 +- .../show-composer-mode/utils/isTTYError.ts | 2 + .../show-composer-mode/utils/renderTitle.ts | 28 +--- .../utils/renderVersionWarning.ts | 1 + src/app/menu/showAnykeyPrompt.ts | 2 +- src/app/menu/showStartEndPrompt.ts | 2 +- src/args/config/mod.ts | 5 +- src/args/login/impl.ts | 6 +- src/main.ts | 4 +- src/types.ts | 17 +- .../configs/miscellaneousConfigHelpers.ts | 4 +- src/utils/downloadGitRepo.ts | 29 ++-- .../handlers/codemods/convertToMonorepo.ts | 2 +- tsconfig.json | 1 + 144 files changed, 1458 insertions(+), 711 deletions(-) delete mode 100644 src/app/menu/show-composer-mode/installers/eslint.ts rename src/app/menu/show-composer-mode/template/base/{package.json => pkg.json} (100%) create mode 100644 src/app/menu/show-composer-mode/template/extras/.env create mode 100644 src/app/menu/show-composer-mode/template/extras/config/react-router.config.ts create mode 100644 src/app/menu/show-composer-mode/template/extras/eslint.ts create mode 100644 src/app/menu/show-composer-mode/template/extras/index.html create mode 100644 src/app/menu/show-composer-mode/template/extras/pkg.json create mode 100644 src/app/menu/show-composer-mode/template/extras/src/env.d.ts create mode 100644 src/app/menu/show-composer-mode/template/extras/src/main.tsx create mode 100644 src/app/menu/show-composer-mode/template/extras/src/pages/index.tsx create mode 100644 src/app/menu/show-composer-mode/template/extras/src/pages/with-auth-trpc-tw.tsx create mode 100644 src/app/menu/show-composer-mode/template/extras/src/pages/with-auth-trpc.tsx create mode 100644 src/app/menu/show-composer-mode/template/extras/src/pages/with-tw.tsx create mode 100644 src/app/menu/show-composer-mode/template/extras/src/providers/auth.tsx create mode 100644 src/app/menu/show-composer-mode/template/extras/src/root.tsx create mode 100644 src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post.ts create mode 100644 src/app/menu/show-composer-mode/template/extras/src/server/api/trpc.ts create mode 100644 src/app/menu/show-composer-mode/template/extras/ts-config.json create mode 100644 src/app/menu/show-composer-mode/template/extras/ts-config.node.json diff --git a/.gitignore b/.gitignore index 5e3893a..6d5c17c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,6 @@ dist-npm/ .DS_Store merged.txt .eslintcache -.cursorrules node_modules/ addons/premium/ addons/**/premium/ diff --git a/.reliverse b/.reliverse index c493658..11a8770 100644 --- a/.reliverse +++ b/.reliverse @@ -61,7 +61,7 @@ "ignoreDependencies": [], // Config revalidation (1h | 1d | 2d | 7d) - "configLastRevalidate": "2024-12-25T13:06:12.128Z", + "configLastRevalidate": "2024-12-29T20:57:03.697Z", "configRevalidateFrequency": "2d", // Custom rules for Reliverse AI diff --git a/build.optim.ts b/build.optim.ts index d0b7758..f1ce4e8 100644 --- a/build.optim.ts +++ b/build.optim.ts @@ -27,10 +27,9 @@ const npmFilesToDelete: string[] = [ "types/internal.d.ts", "**/*.temp.js", "**/*.temp.d.ts", - "**/*.txt", ]; -const jsrFilesToDelete: string[] = ["**/*.test.ts", "**/*.temp.ts", "**/*.txt"]; +const jsrFilesToDelete: string[] = ["**/*.test.ts", "**/*.temp.ts"]; /** * Deletes files matching the provided patterns within the base directory. @@ -241,6 +240,31 @@ async function copySrcToOutput(): Promise { } } +/** + * Renames all .tsx files to -tsx.txt in the specified directory and its subdirectories. + * @param dir - The directory to process. + */ +async function renameTsxFiles(dir: string): Promise { + try { + const files = await globby("**/*.tsx", { + cwd: dir, + absolute: true, + }); + + for (const filePath of files) { + const newPath = filePath.replace(/\.tsx$/, "-tsx.txt"); + await fs.rename(filePath, newPath); + relinka("info-verbose", `Renamed: ${filePath} -> ${newPath}`); + } + } catch (error) { + relinka( + "error", + "Error renaming .tsx files:", + error instanceof Error ? error.message : String(error), + ); + } +} + /** * Optimizes the build for production by processing files and deleting unnecessary ones. * @param dir - The directory to optimize. @@ -256,6 +280,8 @@ async function optimizeBuildForProduction(dir: string): Promise { await copySrcToOutput(); relinka("info", "Processing copied files to replace import paths..."); await processFiles(outputDir); // Process files after copying + relinka("info", "Renaming .tsx files to -tsx.txt for JSR compatibility..."); + await renameTsxFiles(outputDir); } else { relinka("info", "Creating an optimized production build..."); await processFiles(dir); diff --git a/bun.lockb b/bun.lockb index 00bc2858d93dc54879156fe42b8e1f02331c7c0d..01c5775d076b944ce0e4e1e076ed49285bd68229 100644 GIT binary patch delta 103745 zcmeEvcYIaVy6sv!Y{&vb@4X1pf`DKe*&*~6dY1qpY?444DL{g8CrAlRg(XfvktR|U z6%ZwXC=m-Nh=?Glh^PqYp@|3yM&B4~&XvS-x!%+6dw)Fk@0^TpjM=~0zP0vNR~FUJ z+TQS~cC9v4cyU0r!yhm9o!icpzPo*Xt12gbwB>bYcz5iww)q2}Ou4?IoW{?EWuqE- zJ~~y#BJxi~P0LJ)NKT!Ws%c3`s1E))uqrS)WwNh3eum}?UIo0!scGebJ<4jD0h}^9 zCW*CNhF+QZ-ElMAP|kth2YwvL`ac9R|332iFkXe>18jzhS>Xw9O{)XUH>>EiBC0`U zb;}&GSdu#;G6l^cZL?$G~qm?tznL8mVf@9PCDoLK9unmx{R74l)EH5Bytq&dl z%dCPwF|FzD1Qg!b<7#sWmE{B;}r<$xFA>N%56X(`ennN75 zJb|bS37JQM>~?D)om3CVW?!ly=WHF2&G)V;^@J(zl*njJ`vjbGIN6Z?Fx$sDAxGq5iB+FF)#J(&-Kp{>m3Kvq-($PP5CC%IGcpD~f_=&L{ut1&7< zpEB>DqUOM_fenCf0UHBf24duysSRa46M*bU2$1Pbfep0&a41TluFQr|(N^XU;3%7E z;twaJNfTLt3FKl*0X70wR{ExYb`ruZ@x)M5s~g>O`F$7>K_5Q zd>XWs<>TB*QSOeK_CCt7Lovy|Y*O>~WqyyA`34d=vZW9>VJ|9v9-KW-Y$sRHl!)Y% z>F8LJJ2g3GN}4tp9ipeA+%d^1(Bs{+2-@Fo*Hs;4JuT4*4#?iVC);EGJP`KL0B!_w z!WIKL!O0580Xf0FfLswBfn0&f(Gf|BXt0x?reRJqs{fi;0wJ4^fm$R+%q!W}?%WR1dw3TFYi2Hpsko=Zu_ zLPAI5pmPc$6Jis%_jbDAf4bN^M9$e*AT#{#l5@EUh((gw0wZQVABDQYTMx>7^+IL2 zE8ui-VnT9CWOA}r1fAvWMLQ`qc~Sz_+B@B3eN&P>zL>S7h@su3&>P4OOijjV7!&Ju z&vHjbpr=pvkou!Qu90M*)lfuIvfDQIDkj}aVtS5Jbxw>3CPxfdvC#iAV724ydp3O!^C(Mk;a$65h-`^f1U3(tLxm+<; z=1YoXDQuZXq#hR&kFh7G04%*4bappBVY1sdVp7a9)K?ii5r}$PXGXwazwKo{DJFh$ zOng-GE8#L@D*V9yMwrvR8+qKxlTu@1Cu@Jh`r>9g3FOS}SGY~#(+WMn8c3f8tPUKd zu)D$s71mQ&PT`G-GXGhHhk@v>Co@MHnJ+3d6=ngeqJad3;R^dJ3{aSq5F48cPk%jC z9xLwvd75ki@-$hj^dumUwc$V>N9}?4^Wd!vqcMcP!N2zdKL#?xOTcEpg+QKbQ-Qp| z`YZGWGG7%S8~hm?gXt%McUmuOmNl{3FOqZ zMLv%KBP}8}mY0VeFlM7-j^u-MIg$?GTv3C8JW#s=x#a3TDqT7USPR@sp>Kvz#0>bKJ+2OcdnC#opPUvSsqx(FjDovUOXC z$kP)hBURISg0n*p0a=f&ub3l8x&+7phCMFJWr4H2eJM|i@x)K|O_-t$n=3PZhKwBP zSKxG2=sa0*bVM?CucjRbr``rw6F5UvI3pr9B_=`BF3y+fQ=rpBV}Q&*Q04n$fpmQj z@bb`;J?^evM7Er>9oD!7G5rCB-nCRvx@Oc!OmUBY|8iNm*8hJeif3%L3D4 z-B!&AjAG(_BV+C$1AA(ivgcEPtY`#~4PQq(c}jFrLTXer;-TrO2&T2+E9AM^6Ug!p zD-3-`4zMLS*IIINbmBCvlcvoOvVk}tN9vA@^qrbKDLz8!v^kO}cS>ReZu8HdF>`%d zc-~kg+t*gh;`_ll@Or494ymZH-?K7b#8fPDe7*(ex%4rRyYOuw`)gJv}?F*QCd z5@)-v%HMoi)}NRhYnA^A9OG`587z%VT#atu$&<8i!P)-^ZXrBNoPOGzQ_&ST9x}(< z2FN{<5Em5{Gey&efwRB8fm|8!lOvLnBGR<*4YI$rH<*PL8+yJ~(~u%>Gwr??WGlxv z%4~R^N}B9W(N3x9aK}$ijY)DR{rz)V_f4|7k5#_+fb4A!kj?LXQBLb?N`FD>p8v9N zRwIG)K7NbDDJpcax0d~YH*TvOH@2?tl-P(U`wXyyIXk#}O4Sz`8{?ZClkQ%iI`Ei< zxF06L;9gb>t>Y|&I6Qvr_~40`Z74#~=Pl4p~UdR1V9YzWaCfG|T1O+&E!-4ErA0X!t;dS!#SWUa}hU~ysK<@ST zfi;1zDSQ^ljx7MP{!}2BW;BrXT~P!2&Vl=ZYjj3T3b(fQWUegO8c6+7U^U>&du4}` zWAF$#B|(dY&M6xY#Hon2-?rDMm(5rbWcTPsp1u z%l)xmcKD%pq$lbDIfd>z$%#oZaS`ai^i=diyMc^!b?yN(w`vQI<2_m5-ghOwiTXKp zc2FdPoRZk)?_(M}_OW9ZJGQZ79Xl4XgC;xXQ9+d_^Y5cBI~e-=Ajpo!>?rJ2beQ9a zn>N{cY;E|loT)iLE*U&FS+T>^m=tR!>@YQJqIA$b{v|Z66|<%_ffg#a;%J6 z<~ne$xp0-wj#uq?*N$85IM|MVUsUDoc(%VPZ^gCTopyk1s*HAkd;^?|+YX%1DjuLJ zzzF#~s8#=5cGT+&IpwwoZk?7MN}dswpy4z1OSy(80y)JufiAAONmy5tB9h&c6XH@+ zVq%kjQW@U(O1gB5Lc|$xCcK)Q>^n0$B4yN3X#n!s+~$ff=a zkb7vc;yYFPI`o@+>KX9*z$_pg91W}k>;-HJYzO30t_*Alym?BVS|@;w`2esF1`E6Z ztPOk;$cknvZat>szzqkl3%wtZo4yl}9qn;JI;5^jcLG`dCv=ecj{|E1^OU2v19><+ z4ZKflqh&s(5-I@c(5hI2T$}kn%FX-+kf+3Kb9&8Mp69S~*}VM*X+Ai+F%4J`h{x!J z*coo^>}455p8#^cJ^<2>ZXjD3r*NRsN2_}6`a{8MA>Zt4vNuUUl=EczB7qZ@kG;;b z#EsV)@i-i}-H;9W{36E}>z)y>;o}9)4qQ{|g+O+|I@4nC%nx1(e7Vw#Zb}~Ut1LGR z$d1oP`hDDT5ipuV7zw0b>{nV-@Sy37N3pTDr9MbuqHk$lIpe?9~9ef(djs!b&YstbkXgG7kV*ps!Q6o?Vxh(XA)94&Yq#G2m?I@8@<0^v2LrfsFoNLOsks z5u6@ONuI?S(9*qS`S*bwxI5YN=hp@HF>4>A_OWT7iuUPepNjTzZ6DL!BcuoHS? zNmX=fN)ls|uw;|9xOC>yv>{b>Yq29BaL4n#Yk(4Z06E7ID9G(-z12m53e|P%T)0zB zy6zf~yM^y__#ANooUUF`L#Af~S&#k9YJag|KQO5;Ha!0&L-pFawLk4|K1Sg{XT{HB zWL&jP>PSoh(j!qoHk6Xo>0yq{UaTJEr)Q@o;Q0e9?%%$0ZP(j#Y_Ab}uK%g479F$J z6t7%+uI)u-FA{sP*^9-V>**L7Pq5IYa^xFva&ju{pu-+{Ba!6hIw*ip*lSnJ@@&O)c_@y>5r44$icw~+Z(wJ>w*H}o7ua%Cj1Mn5=H zJ}qT?c=YAPtRdt}M$D(3fXJ!>E}8<_zXuhcfqHoL;51CZ4WGpK&QZ9B z;#B?iR|3|ne(TC@FR#Wm?R4w-_-9}F+b_WF7vJ^^@6=R&Cxhz)_44jQhpg`ky&qBz zh3R{yK-h9+w)<2m~skk`0Lgz%pMMT9!MkIGjnTEZ|g7%}*?*Y=| z$*FO15x9Bmga0OU&fIw*D@ch*jF^NMH(#248#nU23)LFFdM7e8SdMllkn^?~$mL^S z2Se5T1ps-PH&p!75LwPXo&UR2-agH%p&aVZ135&xNy{A&a8@D$C$Na^zb=^pxBm(l7KAXn12 zK;GDh(o@p#uzC=jJ7w`uxfb38=TdGkOs=I(;B4nvAdj5&{SYE>Y1&KJe%hap3=E6Y zfovcK$cj*p#cQhcY9pnKCxElUHo&?-L zqF_HDV~h?!uI@TO#u^SFcgc^ako+W&b9)%bhQC7t)ISBXqw(P~wiyQGX%_@!`nThC z>$UiPU{mnEo{75F5k@0mYhX@qNSdgln{xSIO+!!;GCZZ z@?602jWY%}Z&H#kUjY`4ljZ-wTyexVfSkHie4!H=qiNTXPM(Obni!H0QQV_tOq0jc zH6Sx|0kX&Z(m5R`Tt{%axEYWgON^M7fP$L+8sjTe%r#_RC{vQ<_mg-P5;G|w1?THz z>)Y|kZk(hknuaG5d0qqS@e$!^7+h?-5~Pdgf^($D6Xl$DOp-3H0&D_ZOO|V8D$sg% zn=YqnT#B6IoI86T!I%{Zx85kmt%EAnSh( z^|K?}(I9t0gSnDdpR3FL{~R)~f)?{+flP2V5Ci0h`T@D>pT@yJkJt}Mk&y|hoYPsD zGIs3H0$I;^HHCwL>_ESTvcU&|EMF7I?frXpSJ|Tsq_6y6NBEf7h(`6CLKYR*iR^>e$C?SNn%wyKi~-(Pqn@mCcpi6LCToAAYK!*$y!~|JPv2RdzV3l5L+dv>cVuIYsLEdtZr$(AA*Unu`yKAQ`NF}k zpGg|gr1_>RO)fpVXw%h-#y7(nUfCOda^U#4>l}%Haoj_XFWkNIk5Bdm=pTIQK3&kq zcm%@;h%oBXLH-tdc>NZ1xLJ3W)8V}IPQsC8wdT|>)8B^RHy5M#IQCUm;c)2 zMwz#|Z1(&*_1EMsZRhO#weEupo?d_LXs;*BO?!CT6SbBN8`=8ymO3}qubFiHli)F* zPaBu=jQ&aVH#=*+`uOfa!4J59%{u%>#EXx$H9bC;AD@-F?dEeW9=P#Te6zyqi?4YO zPYLnv=Z(}so^M{of!#bIU*_H+7RYj-k>-sn;W zMpv__e~`Y|OdA%We_-a|uhT5R-w-okc!)mTOvB&xX3p>s<0y_S4pKMshxzG-889LQ zjiikTan8n>(8Vko6lA;!s~z%qo9KcOiu(mS9Yd#+7ARJhOg~`ej0|x$#j!ZpEE*PM zB*6+n-tz3CzQar#6=K|ihG(wKvK+FrGXepbcOEQW@=jA9?q|G79Snm*E9G$Ma$+2o zoBD&PfnzEbgRwtOEA52J>0~+Q{WxG=+`U~&;;fei$Me^Ble7`^3Xv8uS(IFpi%1?vTdehl+7^1ud2hEWxcV}KEup}`5CLh`hj5_VBdprCa{6F; z0NP2lEgIt}*dQw-mzRHIO&ezA%$niv4H5<-yLJl9 zAFPtq?T&~BI0zpzf1IE3GT0C>Yu+72P0aMkE@Ny{O$&txXTRg`4Kf-8YY1MfVFtM2 z?Pi+R9-78F^rdEwJ4AoaEO3W7?ldz4r?{NH_j42Eec#0!24`Nbz;j@@KGmuSL7>8Z zSsT%q3C8t^{SUwI0%N%{X4X)+v$IMXSL@V9cS3(B8VgH&{n7 z%Pq$5VC;KEr?pr=1uI_YMpa)~8IDM-x`DoCdVEKG#6AGA*r zo-2=m(IF`3_H*Webr3~CdKYgT06d7RQa3h%Jygt2fyuVAFwzbvE@u=cg4eW;n%LV> z4>n$eJPJjyIB|#+{fP}nT_cLqwt{hm%bdUci4B5(xr{5BS#SH70U0EMgV8%!{p})N zft@umJ6I2Pj`YKi7|f!?AZH$|NXu%Bl^9`JS+FKp);G3ggy6IYM@1O^E&nngk1&IA zimtege^vCU={+!Dzx~nGcbjMV_fa)+76y#K% z1!F-hTdb{Np=SD9A>KGuxaF)V^m;!O1s_Ji@~D!vqW@T;V`0kelArBoJfRrcAL?g( z45pS8ny84g6El!mfs5HW9?ZJH9nW_+3#Yq`GZ1*bSjVNKMh`PR$z`m@ZsX{z9b=pV z<26;rx|mvE*KmpAu*c?@2*%-*2*%dE ztmFMlFl+X$Q_ZKZ%~&o3j2E3XBFB?`&GeZr$L@Y+?o5~QBksK6RxLb!UD!3O1-IE* ze{T>vNjCl=7#jzh=I@RBm0rQMn_Wz-e2ygp%=9#u@gZ_>#AP{RXIb1zfef;YuCO>- zd8*9-;}~Sv@(LJh#rQCBVe+tj2%qH ztgFF!0t~?x_Cuuy;D0od z2u2@zi_m0T5@1|ka`t}!ldFcEtB(Vcos(;Jcrl~zmynqmVL`^bu&{u!Z$fcs*TJ;Y zfmV>>d=spznK3lTaNy{rOJrcy6-*9@*VbcT>=Aa+&;H&ZT%70-?zbKz<&9?D4Xgll zI4tT|M>rprfzccCB>4c0o+fy+461hgCV%hn;?;?qGr;`KyqF;4 zEm$RMzwCH>+n~RWUgOQc#V$vi31&JVdxDt@*gwH61UM#|xl3G5uSx7|-jX2a5Lg2& zYfZ6rso3fq$<*LwUCO}dXJz{cRv*i%HrciwDYiBiTR#e7eAeY$Jqx#kwT*AV^0yW~&SuAhX=dTGE=N+D8Tg#bIFKgy zoAnH26oGLs%6sXdbh**6eGouKflV}nR|h%2gEiLbcJOSoaE;56G20A$-sQ}njm>9u z;h{&(!slJasz>eTGMpcd?;bVN*Sd^u8FH7%{k{yWqui9(CI`T}vS!2UG1=z|rjENU z2&@-!;q1aelm&(>N*>hCBVY)YRtGsMc+B+mE@P-iR*B40{G4;aMp~}=0#>|Ld7sSU zBL@z7EYr;0;BsuwGz&MloL7++YG%w0a%`Amrf+l^$L7c*1vma;Kcm{?(qHmT$!IVa zdSYEj&S%IhkAJ6f$@9L!T-&Qzqx_6=gIQ&)$B>&~tQ?bzv&L_MnY-C# zcos-e-ah$YERM$zL@?E|?Z*k;Q;}d@kcN{Pm+x9It`tO^IB3s-$;%-0x__C4@IT&( zbkP@@xm#V1Eep-UtuEs`$XtaojvBp4*5)m;;&5z(A(%mJylAe14F^M1g}IMkjCBWw zZsXdy3Wn=b4PxRFGxsHzv2}^PJo00Z1&p1sLJ{M^r83QWQ{YHlYUaM|GG2p-qaEYK zJ~JvjAuk3w2ZO-m{O}6d2*#dC_g*eeW6wH0Y0nmO&H&@xC!asx1Y$+ab^2CHau+x?sOSZE99-~6j}FSYQflm3={7t26x8!8H1iF?Rc|jX6`PR zF-O=vg2x^22s7|?mr(?fLy*g*^GcZoix!I~6|B9L#>eYTE6u>&F5^px^szN&N1at> zF2td$igO}joCC(af-GVFWk5Qc!2^Pv_pOE#tQUNJV97Pkxz7e;jadJPblwN+X4h|c zKPwsH8~A%T7~Lqt%BP++1NXR$-yn{&L{8A?=j29{4;5R$o`b+%q4pR*$5(62z+9K3!t-W2p!f4;E@1BSW?`<&x%YXjYcp?g zkZ}{1bRYZfyH>7uI1`a&6qrn-GuMIzpmsbnVa%t%*cAj&zxaEvD-O-z=U(f~!hJ4d zCd9VLf%Q5cdk2i`4*Ai}39uekU0m69*2}XEt%mzK-0RK2xA33`Q96|y>J(U46u^uO z^K-PoPx_H1YJp7?otX`Im6UVs$46oGjZ2B`Sj>+2L&X(KJH|u(w z0!wuR-yG}!8)7}|)qTaThr4+q7*{Gz$l-p@rXO<|IS{*{ z0M1-={SKHsCfM~(Z^-S2(`+U-%A2xki#ZIu=jtON zc(8$1P93M;ZZOUkW&~^GDp<*`X}-@scUh+!Os+Dr^05*rUI{sLGpYlry0ZI6^I zaTZu{AD#JNc$6C+-cic2E+BRCdhaj7MC;*Xxvuq-Zul!x*SvA zH`C9$oNM3bqhiL{AgA{Swly7Al%<`5<+iNggM5%=zUN_0u&knDYuq7Q+Y2k)O0E5& zZKW1lpTc^?O7;DSR`7RS^pDK+??TFej7+l;Kz zC-ShwVFWe;jFT(b8nBX_=l+z|>X^OBSuPcfN4G4u7mPC^ne)@qwe$rmX<-?dJSter znbK*_=Er%S6kY7%4WlH}+h8S4IX^31T^LwNhnM{+?KD_PEzM7qu4NinNr!iXm2|NP ztfXAulcm#^ft9p(0_PFjsBqTIz3wu4oR#+lYR58u8f+{I$c1|aY`DdE(1m>?4>W5T8!v#-&DIreTvTbY zk1fvGZ z4h%i6NtT#8QUJOglhg|78bf3!+$aZ@3@R- zev(TDOBYWMAA@nH$e5(e-()$Y&Gs{5z&M474&kVmzy^Tf96ass4KfO(GQV4JJaySD z{LN+jep%C|K(wMmWAYX05?ml@evWll%-kZEaULRfipL}j(rt}=Lz*iYy z>@`Mqe&0mfRaH($;Z%xQhQTH%f?1J~e-i2h)#4-hx^+Eb&5rl;dd{JXy%q62@p;HRV$cK@?@%w@ za;jlwMZ?8^V*9{k=ks6obCxfoYa>_id-+SS`iQ*BdY3XzT^ny{AHf=KS&ho-S}$6L z8)<=CYopo0qfeoegWStPPf|ql&jIW$hKO{@xg7RXx~iK8&Dh z7|ltfa{*e1gjYiYgM{oHVW8_GuNt}tYXGev=hYH3sC==ypu{{b&6+4x0lt%^Vqjvl ztYAlpSx{nz-p33X)ltf_tgOdM%sM{B6-1PnTVeK6{VFj#S1eX%mzeo5d&s=bO2ua1 z5_4&Zd92tpLh%nOam#s|S!?}_LtykjJUPnGt92D!%&tpM!q^ML_`Hg4o$k1I zCiutLhD#N{%L3utSxeuMS6!qxK$e;{ur;9Kn!|<|2qqUj)}Uik4H4K7c`hgwBg1*P z1plxB^T-h4eK2`C@(b#+wXnLZHh9d91LHX4akUzZ<-A2`6I{e#EGM7mn%0J&!Ehqr zc$osma#*|gzTgEgmcux4UwmC#6gEbm+SJjl!^(OW?3@oazOUu^6*p_LAlYF#AZeQhjE~ zXpYyB^+j%TJ){hT0W$3ZOjW|SL2*6PVXDG8FjZb>!(ufICiW7lECb7~3#MIHqvEWo zFje<*N>sgZaj`I%s+bA0pX}Hfn5qlSn-u4r4%6-!Oj)l{L=%3I(e%Kk(z%+@9Xf)G zrXsf$=Av#hnF*fcZ(6|ciUaQ?Uxw9%b0I>#5E_GZvkqau`*rKPeEjkWN9%kr=8}QO zJ7C;JGVuNxj4MHgxb2(Eb}Z&dX)XfWVv;sN#cwUp-f>v6Ca#-GE#%5Zc#q3%1Q-V* zU&C($<93m){PL%qZ6C19F|L^m)($!00IdDxU<~zeRb!nV2jj{_oumAmbz5?L8N-4c z<64Tq2jQ3XP`IdMy!1U7$6~RTAGXr9bTUoTbo}4`tcBhN`h%};y^>U6uFL$Ue{G{j zYs`&Z)K~(>X5nsxd!>^c72Aly_GmfS*B(PYdYx)BkzXIvw%u3cc7Wri;kWx8MNucL zG+3iWQAe!viVxDvqX=uH6!(>wRoml8k?EFY70QJ#6Pec?u!FY@y8-9mX;~^2~kN)<5hz`yhfY)2JtaSc> z;%hJlcsNQBpxxg|x*5-1n4(Ew%2xuXhGAFM&paX9Vv_kLL3;5aA{?9T&}-zmIJ%gV(2wg7AZ*QDN43Trh}jU-b6|;aYNt{6y>}us(dh>kJLR zOq&^NgLG3A^wvWhRRcv}AB?SkASMC@u>2ABJ`2W6L@xhhU_AQJ2L!Q=f@Ie*ZnQcE ztgn@ppXg_70F&k56XO(^++d+E`+IkhN!How><5Mr3%^gC0}Ee&;s-^az+xA0DPeGp zgXKTjC4;R9Fix6$_%*z|6}D($ho%g&hzEiK0QMTBzC#Qa-{*|~Rd-p5MJ4`B9|^;YatY>p@~pDQuH zgV|FQvAca~;e5ik-iIRFvAW*rHa zRP9ffSmp%F%o~H6OT>Z_v)2H+#LBu1W?yN3QDQa=D^_Dm%-tpC?@DkUJgC@=DluOvF>k``DT2qMyMry~jI=GQi|<;ZWg0`QoEWY#5N3aP2Nw(8J*)!b zvaV)b9$$j-h>)ztP`Q0@kYIE42kVM7{M-XC@>hd}m>IK!^wXjs94oQ)Fx~o|48OX- zyJk07Ph`fMv8jIgHjy(P`~I6@qHsKRPCXGY0ZVvh=r93a)Y z#7}~ab)Y<<@FoaPfUkw?_{k1`PE&b2nuLzSD$dUt4>r{LDNp$c zI)7M%9}+zZYl>yfo~Uc{tzXRO5xO?Rvd+Uwx2y@1bonb9=Mh-rEv-!?P9w{j2g_>F zc^=kKYKGrrxkaomkc`b>lh6}v7+lSU8^Z&W&;H?HJiYJ&0jJtVFzfl%s6R#C3kV%h zCJC&gRRd4&7gU-I&Ra&wa}lcwX>-8Tiy;K}AA|KqIr-1Nw2Ll2K@t9f@Qjsl)_ySg zl!r>3-Z8v(`S9Ei)R*5lVGU~Ch? z#4taf-@xc42TI_XW`HcwDPFg}{IlHZ69>j1g0;4?M_A|Y>- z_d4!Kyz^_4Ap47*{hq%!$Venv0i)w+f+%$3Uayge-DRsr99UP2<-hOm4Z;G}EINB7 zVZ&L^{LjFeYFRatF*lawfi=|f>oHg!(bz=TuoDp`-$UfB-m7| z4E?$vY!aEsZ-ghi8Q4;Gea@p`}};or6(|oczCx}~cD_A$A z$ya?pfdznB|4fhb{w(xdWZZ{;=mXXwt9JG(WR8rRWTf;Y7|Y=Jz}0XN3{O}1Ekw=7 z?fx;k8VjZdi5b}k#zi8>6*yO>$@-oHPo(Md4IXjtJW*J++zC)zS*flVw*+JF)FxZ&=V+KM(o?Xq zqqF5Dr?1GzNglOOxBkH~$@YU;FA1%*8jE!6ADxkDQ@|LXOSbz@tjuDBAH``yz<4{$ zoX?eJjth%L;S4+*3|t}tX9C|^BGQ3Ijiqvj;Xs(}=NPe66f$+*(l-NV=~c^kc#<_# z4{(2lPULHbd=6l9m7hqng~A6EwxozpX{6LjrMFh;K#x^nTL`SUok}3md{A*B&Gw3y zMoJyAs-k}xvGc4R;Zn8Gtq_-@ z#iJDBMzcOdnz*s750U0nh0_$`rm%FJ_|~U1($hGaEsqCJR~d1{TA$KL35TooA=1Qw zYJG?fv1X%Q%R`Ujl(RmikrGZh>qBHdoKV)MG_pVR!+k(3Y3oxODJ@Ytk@=rc94M!- zlwegr&Wul~j6|Bt@P{3FTJaSW@hOdzo>6*ftPg#s(up*8aS0hP_Nj!@NGVV0e}&BQ zmdaNeeV~5?odY?d(*Fz6b^I{F8+Zc9UHv7HNna^ELxK;H6`oc6ZpiZIkU8dSf?}n^* zu}Uw69&1rPi3Fzczrj)txDv<;p9QkvbwK=2+rU2xnZ8N!(nx)a(up*;DNdyMvf?j0 zs)7AUKn9WbIIalFuOc7fHtmwiUmDrY-;_?I$$t?u2YOZEHH!EUsb2?Dx}o?l3U5-x zhsb=t@?Ydk;}!%qRH!oC4VmwbN-vGn`LDX>r!=y@Ka@_SgUdjqUXFh(#6u@-{=2i8 z_dbOcRff{Y^vck65%-e*ptYi^t87H}qlV)D2(mwQk)PGpSLGWhY^dtF8!~@mFBP6O zRT)cT1!VA5I1TuXPMTKm5h~h)MXo&D^{Q(cR#%NF~ zkEry&Le>+m@)21+Lh-vH`!PwSmqykz*$WSg%-~iTh~!fgC(?{j{NF-xCR?wiYa-(n zY#cTpuWBW-)da=whD=IQ`I1#WBFm*HUK&~d43$1prI$i^t(6k2ie{;drI8h-Lnl6} z(uwqYrs70)BujB3&ACdSr*tCo&G$n4G#01?B6+srcS9yE#vgWMsme#>QTVjte}&8< z@Q3ABaVsOrU5J@ryK7W=BGcC@T(5K@2fPW${QNHwaRhMSfA2J*Y8z2krnR)Qh!V7rIGr6=&bkv zkXb%Z`AQ?xKZMTuJ}QIpv)~a3#G^oF_*C)F6rNOgO5qm@zXGzNGeDO628jP@-z&UG zf)A1P{|Ka~t^%3=XS$lkO&}}!RpBj#w}C8J2xPt@AS-a-54jV_a%Cwhn5cI!SK z+MV#H8E_Pk4TsAN7P6uV;4C;%>3@aH7lC{%7pe091zMZH%E*F~RlzB$Adz~M!f1st zDxJvmsX#Uut8^muIK@jN^?0SnJJCK}K3xe!W=I0E!W6|*Rr+5c^UqZIN+au=rF0^1 z%`C;U=znIIqXZ%=nyWaG>GKpXjnwBu=KvO}^wP+B7DMOMzM|4gBOm`gxk%uu%?Gk) z{IBV<;9*sO$cBz6{8-^pAoCpqa?O0M@C=Y2B0KgCkmb)Qomd;ZKyr_5{69j>zv{`q zgnUNv;qVpGlhqY3jr2q<==4Nwg>{&Z>)$f|4w=@7e-zTyO~8rGlujgXt~ilq8~kB| z?G$#Ph)-!`dMD`Ct5!di;cmzt`>TqARKC*4^e###a#}+bCvvXGD1H}|`~NNpDE5~H z|3m4r{lDsAaYJzOA1Qq@E1qQ9D!dG$21j>KQM^AkqxEgc-4aS=x6 zRj@$mL{^v$WCs>0y)-g?vC6kZr4yO|2_QS>S*{X@EMO{5Wh5FRED!%R zZ>ZWgN-mAeehxbETa|t{q$AHGy(;jQ%70s7fhu=5n5V>hwivPpt`DY9Khx2<%_mm^sDC$jt+r9ZE5t%aEXbud_P1CSpg3%m$qFufDV3U>i{XTA%h zXAS}RA+iI9m3~a&Ng(U_0?1EkWP4vx=lZh@h19-R1^yjm13#c1R&@n`IDo6F-qQGH zndkLyysUWTuYT2m($=p(IPQDD?g*nd@gd&(b;rG5cij7R2fhlq_v?;(zwWsA>yCTB z?zs2s4jCTuD`AF(<@m?Kd%y0m9C7d09co4V<*z^P{kmfw+{8fp-mg302rij>zwWsA z>yEqrxPr&(-F|fOpV#ra;%*Jy`*lYn`1;U+QLxcBRhfAUuy_K!O5{kr3Bzv{5gEqqhTZ{F_xy2Ewv*B$?x zKOf-E_&ES~Tj^hQa2Jx_`*jCBbMMz3|DXT5sE~@+t!TUOd)Sn^D6ZsVOP-uPw z!U8e<288IJA)KJFP&D}kLhTz6=KTU;u{cKIAcf91AuJVHHzCaW1;Tj>Pl^t|LTGst z!irxZEEDG_oTAY4mfkunsVlS<-MmH$|697xicMbq9YS02Gx$YtkZr8*Y#8))Y zu{!WoO)Mh5ripKfJ2Vkk1GrNY%Za-*@gwndO?0aX+^vb#K;iFj_=uvKaK)SGnUlVt zGNKlg98GMf1!bid6lZNHd*P4TQ2LaCvYSdCJdxrUDQ;8oNr4C7hEF;|+2W*92YK=_ zcGf()EEG4D0~mE#DBk6u)yb!{1v9%CF_I(h1 z?m##p#@vD6?*k!^!so*H4Z=kValb)0EpjNVtO%h&5ri*AOc8`Wl^`6Ua7NVm9l~u2 z8NWj~D-KcEQW=8p9}v!ov_Bw>t^(lgcbZUvV0A52y&%!7qq~ zI)vzI5H3@=Bmx`|YFCG_#sT3caf!k~3SnLlE{j!O5N6eYP(0Y!hS=(akX;LcPgw{z#h9`X{A)wVqi{-VpAH z7;gxD>Owd|p-9v*Al#;qVLZ0R)z;e1+M8v<&`&%OZ{oR2+!1B6SPOPAdABp$rqFY6vk1kdd zE9&BBVkKSls|2j9iw#8l%d88DRdq46GO(I1wh^oA!chfSLls{(85 zViK{AE^>)=by2k%u%0fa0!5$Z^g=a^pn)#xR!2%hT_h13>Ec6TV|bwkunD|CY^sY- ziOt}Hn!x*YF^AY39w4@W2WkNyfCq>z;Q?YRc%U}0H9SCU0}l|}!UJ`HzVHCC9XvpM z5FV(D9=2?Y9&WFT9(I5asGOn_UJn!Xkl0!eGnefP!3UcX|2k2lfn${Zu!tb|i5!Bz zsL~J+AYurCBA*Z>>NEm$5z`65;t;_lnluK4h%`c1af}cuS~UT56Iq1r;xwU$=+G3< zQ!FI(66XlLML;t^AF+(kS6m|W6QTD5`ioVB0pdC#O!RFI7%0{g28jZ~U@@cxV2IdC z7%KD!0K>!>!f>&JFhUqD0V72OVU)-rj22a}HT5wfhA>v-6CM$DS_8(3>4b1`h%jC> zX#<=fF2O`P*~Fg!e()aLi7j-VLc&i6{~tesEvb86j69d^z8-VAcgI{ zAZ!-}6lRTr5Z)WYt72_>YCq zpdW-cMNB^k7bzT}kR$5!hp_Sy2pRn$>=lP7^ce@icL0Pu{9BJ9+@^4b!rP)%7=$h1 z5SE5P$QP$6j2;gmcp!uWV&Omt-V-2PrtqE!7zAMtg*Ah4a(#d^?J{<4^h78luIPvr zaIRm0Qab{QlQA)31r|C;A#5;eL98$s!mLRUiYOpf7y_YXB!ullARtztaEe0sPzZ<> zhC;}m48dm@1jGu%Ao#l>XtyAk2z`P(%UY!Z-*m;~{Jx2La&%g;Ny5 z!yzDC2#1iJ0KsQG1cVFYA^0ak$fJO8VFHAU6yhd8K)66*<#Y%QCPF~CFcCtZBnU?+ zAY6!maGOF#1O$W&6t*No@STL|)*T{!5-zIIDNxQ(@p6dPkx;x-p)8Gr;&g~FsO+H< zJQ+$khgdWjO7sjUm#G*I5$J|ednS}MZYUKT;zue6sf0~|;^Ppjr$CuC3rZ1{N)FL4 z3QEf~DBGi;RB?zxDyOJ~M?#KsQMG!EDVegTTn9@Kay z4mCD|U*e&7XF^#T52ZQ$LS+w?-~=cSz%L0`JzDyOJ~Cu1rRP$Xk2v*$s`!_xu-ic|>x z^C85gLO@JG;Ua|wGaw+Wm;qts0tiPaAhMVVp-(o1jF}J+Tu``8!FLt}#22$5Y*`55 z3m3mX0TD+A01*cP5eESg$729Q90Wuh9snW^0wN9qB92S|A`Su~4gw;MEC3=7 z0wN9qB91u#L>vS}90Wuhj{^{K5D;+?5OK@}OcGfHgc|_i{S3!C4;@FiF^^-VaG3(a zjrkm_;8^EFK)3-xpDIEZ01#^sV#Rd=0*!1yyjV{_ltD-oLly!MVi1yqz6gN$f{-G1 z5K@J)7%)Rb5N3)T!Yol`2_Q|x5Yk0HVYaBV6!55+PRJ042#<*-PXIh3jgTph5wb+9 zCjoOr7U6MmnlM*%cnUC2EF{bq=Lic#z%oF#SVmYVE)f=q(B*)|VijSDxK3Cq`kH_z z#CpP$qJZ#}81gh=nb=BLF7y=uQ;Y$K>=)s-ycKZU3Sm40!GAM^xMv^;kwf7kg$4q` zDiI?ftlR?O2!&@wot3ESIWe8EMjRqMFPf|ZtQBd5b>bLdy=b)>ut8)IHj2}P7et3= z0h`1^!i(Y@VY3K$4zNWmBWx9y2-`&H8o*0p72#!Zov>Z>eID?NSWkFW6cAn$L)HRz zh^>U3LSF~iCB^_m_70A99Xh^S80$IKogC|W2ycoU3KuCf*Z?6%#BAVLcX6x~_KG?i zA@q42LdHf2dEyX-+Z23Xfbh0RdjZ0h-4M=D$QP|PK^Xl8gr%Dx91y1|c)tlD_(cfs ziG?ph*hAqmg%3o)W(d)HAgtL8;gGmQp>__0uq_Zi605dAI7p$0!V%GTD}-6O5VmiH za8wjfXt@_c_%;Z~#MW&PPEqiA3BqwP<|PQ(`yk{|I3bLeA^7J(hoe-iAKv=UA!Xc>}@?G3E^j*&jg2qi|Cg zZ$j`t2qErG2)9HIg^Ls#?14}qV)j5-c?iN03U@@E90+|rgpiQ~p-3E}aGQc}E`&cs zS}uewA3-<+!J&JJ)_ZZoj6Muy>0T&aUg8TX-bbJW?}Os>5{vdh*+b!RCF;HlrOyc{8Sg@A;3Yn!a+`|pdr%sAiS+lNY&i+#43#GE&-;iBntF*j z#Afgg@qRDS@dIFU_=ecROMFXw0G>GrZ0RMI6I;PA#MWM-+aX{Zc!k*3OZ-gqg-<>N zw(}Ajh!4Ue#P(id=tqu`_>VDc{RpQ=N1-1EJS4^tI*A>G&cZkXz`F#3pU5HLUBbtJ z01*QaE6;NHA7kc&M4h7$`h3IDAB7Ms4pF#G!S@pgAtLP)2wTo^>=Z&pt78yGe+yyh zF$mqoX$s!oK?wd7LQk>qQwVz~T&B=l1RRGD{XK*=$076;mnhUe4r#X&mbJ6 zP(&e2^gRJ#)(;T2pMWq(6i{e+0Ydmm2t&lylNi!ap??k-CdLqO`x8b8;}ih5KVg)} zA>j5u4HzS02)O+TkBB;70C4*g!o?v1ZvQU<6GR#Tw?835wE7Bw+n*3AP7@}J4rc&v zv5+uDoFhbufUg13Vi_SuTq5AoKMR;9RuORN6XHbQZveRT2??TrfJ^@zV7k~!ND}(D zfMhX-fJ>i{Dva;o4F6x?jJWUMjF}>b!bJ)VzK4(|V!ns4@+O2M6lRM$=OOg@6+*^& z2pQrKh1(Q-e}Lc-X+J>Natp#43R$An1qh>WLs)tN!sFsJ1@8g~!51OS6ALdw*hAqm zg#{wu5`^eN2x~4uSST)0sC@@Q*pCnvi&Z~DI7p$0!cx)qCkV5CgRuQ42v3Rv2#gK> z23RJx5|#`7GQbpL2v3V0gcZWL0(eG55QNAftQ1wQ0#=C_!fKIEcvjT820%POSR)P* z5D#1jtQBbl!~=x&qSem;!~=wl;xqyAzzx7Av51e8_t#NXZS4a#3_BET1O)_C3_uVi1Us<1yRi{l#1=3y0c+abonUu&cQ;;d>W;+)zj;=HmoV0_IU1$BjU1hPJ_6jj*Gae3fLg7sTdL0 z)pZd!l)o+FrW%V-RUL`wl`Rq7R>jky_$rFI=}_EN$)Z@{grZh@6!+E4^eCD-qxdC? zhpJiz6gD|ftj&Ppv9is9Vw)(s=RomPZ5Krs7Zh$TD3Vo}3koM!6sJY;LOHvlI3|jr zt|(rq)^FU#% z!aPtod7?NiiuB6a6U8x64E02jQ5_e>05252UMMoFfnF%Q3ZS?x3OiMx04YWHTRm~TXQ!Nyc zOMMfOTh%Lsa91lu0vJ$ygtwg38o!|mQ# ztJYQYj#29i9y@o`vF`iM6@5-E9@}Ha@R<#YHLd&RLa(?9XGdoX3wU)g`e=~Xq51*S zcJDejF`>qeF;Tr^TrX}P+uZf>^WXY1yl%F(GQV~XlV6F@K88h^#+0kr;aq`b-y<&G zdm6ca^Q&m9CJqO0xLPglw&?Y=$;N=n0j(xKa;_U5efU}dzxeQ@K2a^3*FXDaY0+Xg zdmTTUz;>_^r%Bwn|S=l=2`ehVBs}wriV0}731*5>C?(QO_t@` zx$b0vc5#tuqtDxvygtA4>N=BeZ}3!Bi_1?=mA&xP*$XfXeA3$ceMXlT7wbNV#xJjZ=N!(jslFG!WW?y0lq6aD&@UGqEX-;XJlxzFJ6_+rkd)2w>_ zJn;01%H1ArEmppH-ut$ZOJkk=1MX!vJRFkGKa*~WI?%oGo=%3&>E^C< zkKg3brPtf%>+YmA#`hnZDtkRsXK#Pf?asG~@2RD`Fk#FJU5mbJ_w0T#aM`wp7n?rx zJeh9(fL~+!W+~s~v9sOfYkQt2H1~fR*lNxKfA3h|sujJqGzg`zqXTnSDHPiCFzof91%MSwogjS$ehm?lL0=h5K|X ztgq=jymOl^uaXY*8t?z9Q=K#wnjSp8`t7G`leXmNr32ZUc4x(4|8xGcorld^+5F1G zF6R#4xVFf4w0>p!z?^p)HJSKh+RMzJmMpsV^w-BcTQXPLf3sOhhi#s|$K!qSY(CNd zj{KiQ%gF7QI(yTyjC4MKeNkw)dO4nrnVZ<|ZQ1LGTYMgI%wyZpuD!-~syb_P>5$XM zPdcw39OB{J=vCnoepy4d8H(QV3C_IMY21|>*Lej(_J;XpbQyhig8S>a<=YHvcl$zy zZX06r1-JDH_`2pn-sWQ#Zy3=)=Ml8fZr{mS2X@4U*{&X3W&egpwVUL8Q|Hypz5{xv z%3lA}*&A~$uHl#$jpp}iG}7}>Q{RIv7F2&X(l5DB>5gk>@2lzI{PtM+KMsl4oVy1X zZPGV+Lfrnk>vqn+c&KT~9Rr5EzWlb|V_wLRJ-7OOPFFvfdw0w8w(G|>9cX{J^6`p0 z54qQ=v1U`hrL8VkS?Utlj9zl|g;`X^-4P+>((YW&fWfWqHU+OQKPr{&uy6bTr;m`jTaViT^k+s`kYhO1J1P)+l7Ce zes1j4+C`oY-V*nv`NmA)O?LU8FU{*AvKQTTOOcs2&IR4N*BEfZvC_L)PvYB6J9~3q zzn00xzdf7%cu1)&%c?#O?VRTH?&**Snetf=N8Ry$?CRe-cWRrVC zlbD2(H%A@{Dt7Kq!Ie&Xhj(xJeyZn@Gc^m2AGa{~wcqhU7xv^xmA#>iOz2iy&hnuR!rJ-jh@&GBjtGS}I8Vvx$) zHhb`yYo+x=;&SsMkL;CbdU9>%`XLvmt_+SpvglhxmFvy79y(fZ`lbojrW!|u>n{i2 z`+3JJ|LX@8t~)#`a^z|3=-f%ihg`ll)@e*#$Heu%scJ7Kb?yCl-1+6DZLR*)3Oh3O z)1ah!*$S;purG7;XW`gyzmB_yWiL47OTtP24bIH;p+}AofA=| z`?6CLw#BriiYGaKdTf*2D0A0jUCY6vAEe6Oh}79T`FLFRy7k`Lsd)!G=I&j&^vq2e ze;srt%Ml zAK6X1+g2^oMYfIVQ1^gS`udAgWp7mK>|Ni!wDOedX&wI9I>(i08Cxw~t+?lt2admW zqUY;6<$GV>oN4N!S2cW_InUTR?XY_~A8((ZclwO}9@WWx>w;Pb?M7sE;B_k5dpzt{ zwfFsUhZIb_8L=oXeB_%BBeK0W2Fz{f5_rcgyIXO)(_1>6uUDXPeB)amgDTI=ZMPup zzWH9=n+=#U`b9*Mk%v-cZ%pd!WjS8u@~e+0i%(h7?%bq$ox1qtJ@$0w=m9~$dd+?5 z?_cHMX!_Sp>n@#dy)ehlZjnPaHE?UQw&25gyxb*w6^i&T8$GA% zb)Nxy7H@XG+;i)Cdd&oc?!uUNw`w2iYfGI^$9BO~2AT z1{+T5v;F>7znfR3p=lD%{m!mT$|F=w8Kk_qJK$JvYP6BJ~#dbc{?F z->~%kT6I!oZ+zt zrfW&(SJ^yPCw*xh;NLUD==~FJp3Yd~+Uimp#@*=0i*K^G?%}B*=Zv@ZpS^r|Y~PF1 z*FXAmDDdN?Y}ag(j`wm=Ph&%eM0XCejjnt2V6Ptfz-bwpy2f;yUgDEs<>>AYw*}?4;*f$j_9ePM;Wa$jd$Ty9O1U#vCWcO3W8b6j!NA^* zO*?<-Fg??|!&e_??KLksGDFqi#oo>*0!}Z~mui0UdTjmSmELYGS$=S=&x64c&1R;m zy|~ob%bs;u=;q1By-{n%Ms~bkwp{Om&4-;`amDwopo3l7bi*7<cx@E*1c(-K5cSX;rOg|GqgBfqeWcrjkPO` z|7BaJ=%mSU0rGJBs7aWfdd{wG;`}R)^bGwa>U%zZfh~w=i&Q0A*we8ip^;z$+m8KTPr&_kPz0*wm95MNv6Sgm(v7+;rbfvy@Svh&$ zLXUO>b2Tn?Y)A6wmG6?n`=8A7!C?J0eA(h5^HODRX6o$i4b$7b9z5--{f@>X@;q!% z*7y9$*z`{GW?p*r#=eyV2_n|0WD#psNG&27(1wVX)*_YXUO+M;Mw8^uPour`WoqA=D$ zv02rtL&z;^rHHM{x-KG7H4~AfHj3D$Z0jMmt56X;)OHa&m0f+rE)^zXx7sgak8*B+ z*sFSr*r$$**st6hA`Ym5A`YtaA`YnnjSz=bjEE!Zx`?C7zcJ#N8Y|+sx-a5{D&7Qf zQYDBurIJOQRv}FhXVgp)XVp6q=Tx<3i1TWphzshQh>NOTbHpXJQp9Ct-2!n%H4|}F zZ4_}$*|tPnSD_+qsO=(dD!W#QTPjS%ZM9#-9p&5_aaZ*gaZeo=abLN&K|J6{al}J) zUc@6+pe^FDiV^WdU2m&TVjCNRn)zO`WY#IrtGWX#CgaD<=R_s!^@wLF#S>EtNi@lyn44n-^6x|hm{q_%S2C2 z>fV(vwVOA-HWXGQRY(-Zng0AT=|4tqN{BQvh~4zAdK+WS^89*jl65=%S!>Z{ z++UUR#Yo^JGK=V!oAhp7!y>|Dxo+TuS$?-QTK@Y`=djKlBarn}?_T=xx|4O)?_PQj zbFO!iikHNwvu&7^sgFLZ-k7@$c~?X!=S3S-uO%e^MRY02tCsyy_V22&R4ly|Ito^jDyDcL_ILvXqE@e8Mk>#xLv+V}0-v2S~R9`=m6XGjv3 z_oaC00)8+zy2*38ki?&4%76R} z(j4_l>j&l<%-FGSx3C`6pv!x@eiD(*){Akvu6=z6hIa2}_2Q3eJy-u)=kh+O_B_3} zwYk$3HbwaNj`h&m)KY4}LcN221>e|DTBtv5TxM@=?%og01^;^(zQ`x7t0pYdpVRrC zO=`Vdud_Bz;T+;jl5bNN%Gj$<$B2lqj(vQ2ZZs7~>(-1$LiKzdP~x4wLH{=6mQvR8bHUMvk@UQI`drp*{=03`ho=mRRGHI#X_GW;i++)* zlNi%-tIUvO*GE6bl>NU(Z70tAW#mg_#&<1oD{S_fx+(G>6I~C z^o~YT1^(Pk5!%FBGe=QczS?Y4(<^0)`kyQHg#EwT_+M@O{TBYe9`U~(@xLCCYEApU z0rCHh0in`mxo&<0FZD?EUTP>C_4|+c!|KlJ8b2{9i*X(fOdq+HWuG7iUdrc&W`i*O z@V)46YC4Hoj)juXElp=SF2Jm^x{Xfal3!+E^(3DttGg)qXFAxy&!pqX9nDZqE8s!L z^syWpYRgp-O(#c)S~47@>7JmIj7WxyYC6jiqH>l)B~2&CiCW9y3mH*V*Oafc2;~5z zx|;5_rpwHAJxwQPkV?XGyhTGz_g2%f;E5A}63h8Otu@^T&CZ_dH)~ho+MQHsxeM`9%HH zlyWj_F-`ePi_isKNlo`hvvWoFL>f>&)U(ur8zgHwYfYCEU4VG<(P_F|xYuZQdUO;x z$|^U?RwzYpG%Ky#affL-SqDozdEhshk;3r5?@h<_)3Zn$McRYFQ;3WB?4*Gk@ziQ5ycQL^ zVA!PTE!*SIE%qPO(z8wyW(&_j$4;cw5F6Z-;ZjB@_J4TOTtM_H(b+| zLU&u!$$K)fD-HKF-AGMW2Hg`)CoinTt}MJ42cOY$R;jql!2wMvua3kS0-JEi{1A&y z`dN7xt=UaNCvjJR1dva>rmKj1sFv_#O;-t>t@KJhQ_)E@m8JaFno_owMOg*poFw^7 z*K}2J-`8}q$18T#z>`E|E|`H%#=lHtUYc&bW>*7UMlI|HO;;0LCUmCymosK1v$Y^p zGu*5h)<)M3okYGx)78P9SF_uy>14JXs*RjPO((PE4RraDNt&)c?p>PQHaV+SO40!K zpp-GWT{DzvbrU*Il0$N{*LnRf6)9DE*Yy7suwqbrAeqJ`~% z`;um7IV`s$y4fHfIXKsJR(U5bE@_?cg%)9FbaJG8MdV9O*9EtnmoA@Inl22t3qzv{ z^0lVxirZC&3!gWdE*$qettOL?;~}6d|YmN@n_tN+RK7ujvNhzK>^nqyxG%(*6VCx`g19Lo*zN z`-Y~IACwj6V39GxJ<)MX@Q6SD?Ahjj6kz3PA+EJpElDlil zXxuXO$|sMei@_~F#V)mxSJMs0ZG%oqo=?+_z-`b~~hqStM>19#yb+=mD75acMZo8SpvPypnFw*pej^@>QDn}LM^Bbb)YWPgZf4mH4Shyghn7oRobRi zk(&)UonMl%H}DqT!F%`!kJQ%9hP?Uj;Pj^rWRV#NMIac8LNO=~CHPNg2A3!~+H?i1 zgjKK_WF5K|*1>w1024t5<2YmsXbo+k19XH=&>6ZyICO*V&;xoxFOZ+5{SH6j59s8S zPdy$6NCUE7wShZ$N*|Oi=Rhx#C8jJX9l;4?DVYOYAt&SpcgO?zK~{lY;0?0+lLfUA z(txawZ6Q6J&@;}@F>=nsMYsf);VQ@zo-7mPsZN%L^3*0zX~#iUh09?Dtb|pt5Ej8= zSOT(2lvSau0yo1J*b0dttHABBLvABGVHfO%eXt)`{#hM_!yv1^V{jZ!fUNjV!x^{$ z7vTzAg==sFZi1`=@4`K}4-eoWJc7p{tG}qH{BegokQd(3HDn1WE55^U6y!B<0!)S} zAUDPs7!D&~B#Z)C{f&ijFbD?25O4ukaD$AH1?+&A5Ndg%p;eUJb7Z3J1f8J^$jy_R zpj8KW#^9IJ#pTSw<*)))!YWt|YhW#`gY~chHo{041*2gM#KKsR;~;&YAo$wQ2@ByU z41OSIN4_O@eW;Co5D5ce5DbPPAnVu8&;^Xxr-8I!1G0`y2lMco5Au79^28-eSslpo z^&Z@Z2k#CyI?oSx>VMoJt%ok2vr-C3@xG>qmmV-oPsW^%1CHMe`^D6 zVJ6H1xfjck?qSdYIzj=;)fU+VnnE*Z4lThOKl%Qt9H}eE?*4@DAO|11Lku?KWmiBB z|GfuyVIUq6&==&JD)LRKx=;`5Lj!0Cji50!fuFnSL zP7pyRGa}{Ch`LY+0>K`#fE|3JDP_k(b|_>+B1(23WJ^Rgcw|FoH|&AEun+db0XPVU zK-T7KVIAxsb30)d?1g1rA^bS>Y!&^b3B#bP`tZ1@}vM1?`~&bc9^+k@lDG?|gx;@D09OtLz62S+iZj zJq@P9X4nRkRPUXJDp7JmY7%UN?XUxO!(P}2`{4q}s{RN}fQb+X2{0LEko*LQh8U28 zYqLQHkX8N!ScHB7IMe7kKvwl`a!1RF<23{H4ZHCa@4zkkUtwMLRR%u1;w}!S53LW7ZT!)ba83m)k zp6iJu6$kMk4+ioekPqYx<4H*lt&yF!pd%AF7SX0Wb6tYqhy=-MFP6lQ@YJ_=uJK*gD1#- zQ9F{5Gr`9a&0vUzeqfC*H^C#}JV{-E7#I!#gbP8IhYC;;DnVta0#%_JREHW+QznjD zIBG*3s0;O=J~V_z&={IPQ;$n&WW$dhzt$O7%D+z!wYIzeaX0%6bkaB=m;?Fc1d8V5kobK=!6&FKUdHdcE6FJ1Piw0LZ(BXRr(A z!gPp*AutsBK`+P&j_{qPnnrCVBR@z}!WP&HNw6Juz)qL|6JaLyvtSruWI>Qo7LlIx z2ifkCW7>mI218LO2F0NSl!Q`H8p=RfC9x6aZs05Xv3RHz^P#tQ3^es2ikewhW z*hnGTr=|JwaWR4>I1DE>DNQKNcM&eZS&+R8*{k^fyQ!$<|7t0j9saYaB&TiAZzYE@ zFdRleGz@~mkON%63f!=lEucqm8Lq(`ysk1T$KjWN&1oqBwfvsKo&dS_LaGG^4RxbR zYZ}o?HCTGExR1gpVi*ZF0x`<=%M*ACvhDH`Ucqg+2^p}Fofp|v@#I?K6$~(%II@5p zHgE8IZEaP~DhR_uP#FBc9}0pmNOi2BIxK#&V|mxIqu^(Y=GmFqO3ADfSo~g(I*J z9MQ}6;cndWR4eO%+OUGcNiWyqmM5W}AnW2u)@%sNB3Ty8(z~UnOOKbHFT+HJi#!ba zgCC@Ww5tm0aKFPju`UlqbB2y2Ku*Yr%rb8WFJhnnqum|K}U^iGE2*fRZ2SGA_6uhL`kHB#_4X5Cw zhPdy8G~*4p23H|HT!D*l9*V&^I13lxGF*b|Aa;Rp7w*65bY;Tb%ICm>P01{-U04||VWde&$71bN{jIu zLLu33Ne{jtPXj(6YfpJJm&bN_te4G|d>|{5OppOGLM{+H@y`s-kR7st17y);R-_%+ zgCjUW4iK9TVkA2_vXdj85qUjWB$6uTe~ZYl8&+=;Lp zmcbH`LAw~a5Dd5rf;TMSdOpmBIWQUGAr81;!3wU;^()N(rSj32*>>xjuvpKpsP$faCBJ7}#DW=bxQ#k9C{LT+$} zyxnKk^_X4@Q;;BzZWJha`!> zmki0CZGET*vX@&2YC|ok2{MdjxXNb!?@-Cw>lB!!9Oq7OvlmitO?+*0UXNJ&Tn%1X!`BGC1PelP%ZdTfxt z;1hg=_s|vFcj89QLcWC|@CK%V#FvNkO_5GT5e>sYCd@Q==7T|8O9sNx4aF_D0*P26 zmBnccOoE9p0me7vk31=kg;*E^qhS<`gb^?tV&El6 z;#-iGQq9C|E{VAyMaaN(<#yTNQb6G)motzuEH)!I!A95s>tP+NRmqnPfl&)^&WCw0 z7v{ihcmZ2MqUs6HaVNuhcm_}52|R{J@DT39J-7?E;TGJ48*m-2!Bw~dm*J9hgo`*X zz&SVtCt({Tf+U`#NlST7aJ^mA9Yr37gK!9Tg6T$x+zq1N3wvM}h;BdZ1JUo3iOh0w z0L2l3x;}Pm*i2lQ!?GOT2r%6P;XWxDvu#Si!npUobBz9GND7mzi^Z%zMCky$etNCGw_ zAR9Cixg{~NmqcZoSN57^&slaJqOd6=FV^LCfV>)zR|N73L3S}@$EP63J0fq8NaVed z+)4ciD33!jgL$FzfP9b_+#xr}T~>CBvVpu?A{o=$g|W!2T-!lL$PmRJBS;Qyk+Q!p z`~0%;lNQo|=;c~uCXme>+4YvlWM?otIDs?BHkB*LuAv*q%dI@%iEVzQWK`Uqnm$TA zvN%2O;I9isU!|bl%{ejzly2+#V;L56GTH* z{3K$Fy>u?AYPlA7HKa6~#b3gUeNCy#)Deq;RGVZbwZ0zLbs&)DsjFVyG6Z&S#brxq z0nMQqGzEFj(HI(myya>D^4?bwDW!bo58FbUm6kP$lpIUeL@HX7Ltx}cYvVz{!q zW3Z1JhSw<6{U8E*LJ#N;eV{M&g5KKoK;$5h>KOq2ArdUR3F5z*IAza!Fbs!b;7yVl z$!ZMlp%920eHoKdJfcy`(2@0=1d;&4NZcb}42*-Z5DT)peIY9!dv)lJAz##7baP-f z%z~LP0~Ab$X)qP0z+^~(c!+})AS;Vq$eoY~TVV@ClLrT+^oz|%xr@f&-h_K2NIf%S zMw#AnuR*aIR)I{tGV@8LTc+V&TuTHpH^^r#elp$4)VdzG*vs_>+#`rs65WA&JH+Bn zLQ2BYXSN}SqZ2mi3v)|Eu^0Ah28WP`;UF9UxfV><()tw|ALa5PJb?Rf7w*ALxDMCg zDwuQo=g$e;(@Ad{9D~Df1>I#hh5HhcljL^XHF!i>!icnlNy27790bwbz+Tdkbnn1z zxCP(wdxU%oZ{QWYgctA}p21Ui0?8mxE3c8V1tKduZun8AJ=)(Wzr$CECQ{jn3&ib# zk+huLQe>m>3%bwn3C8315%~dRq?ZHHNddp1%f$5${U%8bmKg+D?; zB9?D+c&UN+4NcNnrik(eb>qIFd6ewnSBA2fRn=s5q~-P~`dVCVYF=&kmADxfa<7DXnbj2^QO_=rns5lVLLq#YlE3ERy2~uRJgWMD52|DlZ=e%L{ox zT1_fV#%eTS@^CFLr)Pr9;WLmjC(7Gvd5b*_hNG7^+tTXt5Fp)T9M{riLSZ0;lj*_8 z9N3J;ub+GsUcM^RRScmESV|+(8AExbiIu<_5}`yU2}oG+>kjhjFgVaG-3EJzm}1D4vHj5`71Ar7L(^T(4MO+-47qX|eyWOfbF6~KQIdg<$8C-zg2a&P?4 zN7dA?5>hwiJnZES*C7g67*62k6$KSaN2$wj}y**J7VK!>QdnG(X9#CA?Jmc{m4W;WR`Y=g)39 z0tet29EHOmRdf*cg8VNN@!t>oQn^JZ2_FJUNNPc(B|ec7MxZC0$c|WV)7| z9xJl9__SQ37p3^fHjud4Zj5L5bb|tR+9SsX6(iDfU20 zMWOeK9Y+MdFa+|mQ5jzn`$N^@rNP-%b{M3E*415+xBBwLMLKD2ipBa$r{Z54oPyHP z%`#%~==!u#7w65thed$YRecaf!@w8N_P_P0(q z3`xYmp8^-*@le^lB1?^x55grBI|uC2O%IA4;@hDXc0~#Wid{d|M(kqLz*l5FPOTL& zTiqA2Ox^S_I;%pj4LSIgu4=ChwH-T#NlRH(ndEi4Tiql3P3Z*|@>i2QjjsIomaiA~ zkHr3>`t#bbm0!K`scLlQ_x{hmq5sNqSK7YO&JJh0{V^Ocl}Cn#X}u|Z@o@LUiw-Tx zKLHb<2`k4LI&1o{pVXp!14OFtabK%R59_y_C_crhcP20OiWpqvtH#oJm27YP zT2b|ifh6co67&RzUy#H_RZ_%#bvq3u@A1x%!}S$mBwHhoUvjIl{%8)u$fyZqF>O?L z1{{S^9yZ0KkBK@I>0;vti17&ZuX+gzWJWRw!b7k zMy!;jsA~0r7!p+f55yOvR)1iOJXLFHIo0-~VK~1*wy`j+7^i-HWLkJbjYy@e9=~w( z@QhzN5RtSM5&c3fHQjsJZL3I6=PoHecB;lF>eH3pEFHSX!R9r;tn?UY%0qzExR2^x z(CDBFdmD}XO274IjE7<=b6RujIX5eRnJ>LxCK)O~uTB#ihnUy$BU>i(K$m|=oLZM` z9aVKcQ!rK!L{UMMrBp^vFU|y1*?HAv;_r<5ZqVXw0qvNw$btf9m@EP-kJe*TI zMNCm|L^)r@lr}o#HpQ@pCkE+@Cogz+@TyQEFEJD~$IVYTf5rHYD*MOatoz}p&XqPg zS?5;c(Yge2uVFbKZC7!1W7nI->D;Yl&LrVFYOe(As5btAE?l$g+vJ?s z1yJ!Ac2xC#ne0qry$bne%2^A9%X=OGxZg)F+!S*9uFr8dLIs&)H>xF2AHl-^tkj(UHq)Ynq~CSOfFQGc-2{c=;iejA)z^5kSc zlKj8lb^H1=tLPIt>uhLb1eu#Hjj5K)si``UmDU7M5v_w+-nHu76-mW=woBM~ZdiQ__ znT@STQ|ne*Ia4*^-$J@%aW}W$kQ%EBIj*hTJ0)Zu_3k&hGF5}K%fEMAbC1>X>*B7( zw|C~m*U>jS%_2U3^XQ3C6Y1HhI>HpSUldQ&O@zxYT7jC2&X(_*b9A?O85u$5YLl9E zcKMfTHT6VOhhijJ&7Rf@)1`ULQ9bcqt=2@397R;-L0}a;Ex{O*YSQDB6KkU*r%#1i zGiMA`gRPB$9N@H`E0-hr%*%``n{SM_>XX?)N@5-roJ3U#^TFzun2%BZI<6)wPJ8R@ za?8WCE5LNNWmmO>lVzmJ{7VBs$Jj^3C<7g0cM(;EYHLkp;vdZUx;GH>&3XMBgL8+Y9}e%BPtG~5!sRc?dPNq59s zl`s(EytnFsw63TY8jQ}mQ9f!H-mWKo%+tD|X+T2`uQ*HuGS;a^w}R@e1hny0`HaSZ z;EBHGS>besT9=w{eN#a0G5&>uxNSbhNA5mj(iWIkYw@P##3A>-;w;#`)dVA%=v7E9 zBaR$BIu4M9gjLM(kJroAZ#_4Qb(zw12PP!-B*9oj|GKc1a+_>)bo)}+d>7CydwZf& zmu)^eYiWE|npW0+R;nH*g)=g?B-N9Rr=PxXX?Po*wO=8O$dHHT6F9m3p#5_f4AogX z6G%-q<|-P1m)xsj{4Ym1_)KYPu=baTxlLEbN2bboO-o$S9c-U7#iu2KUrDC=Vj^8K z{_ToWl_O8(O)(jdiTf0zW6{N!FndO?kBWMh)%MMd6q8-}$dp}mv{m(j%bQL~@wr0a zo>Pqe`nNdl#~B@UR{m<&6r+=WR+>RpwnY+`PQRBcOCJYon%ngG&(&)dYhP^rRlSME zZ23)2f_3ITDvOQ!50`~cZMVKL?Lya}x~=$0PnB_!(NXQ1Xw0FvT)BDCEhWIS72`5r zxsrx^uM?UqXe=fw&s1ZPqTQ)vx$kxPHYD0mc54-_*HMcD@Ug?^-u`UAM_%Kn$dT$q~Rtj@E+2;I;WlZ}|{$E4_COtKNnfv*L>u0Qr_QHse8&8OG0 zGTVj^elb7A=M8}~Pb09AULpLZq?O(@y*({c^{tL6CLJ(Iz(gO7#r6}nxv;H}_~Z8ZUGw{<*g6!oQofkzi{J?To5SMue}uLqptmIXf8@%Nas6LK zNi1!p{FTw)ZD|?vJ+58ve5K2!&s5AEO|b9m!7rpq%_7HK~*Z6Ys2f^oi}|Q-zE{Swg9V#k7Rf0^m#9h zx!Z=|LkXpf=EntH>x$}UT4vtP7|Q$=*(AVi;*i>`G)$Ssux~}RXBG*Xm#(@Y6;)dT z=;A7>L1~ROgNJgfkQ7=TF8IuOsAp{gGRP!_bXCj~l=Z66gJE$&n@v7)AGFdE)&0t( zvhypwu8*;lb#S?CcW0E?Yr_<2S9$6vVEgLkJBEk7;o7{_oi~^b_LSTga6!FkVO28NeTK@S2@!eoq~oFHVa`R8x}O?KQn5QIUCX;C*vdav-{N6 z$~H4Aw@8UKMun&2e!WmjcJRy2r(?6e$dh8Qs(R9bbUYVJL+?p%baMHJz9PMo_A>V< z+Btm&qU=>eyP?@6dsMG|;#5kTbgrn%WuV6Yp>N4On3#rCOp3^047S#})>dmW(s!P! zxmoC}cQP6itz33>nw8n8vo?DMy0)eVO4VJdU3vM7xcfy&(7ZIWs!~zC z%<@;4*E(YG|CPc2>dV?zS&f}^3md9T_Vn7-L@$HNHSdQrOZ*$l zv#9pC`=z03iG>z8Ko`+S9g+|`8kt9vr}dJ`gSM7=PYBseVayzDq_R2?;$kCJ85!)= z*gVheyzr#mioRRMm@HZ7@Mxp?EDF3*d;ZdIMNK}YMHH#7b1+6r9Ddm-LTpo2Jsaci zA5%58|3ZD*^M=IIa{bM*~#X* zR%#A<*Ga9-Q{I+uId8;A&)Y`8qJ;wFk$+}ubqAP4sei+%hs0F;5Mq46H8F@_K@yZh*~hyn5{S!SiG${-8*;o&Rrd_ySUtT z3Bt|3hUOEtz0`^#X}Uj2@oCjoWpJjvJvEE{j}undJ68FUViD6;mBS)<3KsU{$gljY zHc4qR>ryP1X+Dcq4(s~bCjW&LpY3hcBnf*;v#_x%ZufqyZFfTcPwpPRry)> z11UZq+NxWUv$UaRivw1#t9T^q%B5JigsLn#h`q38;hohh(0csCg((&lG@o|af7)iw zZ(Zck6pMYK>K+zBS2T-mZ5REylCV7_#o{GCvWhytFpJwEujD!@J{Q%yn#Mra zon6eU)}0R7v$l$xIox2)Ew`AAlPg`+5EqhvkAdkuny4-^Ezf&yi!qf&Vf_=mI zY7iD_A})03cIE?3W{IN)J`#3wenTt2XQS*>e7c6I-x78#7CiAqkB|E`Cn>Mp^AwBK z_~gasYwMZAip)9jC&lMXn5yYY&R$}X4~tgEdsnJheJCBml5Uo+DjJJmzpiGB$uX|Y zhDCnzO0j5&kL0=GoxKake2mGF;?t+AIwN7nV<9U>w?TER9}l@4ono;9A6a(}uQ5OT z#maOwQ+y6|RjzL2?2Z;Tw$E(O+p*O$rdagvqv~Q2Jfe?z#>iZu^3BYpU&$hlf$vwS zs8u{Z(%cy)S88ZC@*3-PZ!7a>Ssyh|!tTOC8r3bKdXM%Cru0v-=#P&K=j`@RYI?<1 zmCavmE1_jy^-^-yzpuHzpVS?suhnR#tc6HdrV~rf{ymO=%(H18G>3Ia6U@We)_&@B zKBl}$t<|EO#sTTlMw&~SV&i8uqK_(*i;_6tC;OCD9(1eZy+3KYIX5!><-tep>E-J* zs#9y?lszf=^p8{%2B}8;@PZO4GJHuiG{Wc z+7}-gbXJ}A9C{Vs` zXOkBfA^lZ67Qqern|suY)g714yD(oi`n8(Fm1I%^Oet^0ovG>=mlbjCUI(P2( zBQ2*afJP2bS>25Rb>(X*>Bw>4u~mO>S(GsyX|8`r+Eveg29e=1J6qE8 z9HVxHQw_2N=YExAh+2@(nB6VNYPe?8wp#o2dB!%nVX7)q_2yG&^RnLa9cu0|_kW*W z=T#)*3{(A^_M**^I9Zzv9oc71T`{izIA%=a&0IPM6`jv$`2U$lCrin4|KEcn)!?(q zPks2ss8ab^)>e%%Pg%P*1RN<{xl~nC_D#>frngW|MFZMlA;alOy2*V?et98JIIINZ zM$iWznN&)2TGe-oXX~!`Xp_pY7`2DkgT`PX9nQbh%-_1xQ?0O&cQAyF$49#Dt!8#V zPwca~gpUkC7WSt1SkA7qG=ucp4LuHwn_a_{tZC)6Bu166(s%DP%NM}5<4$ucGm$EZn!)t!k^D?C`lm|l|w>aNA8ES{(za3qH(tFd=6 zYJexT^dm;C5y=DBJ)}#z;pSY&weB#M6B%so8F%Vx;S7U3o4(u*;Z@wOgo5y zHnW&ssufY|y^MjnT*K8PufLX1%C7)>f(3`Gngz&0&~VkMfU&0Kv9aQCbw<>+hpT4= z2=d?WT6E2ZtGeEpbQ-R@dXw46;c6b&t}$9(CQVORc_N3Wc};9u=}s6v-H!m%hO3tn zpw%ud604+|=mT(6fzFs_JsA zx%p-+Upu&dWDeN~kQ5k~v&O1SzQnjrh4`}l=`v2W^d(Eh#+mPoKN_@lzJ6z%W#@n_ z)gGtfv9JWtHe7Yu5X(h34xCD0JDRMeSIKhAe1@(eHF~H_hU=7FAyz^a#+&ar%{})A z$HX_hfPvP{>yKA$3(*tLU?Kf&P&uCmfxZpXW1&5QvB_+@pOjc%v(?jDArUD)ArsU( z!s_ZyP(P8b|88lqD9C+82GiR^Lwn7+dg>x^$x}F^Y3wBBSD1WEo@6e{+x=^{7n{*# zvCev!R2;+2GtS&--(ts&iU{*)Az{spwk%E!Bdp864`%ZlM4elLI)$P3hEJ#UA+7JY zv`;Y4rE7ZnZ|U~B{X7!T-Zq)v^f_diY#te(XUtmFWnqY|GIxXRblEWfQg?bC(>S6 z1i!#Srl-V4%^W|J&b%87?alv}hURj(-z?nPv$gwEQ$bDL<8zkhund(FkF=@%V%5MkAXi6Nja9gFGvCGUMXdDR<5R7H@ z*{TyVc<*d;T{s_0u)RHM!)i**7aF8K&*0-g9M@w#ewS$xE4_`In)p1JtvuzmZ}o*(*C|X&j%ZB*K_VSD5@!k%tM)36*CIfC;bhwSG8(}SR=yq>Z3%GVYN{DQPNUlHO zkfYQ24Cc7xu6t{vx>SbM&!kP}PQ3W!jDhpJUI@dY7%%IJ@MRq3UY2$-Y*v$I6VNg(2<%vojyPv$R@t}5rMzpZpL0FVq)zy4{6&_wAYJ^9<6ra{RRLu$$sT&qq z2wQ*AiKHpv)q18_4B4TgvCzfsP_rs9q?YVZXSfbtO9;8irm1ms{+b(a20Q$f+Q08h zChbtJWZm_MmRj$GgMB;-b{?3rb8jwOfNsrBHL@ae#$Sab`3op?T47brN{l3}DqQ1; zPa5<3Z=Vlu`cjvf#3I1+1}Pq9)-a_Qcpr)TsPSOhjXf#+Dd0Mq^8iu@@3s zjK1G<=PpaxHP7#PKkw)D5AWW4%A7efbLPyMGlMSZ3o$9Vj`S<~vHGU=N^>Z-H~b6A z>n0u5Zr@A8x=><${v1=J;=y37+Oh^7beH@Cs_zr+T&;#? zMpmtB_e!AQP*6q_TMd>eDUa7NYNZV=l z*pmJLg};pE>~ui%Y1cNNY-^X2&t5nJhCCe(0i-6xm{;Gt%uNj!NgkmkkfbZswbR&nlF`XKw*p6^vI>$fFOqA2o@X)$g6^SNy9>*8_I$DS&X~w$o6{?aQA@BbwS&*;pSG6e;@B=r=4S>O`z2|jO{A4H%)YrF&sv26%5a#3~so2zEfWPRGU(^p&9&P-^o> z5y9d1hypnrM|lGw+*Fk|y4T@dA1vB2(Oy86(^Loyt=DP#c_7>Uape9Agv~xJM9LW5 z<5Q2^;Ak=M+19-S2oK82hvY;o@xDm z5*VGEwYu%LIq9B?0m+&J!uGNEYrS^tn!mrX3V~M?`xAu@;zm+w+x`AzZm(gc7(4B_ zpC}F((Qg5Rr#S8Mi8ag})_kC1K=zLS@df0MO?T$>epLMnJNG?5(H5pX35>cd`@wHx zPn-@g+PVJ)2xm9xw#~VG)$@_g4)HicPg%0CGlF~Q(wAdb$GpKS2sM7v?F@y#3hv3k z@B@#CyiMEgPlyRuF(BDgKv?FmDeI(%LnK!_ZT=a`X4>t*V9gZ%xboi2KHsqi$K0XS zZ&k>HcFmp~%9iigxt>2mhe4}*2n=rT&^$w4^zL75ksZ*w{+vG;T?Y1{?nulinQ@Xo zy*C^NMYR;#N@r>7FhF(;mZr5Bd{)ejr`%hcJ-vIrtdyke8pHv@lSK8?4Rkp-h&8Kj zsjHq`%2}E<1QzJVS;`zLdDFEaQlR#)vs7^?ieGn*+6{%AyUq!>?Bx+je@tIhfkUty zuRy;LWWLePF`PqeMV=Tcwd8Hws;@x~@$^;eS3GF6oiEb<*Wd=syC@cnlTUeU`{%>x z+7P8VoDhhD;pOo<8l?QDhRq4j2Q7P-UoEKeR>xQWa93!R(21%|ErywHgcM2HtZMNQ zD=1Z*@Y)Z zWx5xRph@0MdHv0YN?PJX79bp7ni3gSXMauCU_daM*up72)iWeLd8ZUX3h3Wd-61^j8SJ1m#wW|^NL#c{s9d?Vd z<6+2+x9GiiscStmFl#}?Z&Zp^DPzY>c=mcAMCE`kI^)S(`3WGnaEtsCpez*MCqZgb z$tI4-)_t+80hYUBP!F1Aa8KFw2|{s7KSuS<6lj#90ww{0t;fOZ4=&u#``|}KU^YB1 zV#(neh2tQshDvV$lLbCOyNxBZvhg0>Hez^xc~6+X#4{bY57M`~qSauZy%nqc+&v0T zl$z9g3XB?HP~PXPc3)_1_Yl(w@_o-?UKAyTyPeHEz^*+V$yQYZ3GtqF0#(X#_)4Pb?Ov3(za?U7# z9xxVSHXg}%HY~~?jYm`wZ5jjHI4l{##Jx*s6T`X{(bCaq3uH1M=jW?M6lQ`!F%&7+ zKjyyb-{bo__dYpjSM#hQnrs5)q9SrP4`G4d2tUts2I`z{3S-g z(7m^%rRQ%oQg}x2_!#&=D131LAA1kKfAEqjFK7)9NSBJz`aYt5sVMEWN0gF^Aja z);^^@D)QYDPn`y8;1>V$=Me7?*8kd3DMpzj`aPq1>Cz7E!sqmJI@sG3ikzcHW+~Ej zt&4_+n~~qT!v%jG94u}=8uiP_niZRa1;=e5X=jaw76L^pn%~zID>VAbEJYMEgRGtz zQj^LpT{Vh7@ZWaGNbP^q7q?|%TkZcxH|*saS3yH}EJ*z|;m4G*(2=d9s^eQ7lU{DD zNhC7U=qz9_!jDc?{Md#ctFxrWt^a+Hmo*d~8Z5tDeCczm6zCcuX{i4S$xDXaurUt= zbH#5n*Dl(7@~3*(u(X+$mw~=*tkhWdqGIV&tMrOijY0%2QU_Wc?d1{uZ{J2@xsomm zC)8GTheGI_TP?MN7nAX!p#r)vM)GUvU3_1f{@85^@kjWMMqxOjxL?=sG<2q`x(L&)q8cbQmGLp#Sw z5z${))F|tX+ELvMe@uLrXJa*@>2RrvNGT`Vk#0Pu0Ef#q1tSBCSztsvTstWkZ^t=E zwo@Q$;%GV3I$ZK87>8BbQpt7-1dHgKELjmS*i`vGc@i<;P_$85vnd#ESldnj_W&%j z8RP8q#BLG!Kku_+I83%vXt7*1GwooN_U*PeLsI51o^7XfxSCXGv0~o?TF1+K1!IGO z?lSj6aOd^L#%`wJmW~aV+POPiNGr5Ek5Pw-=rs+*4r`AGqRp7+M!Lnubr3BD|Lag_jn;-Tc(QuJ>wKAM;|N$EYxkbIa0OZk= zNy+<%yt7)X3Bnil1eJ5IUO7sO4#ziM79|Y*a{uz`D;{SNo3lsUY-oU}DZs)!AJ>?SHI5c`T6SGtuW z`L%GcrLV4b9F(EPbDL7zT)6+DdN^f?4l91QRerld8@%kenef`6U)mLRMTKx?n^ooI zE0i$w>*xX3V;|oH1h$Y}k03w#A{TC6lAoyZ(T{`MzW>TLPGQ7;3}aaRsOnVsH3;~` zOqFt-Me%?sqIlkQ@Y6{k4-gT^^Nn_xfU9_kZ#%M%3ItYS#57n5he^Wda<%_=N|#ZR%&rBC?lxH-zNCHLYBL~e-+mZO%R#FeVmdw( z7DyXHH}NgdBSe_ofb}^Swp`s=BApHPnu2CYwRE)+S?0k!bYsZ%YBR^~0b`sKE^hyXC@lakpXJtr}J|A{a#RiYb^(gNR>>yzg zHEcHY)g5<*acNzvZ(VDhe|#=H2sP#7S}-k`jXm4XK-VBItR;?W{qhJKf5%h@VRU<8w(@XdP|os z9@@OOw3@;Af?{%Q6t<^I5$&8Jt*J0;Pkpq5){Qz~w(s-hHl=Ko7X2S0>clq5qBaEhwj@x&5m)qY5qJguM{d)GpC|* zFU>FPKEsYs4-npextcWj%HK!Vp0h(@La7hajs!+sU>rRgJ8qfZkwbQj92Ih>@8INq z)9>K!D0Eci--=LL3R>NMU~rE6q2Qc}Mf!2m?HE@=={PX>K=1W=XoPEes-1`Thk9z2 zhch(VFj|?1@72TTM?7^6!bG3d<>gztj=0m)3mXN8*R4Sp4v ztjO}GQf(0BcyBb0_bOh+mUTsM3|PDkt7l{!z=%h>I6m)-hIi@Sh2DHq@@?t(xNi$! zl#f;6xUVwD;j}OPf@7|M-SB~3uM&rQ)q?qOZw;KeZRy}}uZn`hy^6$Y{Sj#0?IQ51~6dStS49b5)h7bq2@$af*41`e|Q)XW@MsN98U@ncc6 zcp+MPeG58@cTEgMy@zzge$isYFQU1v8z1|+monlNClkp?)80eq#z4p3!`5gP5ZOE2 z`rvB8y4Zt}icm_LS6e(Azyw@ZQiRKerv^8!G~5t)`#13F#}LKe zaXCx3z*ct!2uzxMXUClZ&8>|g8GJ3AJuBWSZ?jnH6YV@H-pSQ1=%ea+P2FE8g4GiB z;H{klzg=WS5quX3Fc2@fzZm*^Mje;nDuJ>|3d%8ft_7B`8EYkPxIfKZL`qDg76V}a z5+#G_uO(8F7T)uSrC>FHHZBFLwe2;EUsQ2U7q<(mKIH9h)k<%t3rnS!s)TpcxO9b3 zycTV?3^4|*W4CUQyd*x6^8*={Nl~^%bS>?_EK*Sub@l5-kC#c_|4$+S#Q_elIw27)Tr?t_=u#*6wQ$ukKI3 zj8atCf}?=En7*|F7eZu{v{H&{c^;VTpyYexc8VIliJO zH+u!@G69P%%2{IP#qY1k3F`D`C_fc{c*%?Krv%jA%!GpTKam+!he{ zP8~h~?S(kuHIzKbCNPRrNXg~4fMB_u2U?w@<+i|RW}rgm?#O2p7><_P0+MPV_YXnq z$dMKpj+}4-fg?Q(v^q!2ZGi!Ab^$Q7*x}yrq13g*v{*529R)^k7a0;e9k!hU!70Sh zRVdAo?<#0<3Na2C4!86LhQqc~xWjke!nBS8BZAgp+bOg~arBfWbF`okw6MkDAAx%_ zHSAIH6r#Xz*meqc`0UwC>&P<~v<}-&p~Wf0!=QC|3Q^D&8uC}e+&hYk2n0uq7y-FV zy;ma&A@T|X<_*v&ds6EgtcX4M+s?K2+#`Vy9f;fYcscxd{PcUj)?e;tM>x?)I1wF2 zX!rbhr{V=W1d;68YcOpJ7UhnDi>W6H5&t*0QyLI;W$isX+b}+nVXa`~SK3)2b^`P7 zr}3d%Yb3vjvKRk(Xfg*0Q&sj7KNX@}|3SUhVrj1259TfL0zxnia6*H+tEJYY&(~th zso|@_?en2~kfpmxBQ}t1Hwk(b3-P}akNU5Z+6t-oOdkRvYN;>LzICW_0aahG1dsR; z;N0suOvBeB!f_DU!E~?WN?Zqo<5Txu?lXKt)sEBc5G`#4X@K7l zfv|k@`Lri-gI3!SBIpA1js*gr((LrZg)f)BzGi|QAqfz6BO6}(?#T3-z318?Q%Ju7 zyz_y;A)xd=(N(8q^+o{ECe~Im0KpYrEKS*f)t>Vjiuu{Tg@e5?f8O7DwgFwPE7jU4 zMN#$)Oys!3B_c$sDf99((uFR%OS#ss6;iff}!W?t*;epT#fAwL?o#%3fKvXT)7 zxIDJ~vetb##Dy$muRmpb%Jki7~ zJ#O%^7=};T&|+wIRVAp5B_S4tnHHnfYBE~RjR+sGz3FaS)-Y!9bHjvnT{qL}9lCA& z0M3t8mX2cW=oe7sQ?V-8KmOup-}U-uj8=06B<%ExgW^t3v~zZ;dM>UF@7di`iFST* z*}KKNea|XWFnn#?>}wGtx%9zUrWdE$efo#v_@dah^M90nYkkjPtcMZSRPM^zzE?nE zR;C^Fdz8j$#j025_?RIdTNiih<@^JY*k9OXSd$(4b~Rk;q~3MlQhm7Szdg3^PxkK_ zV+2zKE7(;G0fhVS!(MOQiTEufSqQ>u%xzVOcaq2XeobQ4bGBUEKtMQ?rs3@Yx;mGR ztWrb_56g+hjx#`TZnz&N1gZ{(ESq$c+G^6Em*A(mG4!U~?Eu zT!5&lC-cFrCb*5;>G#G87hMG<_=_%H{=!B+)ENv99H8bK|fErWvU3#pj) zG9Z{Ya{He#6t+Cv0`m=bBZic%iM@`t1D5JJ;d0%*9A?gCT>M%arR-^~`Q``XnaB20 z`0wv=bEiJs)x5*~&TrTubpXNYDc8Ny#eubNe>U6>c^MFllHAp|gRU)^yIN#By+~#$ zT#w$vht9o9AJvVi3sa?@+szqg5MhoUmA2lRTDZENy+G&UzYQ8(n3g*%YWkd=?>#?m zr`ZiiC?Kc$jM(dU#EhdoHpMy-zs?JEjw_8Fh;LhQiaI&M==a~q=d9szMYTBA{_hPQ z<&1|uJSi<>6fYbT2hYFlKeP`oNqA8c7D+iil249e`ZFyV@%prkr05NA)>=65)h57E zLzX!SV)c0Lf7XUB))P%et0f2=rQ8#vM7kM zeI#AQk z1~>2Z=rL!yC5qtKOOKCKJeAv@hnE_TDJweVRg~)SZ0z6bHi)n}l-n+J+V%(j&3GIu z`J~}4{{X^s$`|d{<~&GwkY#6AJxK(idN+++^dP7RJ3Dsm*V07LWFM*a3l!qK5xkQH z<99x|d#u}AZmD3bwz$z^(Z^>b7R~`wRj?*-2P_lNESlrV7q{2?m~)`m zzG`EX*#ijfsLNR6(=H}Dq0+2nb@Xn{H8$-Q}Z1rf7zzVdz!8lL;6 zj~}z)ucZwdDl`zHeCU9uu^bT;Dto=KNAbiQk}4EmIAg=N&;J;GRTZH*uG|NtG76e7 z`P8+F+rxSTqAu}GS^A`jrc4(%YWmvt8^j<0@wW`d zq-UBEC%7E%UexE8&l*-%h@!%x$i1!P=Ld&v3TQc+vI0bILGLuv+#f2?u9**|(L{#|4+(Cuzgc*Rxt z!xA1Sjxy6FZ&&z5if?N>{PKTu+gtPUu1$yb97D#|ONMZ5mTsSy7g1HJp}0dzOHu4b zOlzNHP~t}W)`jlCeX&`U)hl&ST$gDxp%b>dXxYt~l>0y8UZbxv#V$*W8~GnjJaC!K zf@*nB0K!419q~!O$hEC-dTo%H(WKjiMPu@45!OA~IsWd%NlQ4SBpTRd_Gsz{jOfk4 zV0*i@O})MT$A3z+V;lk`2#|rf4HG-wx{_yy+!#&EnR^Y3z3&)8U~+#@Yi zxCPM>mn=HI9Z%bPN*2}K33vn)HuQXhVnEz>5Wt*p}U$TD^CEmwzs#!=i>Xw+xCFed4#6Yq`vWH4|0salBu zgtt|icImdS%Z%IHUMj>^mRh#1?gU})qq{#`b^39grR6a%O`xp>l5hTQ$v;|yWjVKD zpV&@Ka{GM8=^85E+JJBmF|>K!z5VI)N9+)_1)^K17!9t^(iPN|tJpDm0m55MyHlD} z&vlu`sfH?7n}$j&Hab})_-J%O=W03Mud@?4uejP6x*4G5MQ`OcDIpEJ)jV&fwW-F| z+O|SVe=px0|2uf3)2?W%Y#DyK@F`*r)$3-Cc<-&k?skc7jp)21l;Ncnquo~b3%dJA->}_K1%!jM!tx zIFe20_n_mq%%Q^1@a&R9NB;-UzB%Nv3zWlig!S@yJk6_4j5~J)UQhBolS8`Qz!W1$ zHv@<)MAlc+u5SN0w#2w!nL~?qVSL+Ihvw2nX5BhhEJ#0TP|G`SN&F_TR{fVUn8lPf zwa=JJ{kUkG%G7>e{9d9m&oCZ}E2oyMzDE|T*qIVaj3=#HU7gnqF?)8~I4$js@n3&s zSBNTAv~x{3@A9^qsP;w|h*G%0nonC0)b`lE2KG{Hy1_|J`BP}Mq>QSyDQAV$;qe07 zY?Ix&jd3o!I$U31)0&`OWu2moxJynV;CmU%D3z76Q)Vt*CJ}=h0kuPTAwjYT@#& z%cJAK&|*XXI#VbNeXCTIWyL4@!%e16g;K{H<;~@CzDSChmp!E8+aaqhH}TFnB+r)~ z4ZE|z@7@y?zSp=lNdE4(yA?>lf4yjUb9aZtZJu~%H}cohuk7iUn3q-?@9gKTuJf)m zqw!1Qj9HlmOP0~)aOV!6{@wlc!fX2l{9C`d+n;{fum76I_`ojsuhwZMkA#=cx_(KX z`=kyz!RqnZ8Le6z){J+5X3ZLt$s)gf=+N9vl^Q(qM$RIKtqv7Y`|N!a?li(XyVeg| zE?CoejbBs*-dT{Z)2%JXHk*tPl zuE^9{gWR8cH6L}}A0L>(SFJ}5>^Qc~X{*7MHqK;D=>GZ-4Q@Bx*02XO$b!?esLp9y zdwQwgPB)_eid5>&KS_s z;!(ueVTlJQVF;=6cS%Psa1=ex%rHNlkzZPU2kgjP8Qooim>le7) zl19ljllZT?G}AXzo_ZyuNwF8@))L41qG--VS?3m?F~$sDwDO|tS2f;XHjT+L>eFx} zoD9FoUUcN5oJ-$bma9_CWw`>4y(BmBjLFDE1lpKpOy?C~Gef3{R$P~psM!%oqPvGB zPnvW=ey?Immfo6?YQ%~7fwGPo=;byPc2TZNga43pRQRi0slelg{Gql=g3)S3oe-Fg zFW7!dUQ>~pU6CVcYc;txjeQ`uhlu8}MvIj;KahJ>j?YNTu;|T(bR#`}An&3|adJEQ zW~AJO&NPv`(Z>(*x+2icjsE^kZcaBI%0V>kirkb!AIXjA+beQkGCq>)QTR#OjXHYZ zs?ewi>gOT9OIL480hIN-Y^3)G%K-(ZBB_~M!Gexq6btkYIvp^jTJm$(&S>6D6PYC^&mV3e^<6(pC2J zq4md52&Ap;iUCd$y&)Ycpey+2HTh5l3LPO=Dd-m`d*#ra>vFZqB8?=>nlzD8FUx+k z@17J$BX7tR37TtMaQl*cK})A=$^l$Y9sa%sW6r-Kx6QvL%T)Du*}LGU-{ozt1#=$B z-E{?l-fn#>=alV1=pX1udL?Yl42% zZ=H~7%!;>|GOY@}KEY^6P~r`GW11=5WHwk$PIx64<1;JB$>ZRqggJ@rb+@SpnWCHCApcri76l9izDJE2w!?ATzy0Tny*>7k9 zzQb2(L|t{+x3XO~#P9Cu83|+3j9E}mdU}SLBA#P_Fnh|4kZZIq4Nb|iR}Q;5=B&gF zOS--+j#8xPhO8&5S6%jKWG~hc3pCzY!uqkm|3aleo93!f$$JBj%NjCRYCXAW1k}v>}cN^ zIgm9{ZGbcM64$29g?Y}4q_#7C5fcy`6Kv`JW2K)X^LV&L9GIb0E`N!i|Hrz{Jx7u%q;((ELqzZVlIgkdu*VkTX%QI^^`!QfVV5r-^&6ir!K`9P(D(>IPT zeJ$x)If9s3EhR9`dbWa*T5xtS6|r+Zm2cF~U|cMU4g)GZ{rT%q7n?wejYjlrB0 zkg9uwC5c~6<}51@r1)fGd}CoyHgnJ8lpBXUzkc& zS^|AE1^p<6u!@Syhp4uC*@f3*pFnLF-p@~96Iz0Z_Pqu#>sdS5yDqy)pm&5NT&PR0 z%k^v8c2Iz6`$^&7%JoBRtzv6vfuM8*+b6mdBR9~M>>VYLl-@#`Ou4?N@)IECspowH zsxe3o_EdKl0I_W}(C7zp{T`*~bapa;!VkNn0w>F8NY~>Jj>H>d(o>)x3>|@!Xtc&B z>)8{a59+xEMmPn-iB{R#Nif>NY1Y% zDWp>ge+kf%?h{-C_UpKJTQbH$Mr;x2C&K&19z%jLk^g9BrqP&63%kj|_0%l{uuy-2 znw@u0CydEVgRho2f$n_^hk9Zqxe94B<$6tu0iY`Wt;dGCptIE?*_23z%5tDiRU_Vc z*p~oX^$~PwqZ|^z@~}?}b+aV)Hz2@ngw#~GM*viR0r33^sezALApuZd1T2JVHlydUI~tU>}iY1Bm#+ sMiSFGT>`8zr{(}6a&(loic-pDUs&~PU?t-%8SJanE6A$jR=>{w0mK?-3IG5A delta 88527 zcmeFacYIY<+V+3;2}iP31Z)%mK`ej-rKXX@pdh_U6F~z65=en0APHhXML-7;H@ZMY z#R@7amXX9^9L6$$qGAOV6)UJ%M+F?l-}l;UosgN2<2&=r^LyWaIG^Xqbzj%&_qtcv z=WLvLYg;To*zEcaZTHN7;DTi>ZhpGev#+H;c|y|Vqfc!6-j(adbJs+yBXSM~jlvfauARa+-e`h?=*srk8)NPU;@ zMnttz2Tqo`KL?AvlPiBtu(eGnE}AsHcvd85LUBnEt$d5F+{Gn18e61aean|R%mUTQ zDRfcJY6Pm*v(fp#xu@|*ydkLc33)jMQ>iD;6VP!=ySd9~NEPjeslj(}xxAHhoUNd^ zD7P%XFgNmGFeKWjav*h)F!v)+PRIo1q_aRZ`}7HR&K?KV{FoE1UOXwcY(icn@-bX< zI5D@hOp{hTp$spTMamo7R-XaMGIxT*K44S$V~s-RR?h7Uqpi8Apekw$ssqUD9) zBS3ZZ875T2>Ow{IX>NV+H1InrY!1E(wgmqKGV-~LTG)C@L3LypsPt}N^GH7&ia{N7 zJE79X+ym4?*}3=eM}F+u%2se6s9;(Mo(!Jq^r};A`G2?Os9vSC zvGra7SCD=N2P@~kNJW~s8EtLGS3xzn5mZH!3yQ~&FONhvw6g_E3vx=C$H?Pw1wj7f zqT&*w`~k-swzuWui*tNQ&V=03NaQ)EAC+V$dMxNiDkF0Xb4w=Yc8WxfAcK0GUz(&g zBfUG=ihexPj(jUz6Z)y+r=4Xx{0OK4Ov))On@-0{a%Yt0Pb!aGPlx2mNu^1-v&u?x zq8)AipU$@JOetl>RpuAu=FZBUkVC;NI-w4<2h~t(P$Qg{Q&O6nR9M;wE?34qF)gPo zFKKFS`7th^2gmjCU;FP`pE(QfmMNc*o)^xG=vke?rovqWFrR(2*X~hq{Rkqwl2$ zO%qR#PRa;+B=$PxU$u0o=HULs(<`s0gW4)y$hK27tB38#v!F&=mQ#{6iMh`{*Lvg* zP*as~o-OwST;=2W^NaFwOY+M~m!Zqk3qX0axM%_q7KvP1k~3`@u{gd!ED{Xz8&(eQ zhl?GSJIr-B z#A4;#3@5Y$HQq)J>p1*&pw-_8k0pJp!>1gsacCTt6c-fCm==k2A8q&3mY~koBS3AB zdq!FP6|jlc`=c0ap>wYT+0^D1fi1zlj<=8%=pmK>?8$ ziQiv7WuMoGXAy}S;SzX#@F7rJbIBB2?lyP>_%T!Mi8B+_0DmG~*s8$B%#_mH%2I{< zTvw6y*_?vX$Tqlw>S<6TC;^WFC+8NGmKRNkXscwiiX4ZoIh}+DP6x*j+VVt2F$sM7 z!qu@9Q1!(1is{ya)2HKqjkHsVEjWn;RbUT;$lR%{ouuMPk(5%KuZnyc@kY2jcY2wv zI4`G^)g6hfhf8k&YO2P#`ex=7l;sykB3ozJ^nU2_P-dm;NoSYwJRKvfhy=}j8&EtR0}(3FwWM;Eoq~omERS6}$2L#|iXVNs9T}%TK8i%Hg{uQ~ z$S;pfODfD^RYvynM;@gR4ZmDhlccA8z`+dp8 z+_K8Zi*BNFi>A-WFUc+W=Ld(s-C#TNXP2)E)Hv3H>R8z_8%cLNeYw;BIbg0sSHq2N zvS_b4m8FsRnus+ktkV7X9&{ zwyVG2ZE+8%j=ce@foDJk@$W&^_kGk3Xg8=}o|#{!Js`4kwJkSmHHWqmwjdk_9#~^X zSenl>%B12*HM-{TK~SE#5>&+n#WSYmGUD5uJ|0v(T*Jysx$<4VI=K0`7L}JjXysy1 z9xrs+MuQr6-;_^2I6;naz`aYPFK zVCUnNKLlwfw5W_<{^FO!_-!+O8I0dL6aHx9CVq+hYJ-h~_+>GED~;c3=LS->i7-&m9XxGkNY78zJL~p7A-Qxf6>EXO!g^l%}F9z0M2PaX-Qp z(A*yK%ksJMm6j%5nwL{{&*tFH6FXFnq&h9A_O#aoJQ38wyZ$8`&>w>;zQgf|OaF>~ zYkhqD7ki(08Po#(J*aw?fv13%feP4hU<>eEP$mO6G@D-psdczxbym>Bt7^w28j<*8Ufulg>|Cx?!MSTb!t!w{_ZU#n! zTB?^Y1M+oeGb=V;jET;jN%tey^)I-oO&LLSyrLwu65H)#2Ibn!@MZ z9+F#HkYB{o*|*C!H0DD);)2|nMGB=MaCIOPtd!9kLlsTT&7GE%UmDp%!TRtIK%QRQwB*NxC%i2)6oDtFLr8EvcY*LQWZt6;$#_TNYXQ zna!|_3N$wzcH4$Hl*`I;Cr0=#W2SokHo6+fEe%6Gl3U7EucSP(WRK0ST`@N|5}8Lm znvx?ww*!~~S4V$vyyhaX3;!QW!XPrpg&jdf>8YTi`?fDF#?y~QSAO|Xo`}NL;rKK9 zl+vVd2hS;)9DX_T{$AT~^lR6FuWWtS!{yn@mF_|3#}90V4?%V0+HY(WZ-#4iJOHX= z@wtt6AU@J|q_-r!q04W+x6iO2d}o8M60TM7BwU_|PijTi_wM zdRkUGE27V)A8iH4|71u0&tDV7H{STh8sA9c`(%7Sbo*rG|MdHU|D~@BSpV@IxvW&* z7DOUH{@sqabjE}d_SMLrez8+BEx&}AEsYdbC|4wMd&CQau`DmQNMGwMaQp^PvGXwH zbfkyh8Ipf=)C>28KA>VI6FiFe3%}UW14UN^x%#67o8e7R6-}RRJCwzm)ZF!+YxjZp z7wi9=KK=+VT%Pgtdp6thUotQSZE;;M#6N?T$09s|p@zyzx^&aX;?cU50$N5>ig^$r z;^K4tKRtwhZsjV;P$~7?3x{()d*>8e6BbI{NN2Y7^qHVfG2`&L3OSHNI>(|-zm1CGET1XG;jn7a_@KOa_hn7c4bT}E-mB! z5*a_EbbK+6|66NYZpIA#^1!W`4yz$X7ycsl&Z%zN(B*|YPqSW%PnZ2}gFRqMF$@MLfv z*aDp3uotN9xgFRHJjUT)&$LtWE-2SO2g(xd>GR+t5UPjliTG7kDijrM~Og?rlr8!zi6m zSeV1jAbv1!Lf86P4{AM?%V!p zvH;?bGx5ik_~T0aIV=7g6@MOzKkmejg81`Se2b4iChcTk3ahQ4{Qj#0(INPMxF`IZ zBl({Vq*906|7j%gYeE8ZrWN!(5vLJ-2I^F1Un(o-tAgj@T5(T;x3BNc0o zzXa9rt2Cf|&w|<-)_^*5mVp|`(6M&!x(Y52mQ;?j6>huOR`dwiiiF!Qu^sVq?51@M zIuBWMe}=1pk3n^0V(!d>;%SjczAM)sRL9Q%PXjNQXb0FCRK0c4TY|6Tdf^W_l}})( zhfhzk4c!V4*L9vPSQpf8`^997uY+oMgTp&OH8>Yk#V=2>%lCZ-)&@QilqU)b?0_mR z%@5x>$SMq{q;l?d42|doP#eyNWYk=@hO6P$#a^T%SRXD|zvJ0@c7EP#x({ zgR1Wj(=C4t)SRc2uJX^7*!(BMo9Q7q5kn)|Gu56uoy)98YMzuP6wlBzwPkSqnSXjLvBRi@V0Ti3$1dr(&r9rl!-R^BEAN;SjOv_{xO??A_1}E+ zh-r^q-}#d0V-33XP6*bgGLW)7m$Yx3IP{}?r!E`+@Q5I>*Qf@)?*C;?vvob{ z_qu)O{dYEcYG1G^sbzx}Hw?_5+kfbNGxxl7{dK{*-NCe8(-P-x{_Bpcq2pEt3)}Aw zW+k<0{KANvw!ZrM6&FYE{btLgy!B^3wEFu6LvLQU`LPdrHAqNYus7vTZSJc#>8@}3 zc#mD*e|6Lw8l(=&@@@{w`Fk*^;%|>&KYwQhse`k;lY(;o<^)xPv;13*h(yk2@J9sM z&!+^bL$bV~Dvq!Tey}xRk_1n^Y&r9+d2lfb}4$ zu9&wx*grHY8uf#;VVSXEM8o+(#;|nyQ#CBhe-YlEtkEEwI`_eP+k(TA{d1Uybl7pU zknCRt>l|m^lfnKAvi$GR&QX4|@k}ozC?B5X-5XR5&+>P1x+x(VY^0)AL{1l&7i=7y z92*WhC&(C-?%f_#jmYxe3HFc3iZvnp(}Fqcy9ebXv%L916@Q-y_K(c+KT=9q+8ded zH4n;1WqB6`RimADGR%0*kB6+*pu3GTDDYI*c*W%1P|$ znw)6ptczi8{LIJgF!d)ErtNV#V=5O*VHsxz8-}F&GqJjnt8SR98m9VaiyHn8>jgL|89aEWm^QHdz0VX>`q5e75~5 zSjrW)?>!Rizc|bLAxOO>%gYMNFUj(!vu@SDI>Ee4lD!4NezYB|ZPj&R;9Z#PpT>&s z2RkD0Hl_qsIa&Tzl+;j}N6X_lZR9cLWpJ|p5UekZQNZ@WG!K3lh`rfE6v0P^^LY(S zX?Auu!+OH-%&_F>G0lSN37LLBcFR79bej+IZ zZWmN+%=E{!i$uDT<(M!$w!nIXjc#0+l6Yo(9cndX!qjoQx@N(&HgGaEKj_kkv7F@S zN$rEQ;!J;J`?!a^{A7PA%q}Lv+J70Qt}q?Ubdw~v2+hXJSVk~46fThH9ZA7~i!=S# z5w%y?AUc*!sAi4$Lt(0n&6P;Ku0v2gJ=6aHQQ^z{(7}#p+0bT2MkM>wVAd^KPEW(M zAiW?vKRFsZJ2+618BIAmNGm05&W>+B*~IQ2VOnNKDQsfLuv3H^Y;<79;K000e=&ll zJq+gPa~*?D7QoovU$&iM;r+C*5~?DcQLG*UlY)(tw_;x?@}OnnSDM}HyA&Wrl7 zmW0vaOWHYp_$!Uz=R>?|8OnXo`m}pjVP_ZC0dTB~fy&%(nkQy!s zD^WRlRJDZrjGqaUgY2^U9ZZ9>e*C^@zX zmZl@nk8;4$BNsj?Ft}fDnlwjsH_EPG=I)r7ul4Tpf=gp{==~vB{vp2!C@rQ>& zY#FRmka2ms|E9~u9zm2Jkzt1xMwXufQ+;gP)HfX_f7_uw40C>+nCyQBQ*Uh~G$)(w zt@h91j)luGW?)@{j8cwfSz&#+Kl)2%P<>sde|DDb0GlT67!Mm5PU9n3@~d6T-@)Vn zd){`!WE9$H2PH>~&Iu~UW%{cT)gv2?@4?g~+rJapS1nT`1I2<3gE{iCv;vO`=H(^( z@4)Pxhf&0?qV;ov4a3v@m$B&j+?dI3NQ82D*imR-=m%3*nBs$Bl0q^e@L13d&$C;G zyX%OBHCDi%f*KBWl9|mW4w)g6_%olUd&C7JqqKx zF+4r`O`jlbMW)}DD*y$R%-wK0Y@o?mkkC1p_LW};#(8l`x*zLT6O`mS7nTwnEJ*jS z#j>TfwqJwEj|pLf9M?ZcyCpNauYXVh3?C3w1B(X)2X4vqw+)DokC2bH8W>dbwfrRm zgS1;SV>yErjTyJ5#~#M&A6h@xSm$$sTP>)u-pA@2=4(5Y{)E=_8mp?t@`lAz`_x!V zYpfkLR;vr*`SP)P*}l|RKh#+1!$be9>|O_hFr&-Q)mZ+BxHh21S{k>aACCyq?#zsh z9vz8{2{P_Xk3EhxI<)Ft7>NuIt?^hkbpzJ0P-`%TuUkW_5Gy~l-o(lYt>la1+8tOn z-$AU&Vd})O{FV?}o3Snqtv2K0)&eYB;$tk^myC2ICx)r zY!y~Xm@#Q$P`xHIc6u&f;|FurbgzRkPARdZN%A+jqotFA>iaWebtZFQXnDtSun5Wb zyL*#^iU%^I?@kV?fuy{k`tnR)-z{q;+N0zlm{uua%F6y2)&<7yo1YwOoF9o?5^PwL z9$SWWVc4Os@`JRsnb8hYf{L}7{_H97Q78wy-+g{|QT*(osP+$KP3nwhYUe-F*hEKWX;d-3Vh9 zuSt*oQW#V`lIiyB8h_NpVoUE;G8SI5@B_GxiZ_ z*}Cpw#gNjEp{ohZw%QF&eyoE;tn7pSa-DE$b#BK%QX%(9-rWuHCDhUsSXldNJ z8@WPd!&H==m8;%tn013HdI@ItIPd0^#49f6k}ZVQgYrHFAJ)l%JgqUWTVg!mnHk(z;ud)XP)0>j+-ar6;m9ho_Y2xI+S)i zX_v=mO(PluJDZ&0Bb9$EOyxL<>C&ezXBe`6n@Ss@ta~m%7s4{hY15X&>=C7F_w%ss zq~Qr3HRf7kze>0mHX>|J0rUh+V-BAO{K!08C3B{S5}4Ll9X$#sLNp6@tgB(FpXEnz zpLT_vT&`PllcQs<2-5zX>9@Sn9=Plm46+EOu(da`#~mXGirvChM#s3>aILDh$T}^YuxOt}LE5HF{|Us4C}17a@EW^>>_bEjOim+$nU4El>J!t! z?7suk;JFO*V0GNJLG|WLfB3c5O^Aib(HpM~(zawqpS?Dy03yE&s)3B(1qZfd#>#$2 zum&4$N%tSZvR+jGKb20k_>_BP%f<0D`E(Ra4#TG`uca_`C)|PkmthKG7>|j+EDjF5 z$PM{A`^6l0uA7qmi(m?;FpGa3?7Xlt#p|oE{vji>qb-&MX)k3)hb;-JE3*=p#HZfl zFnE7SaNs3^>w4=-JChf|R2N}8GbItCp4biU6`0+3v|x{2S~IPTpcl-#muQdPv^1!G zCDVTi(eAeFA<@Jeg0yX!{*W7LYQZZDU>RWxURARHEX+*=(^5CE9Sfh4{Bs>+4J=Pd zgeXv$zr2vx1*%oJpQLn>2!iTlzsoW^;&6|R&4+O@Blup%V$S%Q&A+i$4!;jfb=e1w z`7o_-yF`Bs=5n+!_{ zA9ZfW(vT9&#+hDFj!=({hn*K39G&jpi=|#zcfJR+bC^x8W-D3jYCFm| z>uKDPZblejyTDQXnPcn><5TL~7KtQ>4`oxaG}*|w_Fh;T?1ZovKf{K@a5NPRzTE~I zIY%Y?&%orJaB0SR+@WzF%uDxg!0Kytz90JJj-cYb%xIHUK{YULRdC?FO#k*(kw_9* zI5eN(wg&4CVzrDXqVm`&5N{0yd;ASc^=@?Gvo3-2)VU|O=_7Y5O- zcLfJN$n@*pZMQ!{iiOe#mP!`e|0`hHFklOkW25g0=k3Gpi5LUpvi}pzZUkiV&%L)c zTLQBKRoV`ib%$81`{LV)ST4*q%+Vja4VDqi*^=(RgOzOCVy|nq+IEP};@(r&*xD^C zf@PAEP{a=p!qi85k4(7VP8kCsa57+rR#7QT=c%pbF_^q*+1IX|Wo;jbZ-?4j$G{Hd zG%!~uJ=^LSvp{V8SiLTJ4_>D3@n4LYYF~v+04cY zM{2xt$OTh4hJB3He@M9wj!cgZe<(PxC)2+Q!6k3uD?6APV9Q{y{PCfn`twZx$%k!^ zSXP^fAeai^tI5pLBk={U&<|k!!{XWl--nH|m60}aogGOSU9p>CUGxwgeQ8}#{bgpf z;iJKUFEjm~kJ?;ZmYAick6FeBa0Ndwz-;Hd4^k2#a#OgS#Wuru)LfhH*L&PfI@|Ta z6z}n%`m4<7`p1Ij1SUI8fCDz5E)$hr; zb$^ZJuh*lYN=&M;o~f}~R_QTSsk3XWH?c;C+F8}%gX4GIz3QOiyR16Ukzqp0Q+m!- zvBg-!WceRpDFhkQ1u1p@AkS^c@1BTp9;*G_;xpKYkm35+`5Qv_j!TbTy&*{ZfjLBQ z>*RjELH=XS+JcRRY1?E*VRymYDujLU4>`|zx^~*7FwM3t_YCY%E&iWsRyMT^gQ=@F zr-2=6;eA+5EwM8(*F6zK3)B|b2&>8D*ZXtrriQ@|Rd);QP*d;MPK%xK47G%n zV;t&h6|8SfQwh)3uC5>KP{ZaQ(%y%iS5vO_b2e7(H^Q@E7m#LI{wBK;>@|NQOjE@r zDmNu@vz^=US5bckOi^t&^5NltU`jo`2wj?>Y-;LgC(mb-FA*K6Eac8mX#BPCc&hvZBcUbBy zCpBAPx0mg z_Lspfs;ToEn8Kg1pq9)x;)C_dQxYMH1DmuRcBojFH{;EzfvaIQO+NY%=7vWgq`eid zQVm=O(;BjS$(yij7{BW>AC0z?Go1RVSZV0t(@XS;?dE{b#J1QGUrfwWbm9(Eaio_S zy>o}D2K|poO`#0O8BYw&-i~kYx_%6WX&DkVmnZvA!Ze5W_O>6^A7-EYd%nZ|2x}02 zjqu((rs8Po?*DEiG6~g&{iCq%mMuz-e)X*I%+AD9Ee z=!Yh)0c}nF(B|SeW|2JxJDW20>F^_%M!=rOW#;6M;^WaW8uO8N)2%B^rKQGe6g+cpsaJV<}LDXe(6^`~W-879bu|Ke5BX9o!qsVI^Uja()5JtZAt2 zrz`DZtAs=?! z=XR}8AKPpWEG^8byYv$gQQ}%{I|*6nTR3z+rU-(2oa}Y4gVie({l_rvDx?nodzi*S zYdlpZ{Xn+x;kp#d)x8;Wu&eOLnrbe_RKalZB&_%`VrHMhSYAg~?Yweu84nRZ*{}@v zk5~aL+01E8cd!PUjMnu0Tg?8}%-ARM;Ay0XR+#?AA+y!~nyiJGeQl{HF?(C{mqX_H z2Wr$Mhs?J^bHk~$@N-T2Wad%1r7t;W3akh%GgS^psMXY&p_WST?fJE0l2 z`}cTOe+i~mB5U@cWWUibHU7LXIbqf>5%Y6fFC*b~jP!Om_B8$}idH211Y6%2SbG>z z%Y)_pFs)*{TL0>D68C&@-_9c~{5ClcO99OKW4pT_rdXs5Yo<=rq_t=L6-T}BVqk;( z8CY-fux8jqk4i8HlxHIUmPIRu)0{>qjLO*Ox@j=&!}eU8tRvl9JVD`5ex*kW7E`p*LsKQ!E}Y6!E{yDJ*Gw-gxTM0 zpvpQ}uG70P``T*HtY4G06w~$e=|gJW2JveBelqcr`|cj5fQy)U4ZY;(dksulDwC1N zzk^XGe(mASmIq-2!^t>+rBfz6wPNS;&vL>qll=KuJ<;ojla~lnE_)?uaGag8@J1dx z2Nqs_W0zx{r>Khl`8ZRZ&g6WL!n+pi_^pnQ*PwVA3sW=`znpY;!PGadQNN@lLhN>} z)1b!*wKG=KuxyUsM`0Z(#sT>RujYB-8OE1Z)Zgkv8;{h;X!Bq_f(;|mqxYX^sx$G% z*C<^mYOhb78rfJ5S)0a!9y-^z;&H3zc^O{~F64=rMlTBFfZ%z<<1VxLoN7x^N7NOHner+C3rmu2|BM^&JN zH%h-b|GY?>1bMjVjDd9vD@j<|nqJS%@HZi89_${PcxuhaaLsU--KjTntC)|1h;I88HqkS|^@krk%v{f%|7UY2pV{{&MvcxoXwx}F}NB;A3EPdC-QxZm7y zx;X&)uaT-AaiRT+Z_>~3!YfAjWpFGL7XDi6FTomQb3K>rzX$6G<6TAiJ*JHpUi>Y4 zv^I_W9aAx;tJ1>}hX+|uP zk+u3*mQ?ui(7JUWP%!xU14Ur|aONN@cl z5IdfKGsQ0$52bsPOnE;qEBZ;QsqRMzwn)RL#ZTbG!d+PE0kJ?p ze=i+oAC|iCPr0>d0Dn3kPQdmVOYp&grPce?ue<-qG}_7C#1{56OdQXo96V&`Xh zyb`hjzdnxTRxGRXK$bZ$nDu?mIi#C|gW1h)!@M9gz3ET>0WHV z^y))q-JYyKn{_B=A8X!n$o%+_nbfN$!^}hG(}&E&^J_8;3C)ZPXcy~zTkWpU%ur`~ z*NDZ3%->`7v{`>XWS-NfMx9?{`dcv1#qs>?*3aX2;J)^73Qu5foT(a(OKw6OWHyXu z$e&;iux6)zp*fCTSWD?I9x|KsmrKG@6EXW(bL}DX$3te;fSL>o51HEznN0`QWS9_| zb1tM^tn+QP--qTLb!JeFIPZ|T3A3loT5oWTIrNZe4w>)Pn0~7vi~;Yk%2+)GFh%uo z;YIo(nAVwPyJ1>c>>#X-7DMq3jEfQ5!Bm)zW?uZEmTFiQjN9ww$=-gGnnP^%9cGVB z?#=u%F(0N|7T>EDCVN}VezHUf^R)3^rk8HY#}m{uOqJmGW z(;`^-h!gwdVs-amh1Wgt5-&1^9O2gvbs)TR#&6m!a&)li=i6ymWnl(we8-KqwG$M4 zdodNp%UI;vh~;iA*o;t5`4d`%4-VI^@EE1KvS zl@sK97_WKJr<_UJ8P(eBSVO})vZtjaPPW@8**U*5@>C%|NyS!RT@n`DUt^8VcO8yB zTw}$i&^>eTBz%FDN44znoKd@A`tCBU*H4=2=^v`ePgN;(AmOdvzoGz-S(!s+P+|O7 zQ?OqN(?!^RzxEBx&Wpa6>s@4*6H$(bu7RbJGyDe)v1edm$NdY6y~s6T?ds5bFdaem zVCgW;mf<46Z>f`EJiYK#@dVa+s>^KT1+zq$^}X5|Gd(_gs&5BOX|}%f61$|sryhR> zOl>h-%Dn~)>kJohqf+`;lXfXgQ5fcoZG}w==V)LVvxUyn|Mk4}W~&Qbk?^-NuDXLV zVrS0?k9&Rqz6?vN4hKAx9NjR(R9{6X*PUrM2G3+SAo`EE)C>RJjH4Grhf~JU$Du!8tzfH=5L_d>RjgZ^4c_db-77fz5GdhpAJX{#zI$-x;o+wl2R=W;=&x zI&3eI4_FyD!i-5SLkE{ZsE(cOu#?k;GCMmil*z3!{M5z-lw_yZM%A0@(f-jG9R1;^ zHcH~e4?jW`;H(ZmzlF-z*X0w+?!;P4}q$uf=j z=HL@vqe@YtD*XHwDhm-5erjVql=^JHKz+@zV5hC9p3KvKVxof zL3wt#jK77-^0v!Y8#N^#pv!Y#x^!WJ*?K*p)(H6s4dVX|+Rfs>>A(qe{3JJk{~T@m z|3w9%uRCCY@RG(Xio3#u#0--XT>N1?}_!*A31?!WZ0;<9cP@meU(8@+v z2%hKC{}om$K@IkF8Ec~|>VvMNe*CEm4h2>5aET7pkqhCHE^@k1`7U<6HcB6F^~}(a zJ<(+p%ADl*uc4Ci_~V&+PkD_-^FR=}%Vn>PYUf_33uWp*j->Ifad^K(K0@gafRfgV zn}f^TZv2Q#UI(h$$Cc+_psIPorPoI3zju0VRDD%W7s{P~1f_3`y7Lqx6rKh7zsU0r zH;M46jgq$TM-9H<(tiz=wAG~xmH#Ejh2k$q-4c1(CHxku=dY7bS>JROyybAaL_WWU zN1=b@(uK#ve{%e%_!5zz9_@1pLb>Q?Q0Gk@h|*&c9V%;G$7`eVB|2Rw?mIjZROBB6 z@_!LCcZ1i=Ty&GyxKeT>Y5xLMO=Fi{8`X`b=t@7yr3=ONpS9ITDDz~;e+BJK{7OPt z!LR9Rprxzu*H9fhg>>PmE}u~4PILSax=4_qv^FkdTbEI&g6$lyjcTC1OHXp?zlEx& zgUeTgl?bZnY=@m(2BD^=o8v-NknFfnW}4H}oi0?q?hZ4YE|i|>cpS&Z4WZJq`J)b< z>oN+pN%nR8w@_II@JHnb3C-F+dS{G=MJ{l~g-RabaFo-98t+A*`Z*TV(i`veNuWML zmCpk`v*Bs4k$G^rmnb(zilu?mT%Hn%eEuC&xl;0L)m`Sw{Reo8+WCiqsv|zR^B_%4 z;YK!I_5C(ie{Gbsia$!f3lzUse;mpe`d{@>KOc0uP(FJERQeN6{|&U^t_&(r?F#%B zY9D*fqY*wmG)j2l9EqO(i*zl4?lzc#4Z>c)Uh28V<4z(|Lq!2})r z7hz~anE>h|R50d&3YL7Q3sqr((+iz0ls?VzUqdxq;?jkxw-gKy{uwTzHmbm-=&HB^ zRF=zKzS^ktdFZO}3Q*;)a(FeU^hJ(e>u|BdC1AMzmpUN;Rnam~1r5mmMQ(9;y9ghl z8d?R)Q}=?(zsBK%pz2xc@F9l}gDSTURQ|{7;D1$6<%B=Dj2l3GgqoUX9j}dQ;5nz) zMoF8UE>uG=f_g@K)$uo6`WtoZ{u7a)3g2`IzlQ4hcG8vQUH)jl_!v~VPb4}N{}ir) z>3{dFHFyj}9XMX1L)CX;C4vexatXhM%Ga2DD%jNJ|94R3PIBd%yK+M5l`Wicvcr}x zgHRb-foia|(}mJcb-Xr8Kh5bvxxS6#LZ!C_Ro|JGcZjri8GZ{@po8+6y?^1%!iby& z)!CThiVIbLs^dZxOLttT^zM$=M(O;t9QOAdmtGrHPY-m>WNyqoEMSBmp`QDvgNoHz zpgJ}WRKY7jeS~W0Du?qOUJWYWLQo-cox^3IK0-OtfGWQ{=3;g^LSuwST*m)vsF>B~ zf3rb7jXdk>70Qz@I9?m&iOQEqkSktx_=?N;Ybck#>C%OA^>&AEJ6$OKUB`tg_aUeT zKXJI*rdLLs5u!4D;WB(>Gemw3)#0yQzVBSV+NkvJoi3EQ&vBuqx`BG7__N0UBUJqT zHyx0xj}1p+@kDqVxb$|Q_R{uNYMMX%445ECF@p zTyTV2{|gZG`EO7oUgYYjjnc1mdTrD|uB#+LJzU~4)J7Ft>h#(u58deW+NgSNa{6zf z@)?(}Qig(NtrPwmR6WnQdj3tU{M7>L;j>gEe9qzX4*%kC6R3|+`8GQ)6yM_T1&1#> z+zRR=jOz8MmqQ~G+2%65>M{sb(Q7XK4W|orD81)+ZS>5}`*>>o@;*=haq~!Im&@~O zsQLex^kcyvLFNC+;XYUH*HB?`z@-bt|K`dabolr9%EAa&jK?9M2BM$})X|~vFHren zF8>jr;_XP6Ur3da2B4hK1XO(|xpbj;^SZ1o6>Q~%)(%f|cm}ACP;ryt`2QMK{%=?K z8}(_#S)}X7i4iVeW!(6Ggd=Kt@;~W-VqhdA5sq>r5_(TIf7Pq&g~5FTTqD+3e z^4|z*>Q{g|CRVw0q4M7aD&O5s7wT|c3?zh7Em8y1MqE79emH}Lh%nBe(LaZQ1$Kw)se42eZGnZ_jd@=_VY)pA*$9L zs$dMR5!VG(f&L4z`UutF(N3@D@EC{nL6vI=>LXPC6F}XMTUBDH!c#yUgq=aTCI!?- zs1Bq%{TzoqK~>ZT)TcJ8!G2D!jgp2rJ&vQ}#(#opU^o@2uCe^l0LHnBYw*ck4|^Xa zgpbmP-wCMwE&$F7-w}}44!;v1Z~WuC0t%$V?*trvCqTOlpCkV7p?!wXOIj@H+w4Plw+LIQ&k);dcURy(3`n zCWqe%_^)3s&{H#?U#sWvI{_!-nc;XyN7dnX0uH|uaQK~o!|w#>>qtI&H$XSl!|w$A z+duIiekb7YI{}B^2{`;tK<#${w3UkMhE@Ar0O{hop4NUBK)SfL-rsr`K>3uee23o& zIQ&k4;^Eic4N!a>ekb5Rc{xEV_3%3Zhu;Y}{7%4cy(6Gi@_&9Opvha_#CB$7X|$i& zmclwaAO^WMP`?Tx(G|ns=5fHe1upcLSTj@A|xJ(@PvdLjprk*myqit7_*`oLf%mb z2b&?RFnyXwM|wA#Rf1d0e!;C~NDE-4StGd3cqaq5o6&+h%sRm;U;1SdDG+>=sBzV;95)OoV4l zm4t;&5t_G0c+TXvM@TsdVXK6{m?lXGyCuv?LfC9JOIY3vA*lnx3#PmSLht4XI}xH= zBc|P1MBqLti_b!NIbz+M9k+> z)=L@C3FWPbxw#WcUMrM?Qg%d4-_9tFPeEDR8ReabIUr?|l(AhfH!hv4*#pUn(!Ka2F9Cy}1unHx@` z#KN;t3bP45V^MHD$TyTupCa7=P z_5>Q3d4h&!hu~P#u@`WhStK~#>=K+{QqKoYG)o1I%pO5wlieF=Vpa&6nr{UsnLd5! z{H7GTy0#BpZEp4>crDD3zQD<5ji9CR`T?!XXu&CFouIYx`va$%9KmU(N^rVqH~=`q zBBx+BSBj{zS1m~NEqk!HfU(m;F6!bMs zMpKXtdCE}~>~E$+y#Z#kV4!JrAu!043kI8Qf+42u7+|QGCm3dS2re)kF9L>}MS>A# zmtdqx9Se*yO9i9N9>IksdmJ#vtPosez7>o$eJ%#ZnN@;|&3?fpX2>N#j#&ek?SpY+ zy&QTv!Hmv9SU3b>gM?h;k4H!uicmNnVKV<&FofL_nomH;H~A9~mJdVNDq*T=G7+Ko z1qgE{A{3g<681?*%0(#VKlp{PdN{&P3DZs6NeH7xAS|AQP-=EaNF0ffF&SZoSu`18 zy@b6IE;Xrn2zjFrR^}m;n>`X5k46}fk1*S;$Vb>D;h=;$rq2|FSr;O#oq|wl_Dg6p z24U<}gn4GoRD|sk>J=bdX+{?yEW8L|gM|6UFGNTgi%?jIu)tJF*e#)X5yB#qUxcuH z9Ku!!*P1592)!>xm{W|f*ld=tPeRf(ge9hY8p7&J5OzvfYT8am7?p#tcsfF0c1TDZ zkC0Jcs6^myP>GONgm6#-cZ0bIjf)Z1&PCvEAYqe)vGWkP8_YwPH4UNO z6$so7u0Uur9btn6?gm#PY?n}YB?5N?2@6XQnqP&$-QX&Olv0GP61W@8N7yZ4&U^&! z1`?K+AtYUmz}?_#gx)g{b|OT#dZyh1PL6$27B4_~*)wlTSv?aaV6!a~hcat6O1;G>?|bIL#VBnqL)jo@muHT=4rRNP!s}2z@=Udqg>z7v zFG2Z)jc^G{%H=3qrF`a@rq`qFmNMshls%r=B4v3cO43r4FFaGR6s7lEl$}!c;-4E( z_DNZM1IpLa6DxA-SO8FdB9UMb(>pJgbCSE8(3hVldck+NROfE!VM!ap~n z61YX&i?IAgggN&jaFLME`zC~> z`w+NG+=sAF!cGZXCsre@HVBJXBXFINFlsqM#u@~!6KfC>S0L<_z;)t&g!K|u-jBeI zLPFln2m>BK;8O7bLgQNy4ocu&@gTw`32Prj;A$aZ)~yI**CKGcSc}kRB|^Q25V&AG zgs@%01_|dH|6zoMw;>chjL_3mNl3XJq4^^S=bQXT5Oz!0Dxr^QvJPSS9SC#QA@nnw z5x8x%dK4I7$^`?>Ho+j%_Ay|vnI{-xb_j-=j*kPw%p$=BW|v^NNqqtsVU`L;nmvM1 zCj0lmXtP3aq4`!Y#`Jj-xX7#$j5YfOM) z!5p(+aJd=s3{Yv-2<95^Szw+SEx5w06I^Nh=YXqBj$pp25?pN>J`XG~`GSRJqhOI~ z@)zJ5GhJ}4*(~^-X|)MhY{~`KnQek4rtN0ndNWV3)a(%4U^;FA0<#D(iPgAm?-ty4 zqe*=MVZDTvFCZARM?&6H2m@Y3SYcMYh|u^C2nQwHV)|^Qs$0z}!Ai4VaGM$O5^%d& zBe=tOF9WO0Xu+Lko!~CxzXIHCas>C7D#5*`;WpqtlP_3pHVW35Ca(hbo9TiF%x1xZ zrqye}T2n4~$ZQilY}&pKJYwbv)|nlGM@`2!fXB=tz$8AWvA#japD?L!YOE4gzKQUp z*&`wEd5!fggetS*Esgar8mok-OrPxtn8Jl!gHodLdpvW&EG}%i^+c%VYh^>5;mJAI}w(@h%jd- z!V6}zgx*^blHNnuYRcb3*e78pLiFXRY4<*d$m*9+7Qc_OEo$DDGU{cNj1N#=i<)ab zKuLTBWv`Ss_@DdOg|c4C%3UaLMa}0@^0uK2_z-1B)ZF|bO5;~i4oZ0^YWjYJvPsI? zk5G0-%>gO1UPBrCG0OWM zVM@14*dQUn_}?Nd`~acwTZEXYl8~|sq4{?Rbxru2uGRC z5_*4xkn~rCdZzrZ2>T@Llu+Na{Q+V1#|VpmKxk-oNEr1ALdK5>$C*VxA|!r_uvfwf zCiN$T^%7S8gwV+Bk&yQp!hn4UP0WgY2#t3m9F%a9>9ZeUlZ3VV5t^I*5@zi|7<&NW zWV7Y~LYvPK>ivw+%8dS*WqgWRCunW_zX5!gAUMrb3HUDIAaI7s7w}zzpsi{0cYyB_ z1ZSGfg7&7>FF=wh7j!V&1ZUmYHesZ9_KoxS+wsO7{Ox2qdO&BhNYKUX5_C1GQGo9c z1j%NPfbS0yfK;;rFpa<0Kob(eqrMKpCJAfnAY_>R5@!8X1C1eMnKdzlHa}>fM<8UI z(MKR`m#{&?xyG-Hu<%EO!nz1OO_hX{pAecSBAjpX6A^Yx*eaoqY2qU+--j^AN9boZ zOX$5HA?Zkj0jB&&gnbfrN*H9?9)+;_0K(#<5Qdl?2s-nR28NkM0?vHFaFbdO;LI0{ zG^$Qy<{W7mPLg1)TW}fQ!u<0cU*c)8u%7qh2u8Y!-0Tp8yn^asfxZpxCrM5#Xp7OgB3OC8lE| zpwui9aMTNCnAFBNV}0EOLb@@|xYX>Cke7%spb0{`S zG5aOVIuc>*NeGo@%}EGtjzXx{3}K!b-3(#7gbfm|G=6i0g-0V4HbOGWQ1$YW(mFPBP6v%SZvB$BJ7i}Q^FF{wiRRKMj%*f zb_i}T9ZvxQvq-Sa>=N8)Qd{Am)Mpq=5@kd5SL~rJQ+DUjzf;pD& zuA;Xlm_HvC9bEVC_5aS#xhu^IGIJmJR&cxNa~g1mStVFy_6zPbLrw?oGHV2P8}AI@ z9y3~SuURL!&-iUL>c$LVaTB)7H8gf2+vV~mD1~hj`b8f|FrAvB^lpmsoRqZ*=0hp_ zr2KFar5;W&J({7cJ_)6HTU@cu2BMfm3gb4oq%nFLcId^W+nDts=%v^xfTKEYfe{7Zs)Teyj#*9SKz zm}`Vv7`X6-1pe0%<&x8AcVz<{urGUqPB1qMUr8|E3AZJfzQCHi`T8G0P}MuNFt_-2BM9uK~iU@jDHPcV-PcO;l2PXOO0I)v{K9iZ9N zjv8m2NR2y*4^7>yGf_@!gz`S)Z-mmOJ<1zWb}`Z>DBGocq6zf{BNTQ<_`y_3STCV@7lfZoeiwwiE(lvC z>^DuiA~f!bFsCcR&t|iPO%js2AsjU2-4JGVL)eKB{RIakC*YD~l*P$p@yw27vTT=- zk%Ew57NsC8OhMQyA!brj5mHhSR;D764DoI~`$d zIzm0OU&1~KW4j~NH*2~htnQ9bF9V^W8J)q3Jl3oe9B2GYk`psYF3cqP1XG2;4+#yk zfJP=?(AaDgG%-!i0h*fWf|JZt;%?iLQ?@8wl_M~%Xnm)Y{diO$D+Y2Gd>__1I zA96l$mRTb>+jzZ!j%Ku=lUXO|Z2Uez7n39CYN`a?OvAoFvdI^u@L%``QcaV7K$@8@ zNH?1W-A$|hK!zz7WSVV)EYo%XaE_TL$Tm9!Jxs@ez`14-U=|Lft9u91)t)AG5JJkJ zgnH+6=(YU$G0Q(Fns)xMl*rx3PZ@jv7lS{1>$KrV9nlLMSxziV*fm_(Vdn=~#@gdMv_?#R${QE(xQ?A@rPvP->P=LrAY91>~e%#%#h0w zHc5Cw!b;;+BFrj8$gM=U-K>+)rU>DLxd^LF&Rm4;5}y12*n8{nDz4~ZH|Hc191;kY zoIr30ArK)zk>F4y!KJta2n2!zmjrhRx^Z{+;10#zTAWhcX`vJf_gyn{I21yE-~H~r z&%J-#JiP2ZYp?Axd)CaE5y3*`J`ll(XawB{B3P{UA>f3Yc@WN0)kU0T>Zmx&Rkp!6 zD^zcBR;sh&tWx=g;H*{yanxU3N$B?>B(zoq3`MZ48-giA5v*6YMbIDyL4{$?D;eo8 zhdFn!rc090bhvX_tEG0r{E^OUEm)pvj&@F`{l=TclBt>toud=tCOYr5PI^(>C0VE5 zojY>Mbc#^($2tFOWx^A;o&=?sncK;USB%Cj%j1*~e#beVi%3&-3E_1zNc-ZtCA1js zywoCcwQjm|()5S#RMkM$U?~L-lCQ$2%v@oR06q@Ny_U>4x-X;U22S zaOae?ptQm%J@YY(NV8v*G~L+m}Em8E3UT32T{R^tj zGo2^uiFchzl|CnaMpsPLBQhqU8x5RxnG|H;9Or`CF}&$?Jw*>+leoznBID&IaobiW z+}PsmYe^rG!)O5MqwbwzdUuX!ZS=mEUSH`)P47pkTz`! z>Q%Y6p8^`XT`oCiwIqw_)=SD`RJWi}-8txd9!I}fq`sM)-+XjZkB>Q@)c&wo80D5L z&d)8@x?u@xPCDPQYI=QCi*wFz)tIwHzn#HCw)*Jk+%+O5CNiRHUoYZGNoq?j+8=xn zC8cysRy|YOMCYW=yS*(5&4xR7)EhHZ_wJFcrD&CNTBw>aK15AJ&8X{RXVv+l^B>8E z^TiK-vlcfkq2ndz92PbK6D~9BDc#4*&aW(HWoVp?n#}Xb&N3EG==nz7C&7<=gC5~~01^W!JuPvrLZOUtV!H4>7@OeOSSnsY$|%-|H+ieTehWu(jD?*TlqU;%H(xu&rO-UecRqe zuS}V|aa-cbTP%O)*?svtVanvS(+`m8{eRLFdLo==DxNZBUdURRveU>U6Zy)IhbcR2 z>UoRYl$|qWS&;c4lk%K5Wm)lWU_i*2xFGL>mJ<5FW>a_(MHy(>K;FYDY09g?r54#i zUh^xBy9nAF^~D`@s#-llBia6${|MV@57* z%KVYZV3EHv$fPDRz2Z$-Ia5!j*F;lR-jp%D^jGNPI8`uZf%qdO5&kM7(_hdQ#6f#g zSjkkBX&7b7Dw{HyhFwkBkI2N!=FF*&DXVJg$<*v;%Bq>NqR57tvg)R+nABgH!Z1@P zA3d32%EC<Ss!YfvQYf}bUoU? zjwvjSFbbh88uBA$8F*!&w3%QdWKxE*FahMRi76|GKczWko0;jAN0!QzH8=GtAZuaD zT8f_Ze?@4CPzHv)2wn241ldi+)~2j7vS4O}{6(0uAMqEFlJeKal*x)$)ReU~WmSHjXKur|VE_+?tj>)K_C$xqmXKW6BnqdJT}ZGH1ymT}JyiL>P!rMz@?CCHyA{GG)um$b7i?^QSwO zo3h5pZcEkqTVcwY;6IB@M*K=-5-jI}08`KYhW2L2ir7B_v&K|xj&QTI5r6B<1Y6)A zjVu&bUgIw5w1jb{-bPc_3K{!%{cp1ww>3xQ01--EGL9yd0@6t>|&-n}UU zZ!0o6xwM6x$mH*osn-twIr>uu)M--|iT}LW?`KR|du08P$sjvx$~u@b8Bg+Bc*-B^ z6vcrILK#r!O~sD*MJF1kz28i|9>^w}vb(0N zCo=h@p#1%A%6j3qzXkhVEJCSCZ;2D6-r3_qxA9C_|1uSa@61X{^`q2S_7Ho4f^S2dU$Gka~Rr**#gn3eMmH8n{9dNUE`dCBu^(Qb0qfLSmb z=D=K-2lHV8EQCd{7?wa&Xa>!p1+)a&WVVI~XanV-Jjl!_gDY<@PY1H`OfRo3&Va`Y zWY_5pSs)u^ha8X-azSp$1M<82^{^4Pz&6+pJ75>=hCMI^?$gLJ4P^^0n?~6q%8poe zz_K?yD;Ey3rIp?4Wsr@aZ2Yc)Z0v5rZIJz(T#d-p$1acyiM_BNWM4NBhQkOL38P>% zjDc}59%QdK0c3wS2_{3Fd{#)lkJJ>JL33yU@(rbi@Dnrw+3d;QPWEygp%X+yXOO*I zH;94m&;xpc?C1J|?BV)@oTLW9U>E|jkBc47eiZ025&{Oaa;3O$XV}&4gJn z8|J`VmUfGN%L5m$Y~}Fs`E>oG zs)cbwAS?JlHpr}oK5~iX`K_0aT)blCG$iY6YpBnJ_NT+;jky^hBV>Zi;0az}18>Lz zS-}ThGm-v)Kj96$g}*@FWjq6B!Yr5#b6_sWZyebqrqy?axyUp($ZjxHWq#t)C|34+ z^7;$e;}w8(bYFVN0O3#rAjtc@M0&InC zupM^5PS^#zVGrzueXt+o2j!DNez0CqUa?;ZPh}_zg+X?*b$q=1x=3Q|KFaFbE)4rw7BctCo{02v_@ zWCl-=k4?yFSw0XVyX7{}7G(D;dxTqX5@gpTyQKUe=Nk=@z*D;NcX$Nyp)9$~kCp5C zb+8^bz(x=&h*&tp3^NO6Lkx6>9?%ndL2u{-eL;4(tsw%oQ^*~#6ZXJfsEfP`ghFY! zOod9yrFbw0{!jpXAs6HU`Ly9L@GD$}Yj7QIz)iRXvJ1Wg@_j}5CgTZ^k3lAYd@OP* z87o+iU%pwn6#Sqd6oLTRiUr~chQbg6MW79MLl(#i@~zl4uol+C2G|IjKz7bsAOW_5 z?2yAD4CFJ!e05v@E;2S#r}U5#o>Qn7@TXe-+$AVh-r=2_37!Y?LO$>Ve<%orpbW^S zx-h&){{wskF%MWk_RR7voQ$+xEvO0EIL;2Nhy$}qwh%c6+j?f9@Lwfrt zx9p+ILS8wodEvG;03&dSMVDCfIs04oCNs<=yuov zD_|Mag^ExdO28>UYJD2yQ^FTPK0SO6wu5ZU<3TpwveE9TR=;F9KSArAgwt>aE)F$AD}pt zfRZjw>iwTCWn#lP2!|R_6KX+i=uGBapd6Hk3Q!Sbzn&9pkO|U)eE9Pn{0(nG_UZ>< z53Gb$An$EH5Asduy)cK~<1F{zID-pl;3{)B37({o3{pT!NCl}O4NN0}=`aIk!Yr5#b6_sagZZ!k7Q!M} z3`<}sEQ95+0#?E*SPg4nt$d?u9G-ZX09_#l#Bvh}9pMHID;Lj)!4tf|2HubbvVsp} zgY1w4azZZ14S66h-(|WJy#nTztmvph8@H){*7O;XdxPS(dY!r1b0!5)1gg_zqo$w2A5voB^Cu=8;{fGm#f)Yx|}p~WiVm040A zw;Ys(lyDw(**?llkQpH}<0X18;4bV|^V4eSV&#HS%%5Vv8-rgqhtfyh(3_Hmk<2-$ z4049c1#LWS+MNR^)ApPBxuqMz5K9c4?a2a-iY$|iZGN=gaIgdH5!?PB|5-A(L z*o4J@yX0_=l{*h&sS+!cSf^w|I0Od6AQ%X7Ag1;4Fa}1$P#6xwU<8OQN9;OdVH`|` zc$f$hcM`}Lt@%ZeR@?*9cn4rV8R&*?BBrlBi^O z9?rp8kcvp-NJY-TX*dPaKo{T=yeEIBD|r5fckmayhR5(0-oPVx2~XiF`~tti6A(QK zzXtcdRFD!`$pP6x zOdviWCXp=Q4M9*8ih!6)WM?e0!Vqlw?Keg>LQw8CliaP!bvy*$Ke)um)C|0jqIEwgJ||7T64%U?U{JcGDIAF4zN75h7;?D?~L2P|uD3oKd38n-w8oGhqXc#M2L;ENfBcT`#)0%F2Tn~^0uV<@m}!|eVb{GX7^lA9f5>b8TD$T5}#=zD9ioyk5s5A-Ck0B#_N zUCj^VVka-;203kIiWN;?$OqYpA+aP>{1V$AL`P(jx`g)zdsPFFNzo)s^dwFZ5QAM| zkodtMHk~hv{{zPr#mp@AslY)Q7=ph6^)8KH>MaHrJtO?G0m=sDkd=k9=sO96gBCf6`QxRS6W zDv4Dw^`%v+5M~3}Q`=9{E%CR2=Fkk9f|%{ZeAftms>6Q`p#ju~dLU*nk=KS=P!npv z4v@?TFKqGO-fXK(eSQrf=i-++ba&b+6 ztbX5W3IZ#cD%@$NUow<}l!95Ne-5r3mxI~(MMvr?dV)QU-4$KCKXI24`E`IKB(=7; zfISg=MytQd*dE_rP>E;Pm9X_-uZTTOdx7a|2Y2Y$N29k95Mrr;QkDUp*@M8H06mUc7`y?ASoG~ z#@(&lQ*#+CM(niU4(Ace6cw{)N=OdLK+K+>kbi{t@He~z$>uLyIfcqeWf{k^X1zuB zC%lGN@B*HLoL(Q{-i6;_u7sZCN^gOWSB z;EEww3T(j@gRm=TkOZV<#E>lUM8^$(YWP>{#v+jBO#}ZAH1Ge|y0L@FUu^3cK$=~~ zgY>;8ctB>bj|&+mQX9F~Y7a~IMYx0|2k8PiU&(D9N!P9~V@q0Ej>V5XKUVKIyMiPj z5u^}bCM@C7Y*Lsn>3I|_!v5Ev{jg^JC##^4`@D;djx8;(0nWFWG!Fb?8D!YAQQhKVo%NI)G(MFSo$*%DuBxkcEWhHkUidtuDEUbyLYQX!Vn)<Xv zGl1wdL?$}TD4pG1#*Md(T41b=u`2J0J*h0 z9ONExPv`;Np$l|^jv%*v#g$vbF(7wGKgmd1L0o%+;z}fOrNmN*FEg~anq)W)Bpu1r-o(-c3M8Y7bgN7SdmQNw zvsGgC=Iw+)5|BQzH?g$9m(6T1$PDCP7UoO;mj#t{B+fh#`5fp9BAW|Fi^xJk=@uYZ z1uJ0%EQe*V6qdkZSOaZg2Qy(k?mCe3#9CaLDfT~+iEjKo{^KuXuco`nOEd}$Y0|xD zDWAX)pKg(TBBI;)`KapNS_&dKCDixUl36llVi!-?H&Ki%i$Y2i;w!z~)c;79n533q zB&m^v;c||No0siF*s3Or+B-tT8skpLk>EYWreqzqz!4H@rsj8g%Z-V#C1yB6x~mph zwamQBt}l+)Wc1RK!m@U5JKs$3Yeum`{0sSGmtCUPq7W{dtTZT8a^L1NXKiXP6aoU! z2-fYbtX6TupBCMlcWJ6N65<~qQLhmv1tAj({xmlC=90w-38bcl`~&Z+e@L|OYZTm2 z7_no?xlBFJ`bpGcBpRq238k5u)qduC>&EfD5>?O5DU+(|qXk)Vs8}D$U5Wva8NGTj z&w4lfl>PvEg~_}SJ43b6N1MruhQhOH)p?u2Y;mTl$JvM_#*1XcYP@YsxAU2jq}5Xo zBwMFtDrh{{Y>&s%Ak)wrCRCoc<%-Kk|zdva>t zyn0|yYvgZ4zE8R5(!#u=qFc7azBukm^H}drpY*(_3=MO_S;a?anJib8dmG}c%0-;} zs#zN?SiR4s4dl%({c}^@_sTL-%WQLDmPwiG?r42F>XXZUy*4swI3cSOxwTB;UT8?6 z8&9ei_o!KuE3*GF{_+swMo953Lks-&qO-drL>~N-UQ4+$ebydFgHPxc4J<-alu z=-pvzU>*wbSk1)oI=~c{HuwElC0F#qw6#%3R|;`PokpSX4HRUw6dV?vq)2Gy=_tq` zrDf%@0_oOCtxLGJD8FTjo|7K(r`lJZ`iJDzJZpBo2mHUk;Ch>akG>v z86ekMPJPw*Qd)YoHJ?_Lm#4hUN7Pa(g)dH36@C|IstWZb*(s`oJQTsuVpIi&iwa4qsx-(PCfEuZl`PL zwarC{^rN*;sx70kyLBf-Ru5XJkSgFu?xE^aTfHnTZ)}o&C}|hMt$_t2Uf-c)T4l^T7V>0acYtd7&>N0mgqhAN0UTh zBjbW9RuoE6U}^h4dpvG=xTk-If(&{}7N$0mkXINABobF)LhBv9yA^SfLKq6aDT_aa z*rdF0y!Nmf$pTqybMYqGQ;oXo$f=PE_oqa6)nFX2rEK1$89jHNxf{RW%m+zNR-^!@ zjq0qVw^uzaOnT?g^Fi;F_xpAk&ey2tNbi-(T98qfhSNzd6ue_MYgyAAjgW+lq6ZTq zYh%9rL0cymxwx1R8L-spN7a$2yv5Bu5T~~q7_6n&Lg>Q-YKH{qlF!Zobv=NN-$+Sh z{5!90E3h;F^QtY1}CZ z+9UERs!V{EAqyod;$^H`hx4Aw<6eL8R;dj|rqFs}9dXlZvU=+gKNr+IGBY!?Fw%A* zZKEFc%9)klsG9w&4W#}5EeW(sjDYaJz4e`U+|160t9y^^QOZ$E_beEQXrc5uuVjvk ziqAoaj54YlLI}~~UhTa8%LAvlV^&K(B+{B@Z`W1@gJ_6us(LV;K9WFp0)HG)Df-Vs z^DgOG&<;g|9JMiHpw1Mj5KnvkZsd>NVY zLMHmi(ZKdzku<;5_;+jTSq3U^8LHo}{`Yz$r8@-{;Hb}30ifArgYqJ~Yk;r3l?_8_?6i&iH{&E^|+NMH_ zd>-B^x`@`{ixt&Jz22&2^y;6}m^5vFC>3_0+lR`;4))hiP?iAt(Kfwftnt>KA_6{5+r4 z!d}9^Fry_`L1R@Oo+RI#>I*k4)oT^tUku|&e)WW0tm_IY*AiNw+F6b>_FFnZ&P+wC z9D5cx)3X=q!Ny+78ld7{pl-U~Xu61|zWI&!Ri(=_#LS&kba~C&`Zhp~L*`|n_OeJU zo;v4==F_|k>6;6xv2U=Z4pg@)Q1^@|%Gsnx{Xp-r{lZvZ>@z4wpmIaQJQ3RbP?XbH z=enmG+^^p?Rxh#MZJ~jxX$7sC^v5bma(l3`m|0e|I1o9$@J2LcugXL+GnG^A-^8#@ zq2*-DUpoEGX+P{q+8<4GfIcd$T2|BoZ3BuJX=!b%bhw!!|7{QUBSADbS9wL$x{6w+ zkhN&Yo}og8%47O@xU>@uI-gPg^_JKOk(0uKBI={Wy=12TV;ZN4HFB@-;?Q_Th)nr> zK|h73&*eTaN9_}my0ObDLaNel-6PkWuT-JpD@WY&MU|~GJzED2 zX|r7?HdjnKy<#JW#xNCLnX_#x6!b0JleQ;EqH+Q9wP}y>fjk%k9Cks&viIG1K$q zBBpAYa%!0fhnG{=YLeXKa_Ws7&nu^VYvKM`PBpDXi#wM$R;L9)m%`>QepiHKW&cQv zlkCJ=TAXcOIitwOpK1-tZ7FfxU#BNhw}BUs^aR9W}|8L|^{)}|V_lW2nD8?#mkZU5t7VskNDm}PKGHFXMY+a@&9qA~2pQdJlJG2$OXqY!K8 z;cCiSSM#-=ucrL#YA-^*zP9(UUtHMrY=;ev^=j%@e=1DvtVeN{hZ)6*+*V>)uxrPAE*91feMR39re2^C za?;e;_xprp=`vP&>(IDqhJ;i*v1R_mO?@08FT+%+`owh!HwwI9`GEG%lIA++(8y?p zjBr`iu9^D}y&NIF;c5(VZ6(o=b?r!!8mi)hj{6-NHN(|jNj<{U*zfeLQtpS=(hiN@ zX2{jSZ7K##{dJ!sWNf%f-GI3BO^ucrJ|s(%)#HsrW3w4DsrZu|?Z?g-;Rrbzt{M~9 zb{!2_-{yD~+J63FtDhViufkQlq@J{fZeET{m9hN8Ics|S?9j+!hODd@*`#Y|o>`8N z;2P=%alNXbk&)ET&Uf=ZnD=40L*u@3Z>R;?GS@eD2|GM8q-`>O`XCnzXU?KBAA;+v z<_*cd1{$)bt2yJ@v(odf0vDf*&scxt!dXB{E;>#GmMwIyqyKZFzaSJUYOf@dB3Q&HSQx@0c zXqO7*tMq3;*vsCD5FbK{YF*$*Lejde<5tZA**HP^40_Ow>J(G59^u1pB0HoFf#jqh>|T}*wl@ZTdu zMqB$slzb=QPSiYn#9}u$!dR3J+tPiU{V+`?BbKb9 z>m$@Sv_tl_F;>8!iLLtVn>~+>(VabTga^@c#;B>o| zMYHdW*3aO=T}XYjd~QaSwz(E8U9NW)!>3vcM_Yfljf2cK&g9j#y|GbwuxwPCOP5?b zleO$NXxW!4x*VGTqXdCpXJLCw#EjHxX8MFjov5gu0QkOP9?uF)dtq< ztU3|b+NQJW*O6j%B@Nl|9eK0o)9_B#1ky0CaQb#uJJIwSf<|&Qt~GT3aBOci!PR# z{YlS0NVY+jI;+$X>`Q*{tQto!qYMKv7kg>cL5{tin=!{X?Kz;p-vrn{JLzGMda-S~h49Qpn zyQ+_j1hV&zRkCoMG~)QH#IL(R-A4|L9dT< zuP}1Q%A*cVw-{r|KT}}J$V+V|e>6iVRqhz|Ow!mIqq4Wxt1fw4Pj*+G+f$VLYGivY z$a=Sj+RdSDdrxB+O=7lH_UWsy4YUomBinu#&#e+hN{vXuoipwHZp!+yJ_ZM zZj6!$tj{uog9wqq(ynl`B?bMSiq+h_`ZbdO`~Y6J!r@Y;fJ-= zn%u8mCdSmDWV!nqsU``H8nxYLcWXk#f&zUmfnZQqY;YJ`kp#g@)~aj<{qS(i_o zCk@@w8Q|2Zzd6k-RmxW{L+CWCWq>RdbVbkp%Af8JNjbpi#&;t|4ULJ+-ALjZZ8n+^ zcT$~R@<3>zi7(ysGE*<^?ih=RX~wr&QpDj%bS798qLsm8f1)!lNo-$?7QsxExoW}=ypmuNYy~Y znsHFVkZ3L=LnfjsZBqMF4d*hcvXrLgY*?i9GtH3k(<`-m<+Y+9A%UC;xB@1dotbK`jbvmlf2^uPa4#L+x=yYB7S?#w|$T* z)rGh}6IW*Vu;DYOO}ZD&Mq+?mo^m~T!3_E3*RXcKrOn61lHOaSdV7!>LtNW)G-M2h z_w=1x`0QGymaf5#&yR!DUP(RGVB-qIYj>)WPyZOe{Vh`0d;D)gQWMvs*8s1a^KQ*@ zq#i#+rS8ggtKnu+oS~*T#>4}vs z<5VL;#SA}3ScckG5rGk+&usk+k}2eF4`b6fZvKY4|MWjy7Y*qdmfpawMtQ>u?q87k z*Z~hhm^)J5^h%BEMJ_Dg%X(4w>q=ddW4~$Y4abR9#I)@#oUY3ECI*I%UgB;Wu2%KN zqSkbVQHtZmD;8IM7h?@);OOi5L1vk>TDGeNo91bn?Y=(B^;W}3;@^kf{MJaK&lJje zZl-G4hg{eeOcmEOP}>rzne+tBNXS`7rylK)DzM4 zou^#;vqQRurVQ|f$EWq4+x}c6nsO6_6WTR3@H%JQTJu%sa}?{ud?U$r-PU~iJI%^S z3@@@`xE*EHL7RE_wJNFiWRD_2O_#fG2Mk zs26C2d`E-XzMr`6q~5j4wr74<3drO(SB~=wRH*@9R62(_Qrf-i7v~bXnJZHnLL}j| zWsw?#rtQEYp&c4_$JR`VPh9|_4o zNWmVr;*(v6dY7Fc5v>Rj*wN$)BuUQ9St|))>?2ZVN!=5qa7Oh2LcGO<@Q|L zze8c;B%<#zt}jz31~TVfEt@)sB;J#RtZ}YYf1bPQ*XMmbK4<@ZtDv@Ag%Q{0vE0bM z%Y*}6bNjXJ?XZ8FwN*0)G1dNFuC@$fQ~9~-6v1UMMKGH{)fkMH8O3&;0?C;8v#O-tcltycuuAP7 zqMfqxE2wcp>BBN>)v}>1;?>t0wep*}sf#sa7w1lU9~$?@0==4`A+tD@yJbkXi~~2J z!4fW$^QCGsObhbwfr8AU)$_}TwCI0pnun#0nW%irfv)8xE|G4PlKtJw%`$wg+C5D3 zF>gA`B}U{rWBkUPK6*4m=nuv@LLWo}*C~(T7?qQ5FbYtiYn`l18Wd;2@s&jR>r03S zIiGCSFeEr`mfTd7JrJpm-=O*pr_+|8kq(W~xjN0Pv##}HG|Y>%Z5z}&(YSzy%)LgQ zVWYRKx+_g4yZu0(MBbp{7Vv1wyW!eg`@PI{BeWV6UY-H6{MQ4ajMyP8RmPS3jL&1H5Z4cvMrpn|>Tfbu z<*y&-aB+0)p2u?OHacA+ zy1v?rMu-nqJkdyV=WKxAhvOd{8j0HX^thM^Q;#9;Xi^vRqHD~#)={TkTODx|H9YEZ zF>5v^t}Rj1sIIZRzKWOB6E(@|8i^V{^$^UeHzaPNW>sAyQFE#ug3;7(EYqj?PE~QN zcFG*bD*rfcyhQC(RmOcW4kjUF9IPLwRY=}vr*U>TvvcZrHg?YCRoHlnpu!CCjT?Jr zuMFH(Ocw7zOt;(=3O9!Kzs&S{2*&VP#I@BlH8TDEcR;OUArX$a*TdBr(RfMfGWZLX zozX18H4RN~Pd#Z36~C7!Y{)1gTxE%;D2d*&&@)Wb>aT~mgsUjx+7h+C>l#&RsCl9> z)-0@V+Pp!Q(Z9`i6b1Wy^L<89=7+0iQj{HLtK6R&^J;$Rn;j12XS44$pnLy%x3|}7>Co65t~#TU_>C96ut)7}{w6MKzeRN?IpT8j zq|kmXeKFcA(YrHx^oECwb*h&w^33fKO6gw7VJDRBu)0QETQM{|iTg34cM;!#E97}V^NE|=6H^`EM3+A?;3K1b@)4y&dTccmG(#J#^qRcjo{wX)r^aflGv zqxfvxnm+HeDPmDEZ64PTt5w9cy)rc(ta?7O`d?{wJ5o>nv$`dz=lI#Uk@4~9kt*je zdqp_XC`X7q-eFrm^3MIVf7u)%O@CJYlZo384awhc){j*dmmV%xDrWwZ3CT{#&8P-V zkKWne)Dg1fXVs6mw$o_H8QQ=9GVN^xO>B&2+`B)kb&|Tg=0r1fNhNU^5q?M1M@hW`8q(i>V_J*}*>>G-4I*yMBPwtTW9H0GHEu3< zAbjR%p6b|4_73LaVXc2u#ZM>X-V|+6;l)Rd!FMTju}*2zSN&1XN*_{d2$4Ncn#;Lu zv5%EBt$82Y-1u!hsU{2H+}xDdYAER8rxPv($Jax1O2n^Pv@MS5V0oEnffrzpfM-xnZ?iJspD^- z6yv$GoAbD!F`h@Bx=>4-eA=(ZjWYIf{`2h@B$-w7`QL`wW?eOgQ_z-dQ`T7*^Jp2_ z>$7#mRke=T){R%yvH4p0QajPiNLkZgGh!@k-=ll)kBz)3GnX@R;gN$7nRJ8KMV$66 z7QT%Txw5351+J-T3usMacj=qVUP9wxxBs~cUVCpCGrjJx7OC5B*@$^hPnzZ#q{0{S zc-~PIWKB8x=*`VnW9DC3HjG0!4q9rx*i@N~ieVQgs$-zNM<5;Z6eI?ob;MBC~TPZWi2bJL|4oV@)Hc z6i%0NvO9W9?IaiLom*S zF&fr^x7Ce}EQaHjXkpe~Bh?KNPQI;9t)=gH?$2*2?xNeOIj(IjWy(br>v>i_oAu?! zqmD9NxUJSKCFZT$s`M%{eu$=w^7L-&ck~(G2}?Sq7yFCTU$@l~~y=#=G;htyOtQ$}B>bV5SIGs<3bYK0N7pgaTb@a9#qA$N|@2arn z;HgH2%_=p z14E-@zAP$t! zRqar6@`x_yuSsDF8>8&|Aq;H{YRrgI;EvnvCew(yR-rrvXzNv(oxQU*zcZk(&_RlrS z^~?`dPr|B;E9{~<+-lGHTgVAJ<-Sa zbd5x>n)MLu5YHs;E6SdM#j#eYGx=@a?n@nIPxOI4J#Lm7s>D`G7F@%K8`XTlr%MyI zm2t#%-mN;L;hpG%eR>*u_}uB@t=dNOu}$xjAB{E)e|KQ>PM4N5=yy3`akgP$SL3&7 z0qLKhB4^CIPqusR_4((l)pCnOE^gC`ndZGrh53l8zSR59SQEdfPgGxh^yojq{6vUc zW}e9Fb^B?Ru`9TZQN%xpEchvHlkNP{YNwj8o$2+qy;`-M^UmU<>hN~XFKa*XUia-< z)sVfPjBUxInyvcQ7@c?c_!v zPkHFwwIb!EkDtn2l(U?fm%fk6%rhy|cd}Wm!aFR)n!uxGr-)bh633}Hp0i@&;|%G~ z$;~BsI)csOZ-mHBWm)f!(LwXP=tQZPYy?7;*Dii7Gt9+M`0MY!?e;gAyx3}qMj{*2 zs%rjjep|efL)neAZg^n-oHX+^t7R$@xh_xSIRW;lGMP`JA>|23{_?SJMGqINWg8N) z7bkj;PG0~*)mh5JGYF3jNA8_kGODbUp>XB6Z=N?W&+&<*XIhyeTRXNUcXz-aVncXe}vubQ1MqH#@A&NsJntTARK2^g8SJp}{RB(b%6vmD)q^ zHaw)d?%`4GL>Df7&188QP}fLw;m|`2L!$op`gd%J6veP5e&?}aJ(=&A7KzLH zb3+Q3=mX7q-0zqciEEe^^}>C}x`>itUHr~-$$AEfF6ep)CdD=TSc1M|QY3Z5q?lgA zdooOY5amQyLp?dehM39r9Rnh1jCWJ5_tUA~?rPe83@8QM)jc`3pX-gwj?Ak10UlTW zjxOTsH_LEj?{2hSq{<_O^GEd zpx5%)n``cG<~1w&9+yQjk>!($$^6Fh9Yx&FHibC{4a>)cgVcf{n*9)NNLtnT5QDlt z_9&Tzi$~7O;TyJq^~k=7K9*MPKEypL>N)N(6`0~-RG@sCbxUK$o*AXzxYwt45qGu! zFn5^mqv1|kt;cMs7kO>YGt!bvAlg#@DVF{KmffFCf0XL`jnXKCYWg#+og<@~C~oJB z%Kr$J>Yq{F`k98AkkME?V%sdvabWBl41wkxce+G~tX0LZwji zTsJR^Qx4)D%O2b3b%CCP9U4Jq$dT#UvmJbW`-`6{)$>xeWAt4So2qe)zN=tU?T_(D zQVp9rC-NpX<$9bk7-KUAxoynUuH~CPuj;{l1l~o!P#$1Yb&m6Ekr6f(cU;SqV-cFt zw%`7M-5#RUm7M1!Wd$kj2$d;kJhTjr6JNz)3C0xny>WYId8;rdN1!l+*HQ|72%yshZ`u%Vt=zLlXU6 zj=n;D$YJb?dOSQ8KW@_c2P_okqY77Zs*0zuL?rU`qiM}xwOn(G!L%TkI&z9Z)Gv?v zz_H$EMd&kqH@Ifm>*2L#tjENn|7f0NIPp%zI^!f&*;PWz*nvLN_iD%MO+9(;!k}hW zh9NzTjpzHF*sWa)dFtBf(#@qO#(kRkPTxxf$s_2Abgv8>iXz<`T9UDmNcWm5bg$m4 z@h_I0sMzB~4tmB6p}*BwB6}y&y`~D?s`jDcEWLQO3ui zX7jf*%Qvq@MVHbtCdH#Kqsljw6N>R;x=cl~=QkGpTl>;~9CD@IUAZLC8^9@u5EkW{5F-CW zWbf&y$c0SbCoNy39K8Gsm9?k@lqUT{sK^9bzN7i0g@fAkaHMjL5SceNuXm$=4@z^% z5%LF-V~OmX+Rvi0mL(%!KVxORxS?JA?wJpHm`HZu!E}2?R8ER+D?De*;2DU=BA&T; zX5;bRO>9|pv$>?1ceBp=JC4{lPze`ZyXQZ2?a4VZ_?)G^5dTq5dsUp92U%3Hve>=M z$KGt-ycI|;P_}FH7r7i=x?Z1(`twCR;x~^6;sPVlkj;J)k7gcyAC_H9g*`5vN`IRDQjQQ~)J#;Pisd_f zxEfLAd0o!ZJZ0JXG*;qZwf;1l^or^@jx8L0vFKIa-PpPF(dKfG)_lHqRK(OXoS(jZ z81v5}#;P99E9#__evMY7`)rT ziWwuS*Tj;wYv%T-A?AF26U3uDr_WH*tthyWUAyO-p4SSF$wzj~%OX_G*f?fZ-e-9e zi}AZ0^1BT>4f z@)%yQlko^&p!IDbwdfqH^#pbK96xQ~(zf(@V&+m~&vSj(r=-y$Nk^7Ta&zGV`*TTj z*HE?ZyjCHFbEw6sJY^6gkoyH*Lh$aUaV?!BtZ&cG`JR=fQ2MQh0H?*O+y%}DqPhMe zqy4|lMXO0nHy8bU;s3f_P4d&tcGWLobo^hltLbsaciCw%J8i|>;j1Vqs$Na)Q#=5-kU1nusSN6PW zEFjP+(caWU_>FPuU&%i)lcW(h(caYK@*CsE#I+@MNp8gDx;S1^Ps}7~Xe8R3dg}bf z_=dzy%p_^VO|&=lxctW0?^H_nT&~BPqY;wWMYy4nXm9GN^H|jyiJO>7(ukXA zZ|ZTeGJGIzqQ5aV;@(#UuW@U3Uq0h*spIV>zMRwL&v+u5U#0cGrmwW}e6*#cS}#ZX z&DqRet+;oXmJxB}VeqQ%>uz8#`gi2QU%#QjI8y`_A@zBN&C>BC_5l5LG=<2wIyrgO zns40MN)}z>@S~bDcIZ#S>(3^d1v3_XwdT6!TJ`HU0rK7@{Y`*HoyuB_WbCg1bhE_Q zHI}ijU*~6rs4nvIuef}gizcS8smlAt=QE6+nb&tB(hgq~$r7Z7-q7;LR;p)QIDK70 zEU_$|^i|=nUpVOTxrG0x%T5tGMf8f1-Wz!Ik6%J-HB|*~YPk}B*I9@^yOS?U>=jYp zIBUm_kGqnk$Y!3!(oLf5NAyPq)2k;pwJgTtgNFa}6_D2xqKb*;M&X*hn|RJHVVpEL z@T#O*8jg zzIJcn42QzkS3QnP0sA$cE%CEI)+?tk?#p|6C3`k-;KY^xW2`p%TouxXOYLx?;ju+_u1I-}>Z3XYI!&D0a( z+9J`&hDPnUJZ(as{yxT`F{+vJd49P2|_NI3b`K* zvnBYrrf?`cCPeIF+uHl4iE|nxk4~6nu;*>>Bzxl|X{Tn^1u;H`{4GsUat=0=A(|r;Xps4`;q^ z3$y3G85m5)c#A8K%yh8m?>hK6;?tpVJ7V5AQb-!5uKi7`WsNf0JGpC{)!`36>~&}q zk5X>$(FjLFUPF;OZa~wuH=FKsXhcP+=2nKmr6|=DP3xT~RpTQuo)JT?L6_$9YO?-f zLw91BOTdRHwMsP8bX0TDwA%YHM@RKUjzc>tj}HtaGbQWsj%wWpLb_m)V@uwwz}=l#S~OQWK;f3TYP#|d1%)h8{cd5MC7DY zK!sXewXZwsKZ4`6A^e_xwzrPFWa5smuRZil?bURdo9_j>X+B%^j zC&VQz&R!e&BbtFdcUPa;T+=2$)7wz1U{hOMTm!8+`>03a zV(rg@Qb?gbMj7Y2Uyc21RBE{|FK;nmt5t~*G3bprk*{2yi!&@{2$mY|14L=0VP=#f z&U~MC^ZlASpt*+DnmNRHQegCe2BDL3Ebh9PV_7gh%{t}(JU;Wwjb`|sh;`XqkKcW< z%MAbRR*P}lO0`*koMSPA{dT#|&KhmTL|q=DesXoqlD)`!tjAjLpqt07V{obt7>HYeoeN-JcX!U zC2@VHJ|uPhJ5AfJEm}uLbc!j^Ev9Q^r?v^tlexaId?US?!nJcQp0jbymXK+tYYIz3 zcz|n5SJk#hlJp7ns=9h6agXjyA`wv$9V248_Ac0>b7VqvE!X79Q?!nViD(s5AS$wD ztAyT-U8kl{>8rR-NJ!JpH7ZleRxLV3cJCHJXiP*x-#FL478UovHK}S`*)>!xo8p=w zA>|;~lNMF_2iK;m^I+E`3GWBHW^zr)KFal?TSAUmu9-8WY!w|9-L*ig=uSN%x~eia zUAnmC5`7R zhP+|0VIeyu$?=t}Y^=;;-j<#0`~#NC#z!_*@?&LlH>b|I_wMewb)1t7p7S`8ftOko zKAvV!!ND#y%eDjW7AH!eo6VNoRIjDyiH*i>#o=XP&jdPQ#EZS=ccIi^iR|ECz9up6>b`-GR5b)c8 z+i~ggmFxbaOf0P?Q`A<(tFn<4{uD(ETO;tZ%#<1iVrbxqDHYs}K^ZfK`1tk$If|o{ z(?QkrT^yFPXUdI>?>fp@AHoc^Hn{)keVRDPb@n@OmCwo{2$v!l=3|c%_=u>zRQDpw zvnbksMLjCyH#M`#a17C3BJd%GCMO)0UJv`SsCB9PggNJ^;boz~*%_4qH*ftS&UjjU FRX? { - const memory = await readReliverseMemory(); +export async function askGithubName(): Promise { + try { + const memory = await readReliverseMemory(); + if (!memory) { + relinka("error", "Failed to read reliverse memory"); + return null; + } - let placeholder = ""; - let content = ""; + if (memory.githubUsername && memory.githubUsername !== "") { + return memory.githubUsername; + } - if (memory.githubUsername) { - placeholder = memory.githubUsername; - content = `Last used GitHub username: ${pc.cyanBright(placeholder)}`; - } + const ghUsername = await inputPrompt({ + title: "What's your GitHub username?", + content: + "šŸ’” If you don't have a GitHub account, you can create one for free at https://github.com/signup", + validate: (value: string | undefined) => { + if (!value?.trim()) { + return "GitHub username is required for deployment"; + } + if (!/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i.test(value)) { + return "Invalid GitHub username format"; + } + return true; + }, + }); - const githubUsername = await inputPrompt({ - title: "What's your GitHub username?", - placeholder, - content, - validate: (value: string | undefined) => { - if (!value?.trim()) { - return "GitHub username is required for deployment"; - } - if (!/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i.test(value)) { - return "Invalid GitHub username format"; - } - return true; - }, - }); + if (ghUsername !== "" && ghUsername !== memory.githubUsername) { + await updateReliverseMemory({ + githubUsername: ghUsername, + }); + } else { + relinka( + "error", + "Something went wrong while saving your GitHub username...", + ); + } - if (githubUsername && githubUsername !== placeholder) { - await updateReliverseMemory({ - githubUsername, - }); + return ghUsername; + } catch (error) { + relinka( + "error", + "Failed to get GitHub username:", + error instanceof Error ? error.message : String(error), + ); + return null; } - - return githubUsername; } diff --git a/src/app/menu/askVercelName.ts b/src/app/menu/askVercelName.ts index c8b9b79..ecc4727 100644 --- a/src/app/menu/askVercelName.ts +++ b/src/app/menu/askVercelName.ts @@ -1,5 +1,4 @@ import { inputPrompt } from "@reliverse/prompts"; -import pc from "picocolors"; import { readReliverseMemory, @@ -10,33 +9,31 @@ export async function askVercelName(): Promise { const memory = await readReliverseMemory(); let placeholder = ""; - let content = ""; if (memory.vercelUsername) { placeholder = memory.vercelUsername; - content = `Last used Vercel username: ${pc.cyanBright(placeholder)}`; } - const vercelUsername = await inputPrompt({ - title: "What's your Vercel team name?", - placeholder, - content, - validate: (value: string): string | boolean => { - if (!value?.trim()) { - return "Vercel username is required for deployment"; - } - if (!/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i.test(value)) { - return "Invalid Vercel username format"; - } - return true; - }, - }); + if (placeholder === "") { + placeholder = await inputPrompt({ + title: "What's your Vercel team name?", + validate: (value: string): string | boolean => { + if (!value?.trim()) { + return "Vercel username is required for deployment"; + } + if (!/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i.test(value)) { + return "Invalid Vercel username format"; + } + return true; + }, + }); + } - if (vercelUsername && vercelUsername !== placeholder) { + if (placeholder !== "" && placeholder !== memory.vercelUsername) { await updateReliverseMemory({ - vercelUsername, + vercelUsername: placeholder, }); } - return vercelUsername; + return placeholder; } diff --git a/src/app/menu/buildBrandNewThing.ts b/src/app/menu/buildBrandNewThing.ts index fd2435a..df78064 100644 --- a/src/app/menu/buildBrandNewThing.ts +++ b/src/app/menu/buildBrandNewThing.ts @@ -273,14 +273,14 @@ export async function buildBrandNewThing( title: "Should I continue with advanced or simple mode?", options: [ { - label: "Advanced", + label: pc.bold(pc.greenBright("Advanced")), value: "recommended", - hint: pc.greenBright("āœØ recommended"), + hint: pc.greenBright(pc.reset("āœØ STABLE & RECOMMENDED")), }, { - label: "Simple", + label: pc.dim(pc.red("Simple")), value: "offline", - hint: pc.redBright("šŸšØ experimental, offline"), + hint: pc.red("šŸšØ experimental, offline"), }, ], }); diff --git a/src/app/menu/compose-env-file/mod.ts b/src/app/menu/compose-env-file/mod.ts index 8062ef4..5fdd31e 100644 --- a/src/app/menu/compose-env-file/mod.ts +++ b/src/app/menu/compose-env-file/mod.ts @@ -2,7 +2,6 @@ import { selectPrompt, inputPrompt, confirmPrompt } from "@reliverse/prompts"; import { execa } from "execa"; import fs from "fs-extra"; import open from "open"; -import pc from "picocolors"; import { relinka } from "~/utils/console.js"; @@ -81,8 +80,8 @@ export async function composeEnvFile( "Please provide the path to your existing .env file or directory:", placeholder: process.platform === "win32" - ? `Enter the path (e.g. ${pc.cyanBright("C:\\Users\\name\\project\\.env")} or ${pc.cyanBright("C:\\Users\\name\\project")})` - : `Enter the path (e.g. ${pc.cyanBright("/home/user/project/.env")} or ${pc.cyanBright("/home/user/project")})`, + ? `Enter the path (e.g. "C:\\Users\\name\\project\\.env" or "C:\\Users\\name\\project"` + : `Enter the path (e.g. "/home/user/project/.env" or "/home/user/project"`, content: "You can provide either the .env file path or the directory containing it.", contentColor: "yellowBright", diff --git a/src/app/menu/createWebProject.ts b/src/app/menu/createWebProject.ts index d0b5d76..65e1829 100644 --- a/src/app/menu/createWebProject.ts +++ b/src/app/menu/createWebProject.ts @@ -1,4 +1,4 @@ -import { confirmPrompt, task } from "@reliverse/prompts"; +import { confirmPrompt, spinnerTaskPrompt } from "@reliverse/prompts"; import { multiselectPrompt, nextStepsPrompt } from "@reliverse/prompts"; import { execa } from "execa"; import fs from "fs-extra"; @@ -6,7 +6,7 @@ import { installDependencies } from "nypm"; import open from "open"; import path from "pathe"; -import type { Behavior, ReliverseConfig } from "~/types.js"; +import type { Behavior, DeploymentService, ReliverseConfig } from "~/types.js"; import { decideBehavior } from "~/utils/behavior.js"; import { relinka } from "~/utils/console.js"; @@ -67,12 +67,12 @@ export async function createWebProject({ relinka("info", `Now I'm downloading the ${template} template...`); - await task({ + await spinnerTaskPrompt({ spinnerSolution: "ora", initialMessage: "Downloading template...", successMessage: "āœ… Template downloaded successfully!", errorMessage: "āŒ Failed to download template...", - async action(updateMessage) { + async action(updateMessage: (message: string) => void) { const dir = await downloadGitRepo(projectName, template, isDev); if (!dir) { throw new Error("Failed to create target directory"); @@ -82,13 +82,13 @@ export async function createWebProject({ }, }); - await task({ + await spinnerTaskPrompt({ spinnerSolution: "ora", initialMessage: "Editing some texts in the initialized files...", successMessage: "āœ… I edited some texts in the initialized files for you.", errorMessage: "āŒ I've failed to edit some texts in the initialized files...", - async action(updateMessage) { + async action(updateMessage: (message: string) => void) { const { author, projectName: oldProjectName } = extractRepoInfo(template); updateMessage("Some magic is happening... This may take a while..."); await replaceStringsInFiles(targetDir, { @@ -121,12 +121,12 @@ export async function createWebProject({ } if (i18nShouldBeEnabled && !i18nFolderExists) { - await task({ + await spinnerTaskPrompt({ spinnerSolution: "ora", initialMessage: "Moving app to locale...", successMessage: "āœ… I moved app to locale successfully!", errorMessage: "āŒ I've failed to move app to locale...", - async action(updateMessage) { + async action(updateMessage: (message: string) => void) { try { await i18nMove(targetDir, "moveLocaleToApp"); updateMessage( @@ -147,12 +147,12 @@ export async function createWebProject({ if (!i18nShouldBeEnabled && i18nFolderExists) { relinka("info", "Converting from i18n version to non-i18n..."); - await task({ + await spinnerTaskPrompt({ spinnerSolution: "ora", initialMessage: "Moving app to locale...", successMessage: "āœ… I moved app to locale successfully!", errorMessage: "āŒ I've failed to move app to locale...", - async action(updateMessage) { + async action(updateMessage: (message: string) => void) { await i18nMove(targetDir, "moveLocaleToApp"); updateMessage("Some magic is happening... This may take a while..."); await setupI18nFiles(targetDir); @@ -271,15 +271,17 @@ export async function createWebProject({ const vscodeInstalled = isVSCodeInstalled(); + let deployService: DeploymentService = "none"; + // We skip git deploy if no config is provided if (config) { - await promptGitDeploy(projectName, config, targetDir); + deployService = await promptGitDeploy(projectName, config, targetDir); } await generateReliverseFile({ projectName, frontendUsername, - deployService: "Vercel", + deployService, domain, targetDir, i18nShouldBeEnabled: defaultI18nShouldBeEnabled, @@ -302,6 +304,7 @@ export async function createWebProject({ const nextActions = await multiselectPrompt({ title: "What would you like to do next?", + allowAllUnselected: true, titleColor: "cyanBright", defaultValue: ["ide"], options: [ @@ -323,7 +326,10 @@ export async function createWebProject({ for (const action of nextActions) { if (action === "docs") { - relinka("info", "Opening Reliverse Documentation..."); + relinka( + "info", + "Opening Reliverse Documentation ( https://docs.reliverse.org )...", + ); try { await open("https://docs.reliverse.org"); } catch (error) { @@ -334,7 +340,10 @@ export async function createWebProject({ ); } } else if (action === "discord") { - relinka("info", "Joining Reliverse Discord server..."); + relinka( + "info", + "Opening Reliverse Discord server ( https://discord.gg/Pb8uKbwpsJ )...", + ); try { await open("https://discord.gg/Pb8uKbwpsJ"); } catch (error) { diff --git a/src/app/menu/generateProjectConfigs.ts b/src/app/menu/generateProjectConfigs.ts index 550a5a3..d56f217 100644 --- a/src/app/menu/generateProjectConfigs.ts +++ b/src/app/menu/generateProjectConfigs.ts @@ -1,4 +1,4 @@ -import { task } from "@reliverse/prompts"; +import { spinnerTaskPrompt } from "@reliverse/prompts"; import { destr } from "destr"; import fs from "fs-extra"; import path from "pathe"; @@ -146,7 +146,6 @@ async function generateVSCodeSettings( "markdownlint.config": { MD033: false, }, - // Removed ".reliverserules" association since it's no longer used "typescript.enablePromptUseWorkspaceTsdk": true, }; @@ -197,7 +196,7 @@ async function generateConfigFiles( overwrite = false, filesToGenerate: string[] = [], ): Promise { - await task({ + await spinnerTaskPrompt({ spinnerSolution: "ora", initialMessage: filesToGenerate.length === 0 @@ -213,7 +212,6 @@ async function generateConfigFiles( ); }; - // Removed .reliverserules since it's merged into .reliverse const configGenerators = { ".reliverse": () => generateReliverseConfig(targetDir, overwrite), "biome.json": () => generateBiomeConfig(targetDir, overwrite), diff --git a/src/app/menu/git-deploy-prompts/helpers/deploy.ts b/src/app/menu/git-deploy-prompts/helpers/deploy.ts index 9d9fecd..3c6bb42 100644 --- a/src/app/menu/git-deploy-prompts/helpers/deploy.ts +++ b/src/app/menu/git-deploy-prompts/helpers/deploy.ts @@ -23,10 +23,16 @@ export async function selectDeploymentService( return await selectPrompt({ title: "Select deployment service", options: [ - { label: "Vercel (Recommended)", value: "Vercel" }, - { label: "...", value: "none", hint: pc.dim("coming soon") }, + { label: "Vercel", value: "vercel", hint: "recommended" }, + { label: "None", value: "none", hint: "skip deployment" }, + { + label: "...", + value: "deno", + hint: pc.dim("coming soon"), + disabled: true, + }, ], - defaultValue: "Vercel", + defaultValue: "vercel", }); } @@ -35,35 +41,35 @@ export async function deployProject( config: ReliverseConfig, targetDir: string, domain = "", -): Promise { +): Promise { try { - const shouldUseDataFromConfig = - config?.experimental?.skipPromptsUseAutoBehavior ?? false; - const deployService = await selectDeploymentService(config); if (deployService === "none") { relinka("info", "No deployment service selected. Skipping deployment."); - return false; + return "none"; } const memory = await readReliverseMemory(); if (!memory) { relinka("error", "Failed to read reliverse memory"); - return false; + return "none"; } - if (deployService === "Vercel") { + if (deployService === "vercel") { const { askGithubName } = await import("~/app/menu/askGithubName.js"); + const githubUsername = memory.githubUsername + ? memory.githubUsername + : await askGithubName(); + const { askVercelName } = await import("~/app/menu/askVercelName.js"); + const vercelUsername = memory.vercelUsername + ? memory.vercelUsername + : await askVercelName(); - const githubUsername = - shouldUseDataFromConfig && memory.githubUsername - ? memory.githubUsername - : await askGithubName(); - const vercelUsername = - shouldUseDataFromConfig && memory.vercelUsername - ? memory.vercelUsername - : await askVercelName(); + if (!githubUsername || !vercelUsername) { + relinka("error", "Could not determine GitHub or Vercel username"); + return "none"; + } await createVercelDeployment( targetDir, @@ -72,17 +78,17 @@ export async function deployProject( vercelUsername, domain, ); - return true; + return "vercel"; } relinka("info", `Deployment to ${deployService} is not yet implemented.`); - return false; + return "none"; } catch (error) { relinka( "error", "Error deploying project:", error instanceof Error ? error.message : String(error), ); - return false; + return "none"; } } diff --git a/src/app/menu/git-deploy-prompts/helpers/git.ts b/src/app/menu/git-deploy-prompts/helpers/git.ts index 79b3b82..1ce05f0 100644 --- a/src/app/menu/git-deploy-prompts/helpers/git.ts +++ b/src/app/menu/git-deploy-prompts/helpers/git.ts @@ -4,18 +4,44 @@ import fs from "fs-extra"; import path from "pathe"; import { simpleGit } from "simple-git"; -import type { ReliverseConfig } from "~/types.js"; - +import { askGithubName } from "~/app/menu/askGithubName.js"; import { readReliverseMemory } from "~/args/memory/impl.js"; import { relinka } from "~/utils/console.js"; import { createGithubRepo } from "./github.js"; +/** + * Checks if the given directory is a git repository + * @param dir - Directory to check + * @returns Promise - Whether the directory is a git repository + */ async function isGitRepo(dir: string): Promise { try { + if (!(await fs.pathExists(dir))) { + relinka("error", `Directory does not exist: ${dir}`); + return false; + } + + const gitDir = path.join(dir, ".git"); + if (!(await fs.pathExists(gitDir))) { + return false; + } + const git = simpleGit({ baseDir: dir }); return await git.checkIsRepo(); - } catch { + } catch (error) { + // Only log if it's not a "not a git repo" error + if ( + !( + error instanceof Error && error.message.includes("not a git repository") + ) + ) { + relinka( + "error", + "Error checking git repository:", + error instanceof Error ? error.message : String(error), + ); + } return false; } } @@ -28,12 +54,6 @@ export async function initGit(dir: string): Promise { return false; } - // Check if git is already initialized - if (await isGitRepo(dir)) { - relinka("info", "Git repository already initialized."); - return true; - } - // Clean any partial .git directory const gitDir = path.join(dir, ".git"); if (await fs.pathExists(gitDir)) { @@ -70,39 +90,53 @@ export async function initGit(dir: string): Promise { } } -export async function createRepo( +/** + * Creates a GitHub repository and sets it up locally + * @param projectName - Name of the project/repository + * @param targetDir - Local directory path + * @param config - Reliverse configuration + * @returns Promise - Whether the operation was successful + */ +export async function createGithubRepository( projectName: string, targetDir: string, - config: ReliverseConfig, ): Promise { try { - const shouldUseDataFromConfig = - config?.experimental?.skipPromptsUseAutoBehavior ?? false; const memory = await readReliverseMemory(); if (!memory) { relinka("error", "Failed to read reliverse memory"); return false; } - // Import askGithubName dynamically to avoid circular dependencies - const { askGithubName } = await import("~/app/menu/askGithubName.js"); - const githubUsername = - shouldUseDataFromConfig && memory.githubUsername - ? memory.githubUsername - : await askGithubName(); + const githubUsername = await askGithubName(); + if (!githubUsername) { + relinka("error", "Could not determine GitHub username"); + return false; + } - return await createGithubRepo( + const success = await createGithubRepo( memory, projectName, githubUsername, targetDir, ); + + if (!success) { + relinka("error", "Failed to create GitHub repository"); + return false; + } + + return true; } catch (error) { - relinka( - "error", - "Error creating GitHub repository:", - error instanceof Error ? error.message : String(error), - ); + if (error instanceof Error && error.message.includes("already exists")) { + relinka("error", `Repository '${projectName}' already exists on GitHub`); + } else { + relinka( + "error", + "Failed to create GitHub repository:", + error instanceof Error ? error.message : String(error), + ); + } return false; } } @@ -120,7 +154,10 @@ export async function setupGitRemote( } if (!(await isGitRepo(dir))) { - relinka("error", "Not a git repository. Please initialize git first."); + relinka( + "error", + "Not a git repository, git should be initialized before setupGitRemote. Something went wrong. Please notify developers.", + ); return false; } diff --git a/src/app/menu/git-deploy-prompts/helpers/vercel.ts b/src/app/menu/git-deploy-prompts/helpers/vercel.ts index 807a18a..53963dd 100644 --- a/src/app/menu/git-deploy-prompts/helpers/vercel.ts +++ b/src/app/menu/git-deploy-prompts/helpers/vercel.ts @@ -1,9 +1,8 @@ import type { Vercel as VercelClient } from "@vercel/sdk"; -import { inputPrompt, task } from "@reliverse/prompts"; +import { inputPrompt, spinnerTaskPrompt } from "@reliverse/prompts"; import fs from "fs-extra"; import path from "pathe"; -import { simpleGit } from "simple-git"; import { readReliverseMemory, @@ -17,7 +16,6 @@ async function ensureVercelToken(): Promise { return memory.vercelKey; } - relinka("info", "Opening Vercel tokens page in your browser..."); const vercelToken = await inputPrompt({ title: "Please create and paste your Vercel token:", content: "Visit šŸ‘‰ https://vercel.com/account/tokens", @@ -43,23 +41,6 @@ async function ensureVercelToken(): Promise { return vercelToken; } -async function pushToRemoteIfNeeded(targetDir: string) { - relinka("info", "Pushing to remote repository..."); - const git = simpleGit({ baseDir: targetDir }); - - try { - await git.push("origin", "main", ["--set-upstream"]); - relinka("success", "Successfully pushed to remote repository!"); - } catch (pushError) { - relinka( - "error", - "Failed to push to repository:", - pushError instanceof Error ? pushError.message : String(pushError), - ); - throw pushError; - } -} - async function createVercelProject( vercel: VercelClient, repoOwner: string, @@ -181,7 +162,7 @@ async function createAndCheckVercelDeployment( let deploymentStatus: string | undefined; let deploymentURL: string | undefined; - await task({ + await spinnerTaskPrompt({ spinnerSolution: "ora", initialMessage: "Checking deployment status...", successMessage: "āœ… Deployment status check complete", @@ -236,8 +217,6 @@ export async function createVercelDeployment( domain: string, ) { try { - await pushToRemoteIfNeeded(targetDir); - relinka("info", "Checking for Vercel authentication..."); const vercelToken = await ensureVercelToken(); const { Vercel } = await import("@vercel/sdk"); diff --git a/src/app/menu/git-deploy-prompts/mod.ts b/src/app/menu/git-deploy-prompts/mod.ts index ae785e7..69a85ff 100644 --- a/src/app/menu/git-deploy-prompts/mod.ts +++ b/src/app/menu/git-deploy-prompts/mod.ts @@ -1,109 +1,158 @@ import { confirmPrompt } from "@reliverse/prompts"; -import type { Behavior, ReliverseConfig } from "~/types.js"; +import type { DeploymentService, ReliverseConfig } from "~/types.js"; import { relinka } from "~/utils/console.js"; import { deployProject } from "./helpers/deploy.js"; -import { createRepo, initGit } from "./helpers/git.js"; +import { createGithubRepository, initGit } from "./helpers/git.js"; +type DecisionKey = "gitBehavior" | "deployBehavior"; + +/** + * Makes a decision based on config or user prompt + * @param config - Reliverse configuration + * @param behaviorKey - Which behavior to check + * @param title - Prompt title for user + * @param defaultValue - Default value if prompting + */ async function decide( config: ReliverseConfig, - behaviorKey: keyof NonNullable, + behaviorKey: DecisionKey, title: string, defaultValue = true, ): Promise { - const behavior = (config?.experimental?.[behaviorKey] ?? - "prompt") as Behavior; - switch (behavior) { - case "autoYes": - relinka("info-verbose", `Auto-answering YES to: ${title}`); - return true; - case "autoNo": - relinka("info-verbose", `Auto-answering NO to: ${title}`); - return false; - default: - return await confirmPrompt({ title, defaultValue }); + try { + const behavior = config?.experimental?.[behaviorKey] ?? "prompt"; + + switch (behavior) { + case "autoYes": + relinka("info-verbose", `Auto-answering YES to: ${title}`); + return true; + case "autoNo": + relinka("info-verbose", `Auto-answering NO to: ${title}`); + return false; + case "prompt": + return await confirmPrompt({ + title, + defaultValue, + content: "Press Enter to confirm or Esc to cancel", + }); + default: + relinka("warn", `Unknown behavior '${behavior}', defaulting to prompt`); + return await confirmPrompt({ title, defaultValue }); + } + } catch (error) { + relinka( + "error", + "Failed to get decision:", + error instanceof Error ? error.message : String(error), + ); + return defaultValue; } } +/** + * Handles the git initialization step + */ +async function handleGitInit(targetDir: string): Promise { + const gitInitialized = await initGit(targetDir); + if (!gitInitialized) { + relinka( + "error", + "Failed to initialize git. Stopping git and deploy process.", + ); + return false; + } + return true; +} + +/** + * Handles the GitHub repository creation step + */ +async function handleGithubRepo( + projectName: string, + targetDir: string, +): Promise { + const repoCreated = await createGithubRepository(projectName, targetDir); + if (!repoCreated) { + relinka( + "error", + "Failed to create GitHub repository. Stopping deploy process.", + ); + return false; + } + return true; +} + +/** + * Main function to handle git initialization, GitHub repo creation, and deployment + */ export async function promptGitDeploy( projectName: string, config: ReliverseConfig, targetDir: string, -): Promise { +): Promise { try { - // 1. First ask about git initialization + // 1. Git initialization const shouldInitGit = await decide( config, "gitBehavior", - "Do you want to initialize git?", + "Do you want to initialize git in your project? (This will allow you to push your project to e.g. GitHub and deploy it to e.g. Vercel)", ); if (!shouldInitGit) { relinka("info", "Skipping git initialization."); - return; + return "none"; } - // Initialize git locally without remote - const gitInitialized = await initGit(targetDir); - if (!gitInitialized) { - relinka( - "error", - "Failed to initialize git. Stopping git and deploy process.", - ); - return; - } + if (!(await handleGitInit(targetDir))) return "none"; - // 2. Then ask about GitHub repository + // 2. GitHub repository const shouldCreateRepo = await decide( config, "gitBehavior", - "Do you want to create a GitHub repository and push the initial commit?", + "Do you want to create a GitHub repository and push your code?", ); if (!shouldCreateRepo) { relinka("info", "Skipping GitHub repository creation."); - return; + return "none"; } - const repoCreated = await createRepo(projectName, targetDir, config); - if (!repoCreated) { - relinka( - "error", - "Failed to create GitHub repository. Stopping deploy process.", - ); - return; - } + if (!(await handleGithubRepo(projectName, targetDir))) return "none"; - // 3. Finally ask about deployment + // 3. Deployment const shouldDeployProject = await decide( config, "deployBehavior", - "Do you want to deploy this project to Vercel?", + "Do you want to deploy this project?", ); if (!shouldDeployProject) { relinka("info", "Skipping project deployment."); - return; + return "none"; } - const deployed = await deployProject(projectName, config, targetDir); - - if (!deployed) { - relinka("error", "Failed to deploy project."); - return; - } + if ((await deployProject(projectName, config, targetDir)) === "none") + return "none"; relinka( "success", - "Git initialization, GitHub setup, and deployment completed successfully!", + "Git initialization, GitHub setup, and deployment completed successfully! šŸŽ‰", ); + + return "vercel"; } catch (error) { - relinka( - "error", - "An unexpected error occurred:", - error instanceof Error ? error.message : String(error), - ); + if (error instanceof Error) { + relinka("error", `Deployment process failed: ${error.message}`); + if (error.stack) { + relinka("error-verbose", "Stack trace:", error.stack); + } + } else { + relinka("error", "An unexpected error occurred:", String(error)); + } } + + return "vercel"; } diff --git a/src/app/menu/show-composer-mode/helpers/createProject.ts b/src/app/menu/show-composer-mode/helpers/createProject.ts index 9fb5a6d..bac5913 100644 --- a/src/app/menu/show-composer-mode/helpers/createProject.ts +++ b/src/app/menu/show-composer-mode/helpers/createProject.ts @@ -1,7 +1,8 @@ import fs from "fs-extra"; +import { globby } from "globby"; import path from "pathe"; -import { PKG_ROOT } from "~/consts.js"; +import { PKG_ROOT } from "~/app/db/constants.js"; import { selectAppFile, selectIndexFile, @@ -10,13 +11,10 @@ import { } from "~/app/menu/show-composer-mode/helpers/selectBoilerplate.js"; import { getUserPkgManager } from "~/app/menu/show-composer-mode/utils/getUserPkgManager.js"; -import type { - DatabaseProvider, - PkgInstallerMap, -} from "../../installers/index.js"; +import type { DatabaseProvider, PkgInstallerMap } from "../opts.js"; import { installPackages } from "./installPackages.js"; -import { scaffoldProject } from "./handlers/scaffoldProject.js"; +import { scaffoldProject } from "./scaffoldProject.js"; type CreateProjectOptions = { projectName: string; @@ -28,6 +26,26 @@ type CreateProjectOptions = { databaseProvider: DatabaseProvider; }; +/** + * Renames all .tsx files to -tsx.txt in the specified directory and its subdirectories. + * @param dir - The directory to process. + */ +async function renameTsxFiles(dir: string): Promise { + try { + const files = await globby("**/*.tsx", { + cwd: dir, + absolute: true, + }); + + for (const filePath of files) { + const newPath = filePath.replace(/\.tsx$/, "-tsx.txt"); + await fs.rename(filePath, newPath); + } + } catch (error) { + console.error("Error renaming .tsx files:", error); + } +} + export const createProject = async ({ projectName, scopedAppName, @@ -51,7 +69,7 @@ export const createProject = async ({ }); // Install the selected packages - installPackages({ + await installPackages({ projectName, scopedAppName, projectDir, @@ -92,5 +110,8 @@ export const createProject = async ({ fs.copyFileSync(indexModuleCss, indexModuleCssDest); } + // Rename all .tsx files to -tsx.txt + await renameTsxFiles(projectDir); + return projectDir; }; diff --git a/src/app/menu/show-composer-mode/helpers/installPackages.ts b/src/app/menu/show-composer-mode/helpers/installPackages.ts index a58552e..6142af6 100644 --- a/src/app/menu/show-composer-mode/helpers/installPackages.ts +++ b/src/app/menu/show-composer-mode/helpers/installPackages.ts @@ -1,25 +1,24 @@ +import { execa } from "execa"; import ora from "ora"; import pc from "picocolors"; import { relinka } from "~/utils/console.js"; -import { - type InstallerOptions, - type PkgInstallerMap, -} from "../../installers/index.js"; +import type { PkgInstallerMap } from "../opts.js"; +import type { InstallerOptions } from "../opts.js"; type InstallPackagesOptions = InstallerOptions & { packages: PkgInstallerMap; }; // This runs the installer for all the packages that the user has selected -export const installPackages = (options: InstallPackagesOptions) => { +export const installPackages = async (options: InstallPackagesOptions) => { const { packages } = options; relinka("info", "Adding boilerplate..."); for (const [name, pkgOpts] of Object.entries(packages)) { if (pkgOpts.inUse) { const spinner = ora(`Boilerplating ${name}...`).start(); - pkgOpts.installer(options); + await pkgOpts.installer(options); spinner.succeed( pc.green(`Successfully setup boilerplate for ${pc.bold(name)}`), ); @@ -28,3 +27,20 @@ export const installPackages = (options: InstallPackagesOptions) => { relinka("info", ""); }; + +const trpcDependencies = { + "@tanstack/react-query": "^5.17.19", + "@trpc/client": "^11.0.0", + "@trpc/next": "^11.0.0", + "@trpc/react-query": "^11.0.0", + "@trpc/server": "^11.0.0", + superjson: "^2.2.1", +}; + +export async function installTRPCDependencies(projectDir: string) { + const deps = Object.entries(trpcDependencies) + .map(([pkg, version]) => `${pkg}@${version}`) + .join(" "); + + await execa(`npm install ${deps}`, { cwd: projectDir }); +} diff --git a/src/app/menu/show-composer-mode/helpers/logNextSteps.ts b/src/app/menu/show-composer-mode/helpers/logNextSteps.ts index 084eab5..5fa58c6 100644 --- a/src/app/menu/show-composer-mode/helpers/logNextSteps.ts +++ b/src/app/menu/show-composer-mode/helpers/logNextSteps.ts @@ -1,9 +1,8 @@ -import { DEFAULT_APP_NAME } from "~/consts.js"; -import { type InstallerOptions } from "~/installers/index.js"; +import { DEFAULT_APP_NAME } from "~/app/db/constants.js"; import { relinka } from "~/utils/console.js"; -import { getUserPkgManager } from "~/utils/getUserPkgManager.js"; -import { logger } from "~/utils/logger.js"; +import { type InstallerOptions } from "../opts.js"; +import { getUserPkgManager } from "../utils/getUserPkgManager.js"; import { isInsideGitRepo, isRootGitRepo } from "./git.js"; // This logs the next steps that the user should take in order to advance the project @@ -66,7 +65,7 @@ export const logNextSteps = async ({ } if (!(await isInsideGitRepo(projectDir)) && !isRootGitRepo(projectDir)) { - relinka("info", ` git init`); + relinka("info", " git init"); } - relinka("info", ` git commit -m "initial commit"`); + relinka("info", " git commit -m 'initial commit'"); }; diff --git a/src/app/menu/show-composer-mode/helpers/scaffoldProject.ts b/src/app/menu/show-composer-mode/helpers/scaffoldProject.ts index 8d40b25..8661015 100644 --- a/src/app/menu/show-composer-mode/helpers/scaffoldProject.ts +++ b/src/app/menu/show-composer-mode/helpers/scaffoldProject.ts @@ -1,5 +1,6 @@ import * as p from "@clack/prompts"; import fs from "fs-extra"; +import { globby } from "globby"; import ora from "ora"; import path from "pathe"; import pc from "picocolors"; @@ -9,6 +10,30 @@ import { relinka } from "~/utils/console.js"; import type { InstallerOptions } from "../opts.js"; +/** + * Renames all -tsx.txt files back to .tsx in the specified directory and its subdirectories. + * @param dir - The directory to process. + */ +async function renameTsxFiles(dir: string): Promise { + try { + const files = await globby("**/*-tsx.txt", { + cwd: dir, + absolute: true, + }); + + for (const filePath of files) { + const newPath = filePath.replace(/-tsx\.txt$/, ".tsx"); + await fs.rename(filePath, newPath); + } + } catch (error) { + relinka( + "error", + "Error renaming -tsx.txt files:", + error instanceof Error ? error.message : String(error), + ); + } +} + // This bootstraps the base Next.js application export const scaffoldProject = async ({ projectName, @@ -91,6 +116,9 @@ export const scaffoldProject = async ({ path.join(projectDir, ".gitignore"), ); + // Convert any -tsx.txt files back to .tsx + await renameTsxFiles(projectDir); + const scaffoldedName = projectName === "." ? "App" : pc.bold(pc.cyan(projectName)); diff --git a/src/app/menu/show-composer-mode/helpers/selectBoilerplate.ts b/src/app/menu/show-composer-mode/helpers/selectBoilerplate.ts index bd032ac..3812292 100644 --- a/src/app/menu/show-composer-mode/helpers/selectBoilerplate.ts +++ b/src/app/menu/show-composer-mode/helpers/selectBoilerplate.ts @@ -1,8 +1,9 @@ -import path from "pathe"; import fs from "fs-extra"; +import path from "pathe"; + +import { PKG_ROOT } from "~/app/db/constants.js"; -import { PKG_ROOT } from "~/consts.js"; -import { type InstallerOptions } from "~/installers/index.js"; +import { type InstallerOptions } from "../opts.js"; type SelectBoilerplateProps = Required< Pick diff --git a/src/app/menu/show-composer-mode/helpers/setImportAlias.ts b/src/app/menu/show-composer-mode/helpers/setImportAlias.ts index 7404f17..02ae95c 100644 --- a/src/app/menu/show-composer-mode/helpers/setImportAlias.ts +++ b/src/app/menu/show-composer-mode/helpers/setImportAlias.ts @@ -23,8 +23,9 @@ function replaceTextInFiles( export const setImportAlias = (projectDir: string, importAlias: string) => { const normalizedImportAlias = importAlias .replace(/\*/g, "") // remove any wildcards (~/* -> ~/) + // eslint-disable-next-line no-useless-escape .replace(/[^\/]$/, "$&/"); // ensure trailing slash (@ -> ~/) // update import alias in any files if not using the default - replaceTextInFiles(projectDir, `~/`, normalizedImportAlias); + replaceTextInFiles(projectDir, "~/", normalizedImportAlias); }; diff --git a/src/app/menu/show-composer-mode/installers/dbContainer.ts b/src/app/menu/show-composer-mode/installers/dbContainer.ts index 8d1df35..8cd2dfe 100644 --- a/src/app/menu/show-composer-mode/installers/dbContainer.ts +++ b/src/app/menu/show-composer-mode/installers/dbContainer.ts @@ -1,9 +1,10 @@ import fs from "fs-extra"; import path from "pathe"; -import { PKG_ROOT } from "~/consts.js"; -import { type Installer } from "~/installers/index.js"; -import { parseNameAndPath } from "~/utils/parseNameAndPath.js"; +import { PKG_ROOT } from "~/app/db/constants.js"; + +import { type Installer } from "../opts.js"; +import { parseNameAndPath } from "../utils/parseNameAndPath.js"; // Sanitizes a project name to ensure it adheres to Docker container naming conventions. const sanitizeName = (name: string): string => { diff --git a/src/app/menu/show-composer-mode/installers/drizzle.ts b/src/app/menu/show-composer-mode/installers/drizzle.ts index 2c9cfb8..378b335 100644 --- a/src/app/menu/show-composer-mode/installers/drizzle.ts +++ b/src/app/menu/show-composer-mode/installers/drizzle.ts @@ -1,10 +1,12 @@ -import path from "pathe"; +import type { PackageJson } from "type-fest"; + import fs from "fs-extra"; -import { type PackageJson } from "type-fest"; +import path from "pathe"; + +import { PKG_ROOT } from "~/app/db/constants.js"; -import { PKG_ROOT } from "~/consts.js"; -import { type Installer } from "~/installers/index.js"; -import { addPackageDependency } from "~/utils/addPackageDependency.js"; +import { type Installer } from "../opts.js"; +import { addPackageDependency } from "../utils/addPackageDependency.js"; import { type AvailableDependencies } from "./dependencyVersionMap.js"; export const drizzleInstaller: Installer = ({ diff --git a/src/app/menu/show-composer-mode/installers/envVars.ts b/src/app/menu/show-composer-mode/installers/envVars.ts index a90047f..758a542 100644 --- a/src/app/menu/show-composer-mode/installers/envVars.ts +++ b/src/app/menu/show-composer-mode/installers/envVars.ts @@ -2,8 +2,9 @@ import fs from "fs-extra"; import crypto from "node:crypto"; import path from "pathe"; -import { PKG_ROOT } from "~/consts.js"; -import { type DatabaseProvider, type Installer } from "~/installers/index.js"; +import { PKG_ROOT } from "~/app/db/constants.js"; + +import { type DatabaseProvider, type Installer } from "../opts.js"; export const envVariablesInstaller: Installer = ({ projectDir, @@ -15,7 +16,7 @@ export const envVariablesInstaller: Installer = ({ const usingPrisma = packages?.prisma.inUse; const usingDrizzle = packages?.drizzle.inUse; - const usingDb = usingPrisma || usingDrizzle; + const usingDb = usingPrisma ?? usingDrizzle; const usingPlanetScale = databaseProvider === "planetscale"; const envContent = getEnvContent( diff --git a/src/app/menu/show-composer-mode/installers/eslint.ts b/src/app/menu/show-composer-mode/installers/eslint.ts deleted file mode 100644 index b3522c1..0000000 --- a/src/app/menu/show-composer-mode/installers/eslint.ts +++ /dev/null @@ -1,42 +0,0 @@ -import path from "pathe"; -import fs from "fs-extra"; - -import { _initialConfig } from "~/../template/extras/config/_eslint.js"; -import { type Installer } from "~/installers/index.js"; - -export const dynamicEslintInstaller: Installer = ({ projectDir, packages }) => { - const usingDrizzle = !!packages?.drizzle?.inUse; - - const eslintConfig = getEslintConfig({ usingDrizzle }); - - // Convert config from _eslint.config.json to .eslintrc.cjs - const eslintrcFileContents = [ - '/** @type {import("eslint").Linter.Config} */', - `const config = ${JSON.stringify(eslintConfig, null, 2)}`, - "module.exports = config;", - ].join("\n"); - - const eslintConfigDest = path.join(projectDir, ".eslintrc.cjs"); - fs.writeFileSync(eslintConfigDest, eslintrcFileContents, "utf-8"); -}; - -const getEslintConfig = ({ usingDrizzle }: { usingDrizzle: boolean }) => { - const eslintConfig = _initialConfig; - - if (usingDrizzle) { - eslintConfig.plugins = [...(eslintConfig.plugins ?? []), "drizzle"]; - - eslintConfig.rules = { - ...eslintConfig.rules, - "drizzle/enforce-delete-with-where": [ - "error", - { drizzleObjectName: ["db", "ctx.db"] }, - ], - "drizzle/enforce-update-with-where": [ - "error", - { drizzleObjectName: ["db", "ctx.db"] }, - ], - }; - } - return eslintConfig; -}; diff --git a/src/app/menu/show-composer-mode/installers/nextAuth.ts b/src/app/menu/show-composer-mode/installers/nextAuth.ts index 37e4ed3..5cfaa11 100644 --- a/src/app/menu/show-composer-mode/installers/nextAuth.ts +++ b/src/app/menu/show-composer-mode/installers/nextAuth.ts @@ -1,10 +1,11 @@ -import path from "pathe"; import fs from "fs-extra"; +import path from "pathe"; + +import { PKG_ROOT } from "~/app/db/constants.js"; -import { PKG_ROOT } from "~/consts.js"; -import { type AvailableDependencies } from "~/installers/dependencyVersionMap.js"; -import { type Installer } from "~/installers/index.js"; -import { addPackageDependency } from "~/utils/addPackageDependency.js"; +import { type Installer } from "../opts.js"; +import { addPackageDependency } from "../utils/addPackageDependency.js"; +import { type AvailableDependencies } from "./dependencyVersionMap.js"; export const nextAuthInstaller: Installer = ({ projectDir, packages }) => { const usingPrisma = packages?.prisma.inUse; diff --git a/src/app/menu/show-composer-mode/installers/prisma.ts b/src/app/menu/show-composer-mode/installers/prisma.ts index c5d1ba0..7e6970e 100644 --- a/src/app/menu/show-composer-mode/installers/prisma.ts +++ b/src/app/menu/show-composer-mode/installers/prisma.ts @@ -1,10 +1,12 @@ -import path from "pathe"; +import type { PackageJson } from "type-fest"; + import fs from "fs-extra"; -import { type PackageJson } from "type-fest"; +import path from "pathe"; + +import { PKG_ROOT } from "~/app/db/constants.js"; -import { PKG_ROOT } from "~/consts.js"; -import { type Installer } from "~/installers/index.js"; -import { addPackageDependency } from "~/utils/addPackageDependency.js"; +import { type Installer } from "../opts.js"; +import { addPackageDependency } from "../utils/addPackageDependency.js"; export const prismaInstaller: Installer = ({ projectDir, diff --git a/src/app/menu/show-composer-mode/installers/tailwind.ts b/src/app/menu/show-composer-mode/installers/tailwind.ts index b309f5e..29acf5e 100644 --- a/src/app/menu/show-composer-mode/installers/tailwind.ts +++ b/src/app/menu/show-composer-mode/installers/tailwind.ts @@ -1,10 +1,12 @@ +import type { PackageJson } from "type-fest"; + import fs from "fs-extra"; import path from "pathe"; -import { type PackageJson } from "type-fest"; import { PKG_ROOT } from "~/app/db/constants.js"; -import { type Installer } from "~/app/menu/show-composer-mode/helpers/installers/index.js"; -import { addPackageDependency } from "~/app/menu/show-composer-mode/utils/addPackageDependency.js"; + +import { type Installer } from "../opts.js"; +import { addPackageDependency } from "../utils/addPackageDependency.js"; export const tailwindInstaller: Installer = ({ projectDir }) => { addPackageDependency({ diff --git a/src/app/menu/show-composer-mode/installers/trpc.ts b/src/app/menu/show-composer-mode/installers/trpc.ts index d14cea8..e6335b0 100644 --- a/src/app/menu/show-composer-mode/installers/trpc.ts +++ b/src/app/menu/show-composer-mode/installers/trpc.ts @@ -1,9 +1,10 @@ -import path from "pathe"; import fs from "fs-extra"; +import path from "pathe"; + +import { PKG_ROOT } from "~/app/db/constants.js"; -import { PKG_ROOT } from "~/consts.js"; -import { type Installer } from "~/installers/index.js"; -import { addPackageDependency } from "~/utils/addPackageDependency.js"; +import { type Installer } from "../opts.js"; +import { addPackageDependency } from "../utils/addPackageDependency.js"; export const trpcInstaller: Installer = ({ projectDir, @@ -25,7 +26,7 @@ export const trpcInstaller: Installer = ({ const usingAuth = packages?.nextAuth.inUse; const usingPrisma = packages?.prisma.inUse; const usingDrizzle = packages?.drizzle.inUse; - const usingDb = usingPrisma || usingDrizzle; + const usingDb = usingPrisma ?? usingDrizzle; const extrasDir = path.join(PKG_ROOT, "template/extras"); diff --git a/src/app/menu/show-composer-mode/mod.ts b/src/app/menu/show-composer-mode/mod.ts index e2824ca..9f73117 100644 --- a/src/app/menu/show-composer-mode/mod.ts +++ b/src/app/menu/show-composer-mode/mod.ts @@ -20,7 +20,7 @@ import { export async function showComposerMode(cliResults: CliResults) { const npmVersion = await getNpmVersion(); const pkgManager = getUserPkgManager(); - renderTitle(); + await renderTitle(); if (npmVersion) { renderVersionWarning(npmVersion); } diff --git a/src/app/menu/show-composer-mode/opts.ts b/src/app/menu/show-composer-mode/opts.ts index 498e3ec..a5f8fd4 100644 --- a/src/app/menu/show-composer-mode/opts.ts +++ b/src/app/menu/show-composer-mode/opts.ts @@ -1,9 +1,11 @@ +import path from "pathe"; + import { DEFAULT_APP_NAME } from "~/app/db/constants.js"; +import { configureEslint } from "~/utils/eslint.js"; import { dbContainerInstaller } from "./installers/dbContainer.js"; import { drizzleInstaller } from "./installers/drizzle.js"; import { envVariablesInstaller } from "./installers/envVars.js"; -import { dynamicEslintInstaller } from "./installers/eslint.js"; import { nextAuthInstaller } from "./installers/nextAuth.js"; import { prismaInstaller } from "./installers/prisma.js"; import { tailwindInstaller } from "./installers/tailwind.js"; @@ -43,7 +45,7 @@ export type InstallerOptions = { databaseProvider: DatabaseProvider; }; -export type Installer = (opts: InstallerOptions) => void; +export type Installer = (opts: InstallerOptions) => Promise | void; export type PkgInstallerMap = Record< AvailablePackages, @@ -87,7 +89,19 @@ export const buildPkgInstallerMap = ( }, eslint: { inUse: true, - installer: dynamicEslintInstaller, + installer: async (opts) => { + await configureEslint({ + eslintConfig: path.join(opts.projectDir, "eslint.config.js"), + eslintRulesDisabledConfig: path.join( + opts.projectDir, + "eslint.config.disabled.js", + ), + eslintUltimateConfig: path.join( + opts.projectDir, + "eslint.config.ultimate.js", + ), + }); + }, }, }); diff --git a/src/app/menu/show-composer-mode/template/base/package.json b/src/app/menu/show-composer-mode/template/base/pkg.json similarity index 100% rename from src/app/menu/show-composer-mode/template/base/package.json rename to src/app/menu/show-composer-mode/template/base/pkg.json diff --git a/src/app/menu/show-composer-mode/template/base/src/env.js b/src/app/menu/show-composer-mode/template/base/src/env.js index 5c2f937..b9e2ba4 100644 --- a/src/app/menu/show-composer-mode/template/base/src/env.js +++ b/src/app/menu/show-composer-mode/template/base/src/env.js @@ -1,3 +1,4 @@ +/* eslint-disable no-undef */ import { createEnv } from "@t3-oss/env-nextjs"; import { z } from "zod"; diff --git a/src/app/menu/show-composer-mode/template/base/src/styles/globals.css b/src/app/menu/show-composer-mode/template/base/src/styles/globals.css index e5e2dcc..4fe5f5b 100644 --- a/src/app/menu/show-composer-mode/template/base/src/styles/globals.css +++ b/src/app/menu/show-composer-mode/template/base/src/styles/globals.css @@ -2,7 +2,9 @@ html, body { padding: 0; margin: 0; - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + font-family: + -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + /* biome-ignore lint/suspicious/noDuplicateFontNames: */ Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; } diff --git a/src/app/menu/show-composer-mode/template/extras/.env b/src/app/menu/show-composer-mode/template/extras/.env new file mode 100644 index 0000000..c7400d6 --- /dev/null +++ b/src/app/menu/show-composer-mode/template/extras/.env @@ -0,0 +1,2 @@ +VITE_API_URL=http://localhost:3000/api +VITE_AUTH_URL=http://localhost:3000/auth \ No newline at end of file diff --git a/src/app/menu/show-composer-mode/template/extras/config/drizzle-config-mysql.ts b/src/app/menu/show-composer-mode/template/extras/config/drizzle-config-mysql.ts index 1f71d75..5469c04 100644 --- a/src/app/menu/show-composer-mode/template/extras/config/drizzle-config-mysql.ts +++ b/src/app/menu/show-composer-mode/template/extras/config/drizzle-config-mysql.ts @@ -1,5 +1,6 @@ import { type Config } from "drizzle-kit"; +// @ts-expect-error TODO: fix ts import { env } from "~/env"; export default { diff --git a/src/app/menu/show-composer-mode/template/extras/config/drizzle-config-postgres.ts b/src/app/menu/show-composer-mode/template/extras/config/drizzle-config-postgres.ts index d2a21ed..6dae689 100644 --- a/src/app/menu/show-composer-mode/template/extras/config/drizzle-config-postgres.ts +++ b/src/app/menu/show-composer-mode/template/extras/config/drizzle-config-postgres.ts @@ -1,5 +1,6 @@ import { type Config } from "drizzle-kit"; +// @ts-expect-error TODO: fix ts import { env } from "~/env"; export default { diff --git a/src/app/menu/show-composer-mode/template/extras/config/drizzle-config-sqlite.ts b/src/app/menu/show-composer-mode/template/extras/config/drizzle-config-sqlite.ts index 34f8fa2..ff55828 100644 --- a/src/app/menu/show-composer-mode/template/extras/config/drizzle-config-sqlite.ts +++ b/src/app/menu/show-composer-mode/template/extras/config/drizzle-config-sqlite.ts @@ -1,5 +1,6 @@ import { type Config } from "drizzle-kit"; +// @ts-expect-error TODO: fix ts import { env } from "~/env"; export default { diff --git a/src/app/menu/show-composer-mode/template/extras/config/react-router.config.ts b/src/app/menu/show-composer-mode/template/extras/config/react-router.config.ts new file mode 100644 index 0000000..3ff1b9f --- /dev/null +++ b/src/app/menu/show-composer-mode/template/extras/config/react-router.config.ts @@ -0,0 +1,6 @@ +import type { Config } from "@react-router/dev/config"; + +export default { + appDirectory: "src", + ssr: false, +} satisfies Config; diff --git a/src/app/menu/show-composer-mode/template/extras/config/tailwind.config.ts b/src/app/menu/show-composer-mode/template/extras/config/tailwind.config.ts index 5fd44e8..2cee2fd 100644 --- a/src/app/menu/show-composer-mode/template/extras/config/tailwind.config.ts +++ b/src/app/menu/show-composer-mode/template/extras/config/tailwind.config.ts @@ -1,12 +1,11 @@ import { type Config } from "tailwindcss"; -import { fontFamily } from "tailwindcss/defaultTheme"; export default { content: ["./src/**/*.tsx"], theme: { extend: { fontFamily: { - sans: ["var(--font-geist-sans)", ...fontFamily.sans], + sans: ["var(--font-geist-sans)"], }, }, }, diff --git a/src/app/menu/show-composer-mode/template/extras/config/vite.config.ts b/src/app/menu/show-composer-mode/template/extras/config/vite.config.ts index 5bc6c19..65784e4 100644 --- a/src/app/menu/show-composer-mode/template/extras/config/vite.config.ts +++ b/src/app/menu/show-composer-mode/template/extras/config/vite.config.ts @@ -4,5 +4,17 @@ import Pages from "vite-plugin-pages"; // https://vite.dev/config export default defineConfig({ - plugins: [react(), Pages()], + plugins: [ + react(), + Pages({ + dirs: "src/pages", + extensions: ["tsx", "ts"], + exclude: ["**/components/**/*", "**/api/**/*"], + }), + ], + resolve: { + alias: { + "~": "/src", + }, + }, }); diff --git a/src/app/menu/show-composer-mode/template/extras/eslint.ts b/src/app/menu/show-composer-mode/template/extras/eslint.ts new file mode 100644 index 0000000..d299245 --- /dev/null +++ b/src/app/menu/show-composer-mode/template/extras/eslint.ts @@ -0,0 +1,34 @@ +// @ts-check + +import eslint from "@eslint/js"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import tseslint from "typescript-eslint"; + +const config = tseslint.config( + { + ignores: ["**/.git/", "**/node_modules/"], + }, + eslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + ...tseslint.configs.stylisticTypeChecked, + { + files: ["**/*.{js,jsx,md,json}"], + ...tseslint.configs.disableTypeChecked, + }, + { + files: ["**/*.{js,jsx,ts,tsx}"], + languageOptions: { + parserOptions: { + projectService: true, + warnOnUnsupportedTypeScriptVersion: false, + tsconfigRootDir: path.dirname(fileURLToPath(import.meta.url)), + }, + }, + rules: { + "max-lines": ["error", 700], + }, + }, +); + +export default config; diff --git a/src/app/menu/show-composer-mode/template/extras/index.html b/src/app/menu/show-composer-mode/template/extras/index.html new file mode 100644 index 0000000..1e1d279 --- /dev/null +++ b/src/app/menu/show-composer-mode/template/extras/index.html @@ -0,0 +1,16 @@ + + + + + + + + @reliverse/cli + + + +
+ + + + \ No newline at end of file diff --git a/src/app/menu/show-composer-mode/template/extras/pkg.json b/src/app/menu/show-composer-mode/template/extras/pkg.json new file mode 100644 index 0000000..c5dff4e --- /dev/null +++ b/src/app/menu/show-composer-mode/template/extras/pkg.json @@ -0,0 +1,35 @@ +{ + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0" + }, + "dependencies": { + "@tanstack/react-query": "^4.36.1", + "@trpc/client": "^10.43.6", + "@trpc/react-query": "^10.43.6", + "@trpc/server": "^10.43.6", + "@vitejs/plugin-react": "^4.2.0", + "next-auth": "^4.24.5", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-router-dom": "^6.20.0", + "superjson": "^2.2.1", + "vite": "^5.0.0", + "vite-plugin-pages": "^0.31.0", + "@geist-ui/core": "^2.3.8", + "zod": "^3.22.4" + }, + "devDependencies": { + "@types/react": "^18.2.37", + "@types/react-dom": "^18.2.15", + "@typescript-eslint/eslint-plugin": "^6.10.0", + "@typescript-eslint/parser": "^6.10.0", + "eslint": "^8.53.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.4", + "typescript": "^5.2.2" + } +} diff --git a/src/app/menu/show-composer-mode/template/extras/src/app/_components/post-tw.tsx b/src/app/menu/show-composer-mode/template/extras/src/app/_components/post-tw.tsx index ebe15ea..522bd0e 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/app/_components/post-tw.tsx +++ b/src/app/menu/show-composer-mode/template/extras/src/app/_components/post-tw.tsx @@ -2,7 +2,7 @@ import { useState } from "react"; -import { api } from "~/trpc/react"; +import { api } from "../../utils/api.js"; export function LatestPost() { const [latestPost] = api.post.getLatest.useSuspenseQuery(); @@ -34,7 +34,9 @@ export function LatestPost() { type="text" placeholder="Title" value={name} - onChange={(e) => setName(e.target.value)} + onChange={(e) => { + setName(e.target.value); + }} className="w-full rounded-full px-4 py-2 text-black" /> + + ); +} diff --git a/src/app/menu/show-composer-mode/template/extras/src/pages/with-auth-trpc.tsx b/src/app/menu/show-composer-mode/template/extras/src/pages/with-auth-trpc.tsx new file mode 100644 index 0000000..8c9bb4a --- /dev/null +++ b/src/app/menu/show-composer-mode/template/extras/src/pages/with-auth-trpc.tsx @@ -0,0 +1,77 @@ +import { signIn, signOut, useSession } from "next-auth/react"; +import React from "react"; +import { Link } from "react-router-dom"; + +// @ts-expect-error TODO: fix ts +import { api } from "~/utils/api"; + +import styles from "./index.module.css"; + +export default function WithAuthTrpcPage() { + const hello = api.post.hello.useQuery({ text: "from tRPC" }); + + return ( +
+
+

+ Basic Reliverse App +

+
+ +

First Steps ā†’

+
+ Just the basics - Everything you need to know to set up your + database and authentication. +
+ + +

Documentation ā†’

+
+ Learn more about @reliverse/cli, the libraries it uses, and how to + deploy it. +
+ +
+
+

+ {hello.data ? hello.data.greeting : "Loading tRPC query..."} +

+ +
+
+
+ ); +} + +function AuthShowcase() { + const { data: sessionData } = useSession(); + + const { data: secretMessage } = api.post.getSecretMessage.useQuery( + undefined, + { enabled: sessionData?.user !== undefined }, + ); + + return ( +
+

+ {sessionData && Logged in as {sessionData.user?.name}} + {secretMessage && - {secretMessage}} +

+ +
+ ); +} diff --git a/src/app/menu/show-composer-mode/template/extras/src/pages/with-tw.tsx b/src/app/menu/show-composer-mode/template/extras/src/pages/with-tw.tsx new file mode 100644 index 0000000..562a9ff --- /dev/null +++ b/src/app/menu/show-composer-mode/template/extras/src/pages/with-tw.tsx @@ -0,0 +1,38 @@ +import React from "react"; +import { Link } from "react-router-dom"; + +export default function WithTailwindPage() { + return ( +
+
+

+ Basic Reliverse App +

+
+ +

First Steps ā†’

+
+ Just the basics - Everything you need to know to set up your + database and authentication. +
+ + +

Documentation ā†’

+
+ Learn more about @reliverse/cli, the libraries it uses, and how to + deploy it. +
+ +
+
+
+ ); +} diff --git a/src/app/menu/show-composer-mode/template/extras/src/providers/auth.tsx b/src/app/menu/show-composer-mode/template/extras/src/providers/auth.tsx new file mode 100644 index 0000000..4b777a8 --- /dev/null +++ b/src/app/menu/show-composer-mode/template/extras/src/providers/auth.tsx @@ -0,0 +1,10 @@ +import { SessionProvider } from "next-auth/react"; +import { type ReactNode } from "react"; + +type AuthProviderProps = { + children: ReactNode; +}; + +export function AuthProvider({ children }: AuthProviderProps) { + return {children}; +} diff --git a/src/app/menu/show-composer-mode/template/extras/src/root.tsx b/src/app/menu/show-composer-mode/template/extras/src/root.tsx new file mode 100644 index 0000000..9129776 --- /dev/null +++ b/src/app/menu/show-composer-mode/template/extras/src/root.tsx @@ -0,0 +1,26 @@ +import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router"; + +import "./styles/globals.css"; + +export function Layout() { + return ( + + + + + @reliverse/cli + + + + + + + + + + ); +} + +export default function Root() { + return ; +} diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/api/root.ts b/src/app/menu/show-composer-mode/template/extras/src/server/api/root.ts index f01ab74..1f17fdc 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/api/root.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/api/root.ts @@ -1,23 +1,8 @@ -import { postRouter } from "~/server/api/routers/post"; -import { createCallerFactory, createTRPCRouter } from "~/server/api/trpc"; +import { postRouter } from "./routers/post.js"; +import { createTRPCRouter } from "./trpc.js"; -/** - * This is the primary router for your server. - * - * All routers added in /api/routers should be manually added here. - */ -export const framework = createTRPCRouter({ +export const appRouter = createTRPCRouter({ post: postRouter, }); -// export type definition of API -export type framework = typeof framework; - -/** - * Create a server-side caller for the tRPC API. - * @example - * const trpc = createCaller(createContext); - * const res = await trpc.post.all(); - * ^? Post[] - */ -export const createCaller = createCallerFactory(framework); +export type AppRouter = typeof appRouter; diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post.ts b/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post.ts new file mode 100644 index 0000000..ea02ef7 --- /dev/null +++ b/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post.ts @@ -0,0 +1,31 @@ +import { z } from "zod"; + +import { + createTRPCRouter, + publicProcedure, + protectedProcedure, +} from "../trpc.js"; + +export const postRouter = createTRPCRouter({ + hello: publicProcedure + .input(z.object({ text: z.string() })) + .query(({ input }) => { + return { + greeting: `Hello ${input.text}`, + }; + }), + + getSecretMessage: protectedProcedure.query(() => { + return "you can now see this secret message!"; + }), + + getLatest: publicProcedure.query(() => { + return { name: "Example Post" }; + }), + + create: publicProcedure + .input(z.object({ name: z.string() })) + .mutation(({ input }) => { + return { name: input.name }; + }), +}); diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post/base.ts b/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post/base.ts index afe46d8..d16a4ad 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post/base.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post/base.ts @@ -1,12 +1,13 @@ import { z } from "zod"; +// @ts-expect-error TODO: fix ts import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; // Mocked DB -interface Post { +type Post = { id: number; name: string; -} +}; const posts: Post[] = [ { id: 1, @@ -17,6 +18,7 @@ const posts: Post[] = [ export const postRouter = createTRPCRouter({ hello: publicProcedure .input(z.object({ text: z.string() })) + // @ts-expect-error TODO: fix ts .query(({ input }) => { return { greeting: `Hello ${input.text}`, @@ -25,7 +27,8 @@ export const postRouter = createTRPCRouter({ create: publicProcedure .input(z.object({ name: z.string().min(1) })) - .mutation(async ({ input }) => { + // @ts-expect-error TODO: fix ts + .mutation(({ input }) => { const post: Post = { id: posts.length + 1, name: input.name, diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post/with-auth-drizzle.ts b/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post/with-auth-drizzle.ts index b8954c8..aafbed3 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post/with-auth-drizzle.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post/with-auth-drizzle.ts @@ -4,12 +4,15 @@ import { createTRPCRouter, protectedProcedure, publicProcedure, + // @ts-expect-error TODO: fix ts } from "~/server/api/trpc"; +// @ts-expect-error TODO: fix ts import { posts } from "~/server/db/schema"; export const postRouter = createTRPCRouter({ hello: publicProcedure .input(z.object({ text: z.string() })) + // @ts-expect-error TODO: fix ts .query(({ input }) => { return { greeting: `Hello ${input.text}`, @@ -18,6 +21,7 @@ export const postRouter = createTRPCRouter({ create: protectedProcedure .input(z.object({ name: z.string().min(1) })) + // @ts-expect-error TODO: fix ts .mutation(async ({ ctx, input }) => { await ctx.db.insert(posts).values({ name: input.name, @@ -25,8 +29,10 @@ export const postRouter = createTRPCRouter({ }); }), + // @ts-expect-error TODO: fix ts getLatest: protectedProcedure.query(async ({ ctx }) => { const post = await ctx.db.query.posts.findFirst({ + // @ts-expect-error TODO: fix ts orderBy: (posts, { desc }) => [desc(posts.createdAt)], }); diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post/with-auth-prisma.ts b/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post/with-auth-prisma.ts index b602dd5..01a228e 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post/with-auth-prisma.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post/with-auth-prisma.ts @@ -4,11 +4,13 @@ import { createTRPCRouter, protectedProcedure, publicProcedure, + // @ts-expect-error TODO: fix ts } from "~/server/api/trpc"; export const postRouter = createTRPCRouter({ hello: publicProcedure .input(z.object({ text: z.string() })) + // @ts-expect-error TODO: fix ts .query(({ input }) => { return { greeting: `Hello ${input.text}`, @@ -17,7 +19,8 @@ export const postRouter = createTRPCRouter({ create: protectedProcedure .input(z.object({ name: z.string().min(1) })) - .mutation(async ({ ctx, input }) => { + // @ts-expect-error TODO: fix ts + .mutation(({ ctx, input }) => { return ctx.db.post.create({ data: { name: input.name, @@ -26,6 +29,7 @@ export const postRouter = createTRPCRouter({ }); }), + // @ts-expect-error TODO: fix ts getLatest: protectedProcedure.query(async ({ ctx }) => { const post = await ctx.db.post.findFirst({ orderBy: { createdAt: "desc" }, diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post/with-auth.ts b/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post/with-auth.ts index 199b617..78ad861 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post/with-auth.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post/with-auth.ts @@ -4,6 +4,7 @@ import { createTRPCRouter, protectedProcedure, publicProcedure, + // @ts-expect-error TODO: fix ts } from "~/server/api/trpc"; let post = { @@ -14,6 +15,7 @@ let post = { export const postRouter = createTRPCRouter({ hello: publicProcedure .input(z.object({ text: z.string() })) + // @ts-expect-error TODO: fix ts .query(({ input }) => { return { greeting: `Hello ${input.text}`, @@ -22,7 +24,8 @@ export const postRouter = createTRPCRouter({ create: protectedProcedure .input(z.object({ name: z.string().min(1) })) - .mutation(async ({ input }) => { + // @ts-expect-error TODO: fix ts + .mutation(({ input }) => { post = { id: post.id + 1, name: input.name }; return post; }), diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post/with-drizzle.ts b/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post/with-drizzle.ts index 4bbf615..130ed76 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post/with-drizzle.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post/with-drizzle.ts @@ -1,11 +1,14 @@ import { z } from "zod"; +// @ts-expect-error TODO: fix ts import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; +// @ts-expect-error TODO: fix ts import { posts } from "~/server/db/schema"; export const postRouter = createTRPCRouter({ hello: publicProcedure .input(z.object({ text: z.string() })) + // @ts-expect-error TODO: fix ts .query(({ input }) => { return { greeting: `Hello ${input.text}`, @@ -14,14 +17,17 @@ export const postRouter = createTRPCRouter({ create: publicProcedure .input(z.object({ name: z.string().min(1) })) + // @ts-expect-error TODO: fix ts .mutation(async ({ ctx, input }) => { await ctx.db.insert(posts).values({ name: input.name, }); }), + // @ts-expect-error TODO: fix ts getLatest: publicProcedure.query(async ({ ctx }) => { const post = await ctx.db.query.posts.findFirst({ + // @ts-expect-error TODO: fix ts orderBy: (posts, { desc }) => [desc(posts.createdAt)], }); diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post/with-prisma.ts b/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post/with-prisma.ts index da1c799..a4819af 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post/with-prisma.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/api/routers/post/with-prisma.ts @@ -1,10 +1,12 @@ import { z } from "zod"; +// @ts-expect-error TODO: fix ts import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; export const postRouter = createTRPCRouter({ hello: publicProcedure .input(z.object({ text: z.string() })) + // @ts-expect-error TODO: fix ts .query(({ input }) => { return { greeting: `Hello ${input.text}`, @@ -13,7 +15,8 @@ export const postRouter = createTRPCRouter({ create: publicProcedure .input(z.object({ name: z.string().min(1) })) - .mutation(async ({ ctx, input }) => { + // @ts-expect-error TODO: fix ts + .mutation(({ ctx, input }) => { return ctx.db.post.create({ data: { name: input.name, @@ -21,6 +24,7 @@ export const postRouter = createTRPCRouter({ }); }), + // @ts-expect-error TODO: fix ts getLatest: publicProcedure.query(async ({ ctx }) => { const post = await ctx.db.post.findFirst({ orderBy: { createdAt: "desc" }, diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-app/base.ts b/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-app/base.ts index b38d2fa..4e87228 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-app/base.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-app/base.ts @@ -22,7 +22,7 @@ import { ZodError } from "zod"; * * @see https://trpc.io/docs/server/context */ -export const createTRPCContext = async (opts: { headers: Headers }) => { +export const createTRPCContext = (opts: { headers: Headers }) => { return { ...opts, }; diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-app/with-auth-db.ts b/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-app/with-auth-db.ts index 00d924f..ff88fe1 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-app/with-auth-db.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-app/with-auth-db.ts @@ -11,7 +11,9 @@ import { initTRPC, TRPCError } from "@trpc/server"; import superjson from "superjson"; import { ZodError } from "zod"; +// @ts-expect-error TODO: fix ts import { auth } from "~/server/auth"; +// @ts-expect-error TODO: fix ts import { db } from "~/server/db"; /** @@ -121,7 +123,7 @@ export const publicProcedure = t.procedure.use(timingMiddleware); export const protectedProcedure = t.procedure .use(timingMiddleware) .use(({ ctx, next }) => { - if (!ctx.session || !ctx.session.user) { + if (!ctx.session?.user) { throw new TRPCError({ code: "UNAUTHORIZED" }); } return next({ diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-app/with-auth.ts b/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-app/with-auth.ts index 755b973..44fb1be 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-app/with-auth.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-app/with-auth.ts @@ -10,6 +10,7 @@ import { initTRPC, TRPCError } from "@trpc/server"; import superjson from "superjson"; import { ZodError } from "zod"; +// @ts-expect-error TODO: fix ts import { auth } from "~/server/auth"; /** @@ -118,7 +119,7 @@ export const publicProcedure = t.procedure.use(timingMiddleware); export const protectedProcedure = t.procedure .use(timingMiddleware) .use(({ ctx, next }) => { - if (!ctx.session || !ctx.session.user) { + if (!ctx.session?.user) { throw new TRPCError({ code: "UNAUTHORIZED" }); } return next({ diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-app/with-db.ts b/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-app/with-db.ts index 4e24ba4..b8316bc 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-app/with-db.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-app/with-db.ts @@ -10,6 +10,7 @@ import { initTRPC } from "@trpc/server"; import superjson from "superjson"; import { ZodError } from "zod"; +// @ts-expect-error TODO: fix ts import { db } from "~/server/db"; /** @@ -24,7 +25,7 @@ import { db } from "~/server/db"; * * @see https://trpc.io/docs/server/context */ -export const createTRPCContext = async (opts: { headers: Headers }) => { +export const createTRPCContext = (opts: { headers: Headers }) => { return { db, ...opts, diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-pages/with-auth-db.ts b/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-pages/with-auth-db.ts index 2bf20ee..307b779 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-pages/with-auth-db.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-pages/with-auth-db.ts @@ -13,7 +13,9 @@ import { type Session } from "next-auth"; import superjson from "superjson"; import { ZodError } from "zod"; +// @ts-expect-error TODO: fix ts import { auth } from "~/server/auth"; +// @ts-expect-error TODO: fix ts import { db } from "~/server/db"; /** diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-pages/with-auth.ts b/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-pages/with-auth.ts index e85e587..97f7132 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-pages/with-auth.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-pages/with-auth.ts @@ -12,6 +12,7 @@ import { type Session } from "next-auth"; import superjson from "superjson"; import { ZodError } from "zod"; +// @ts-expect-error TODO: fix ts import { auth } from "~/server/auth"; /** diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-pages/with-db.ts b/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-pages/with-db.ts index a2e7015..7bca4e6 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-pages/with-db.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc-pages/with-db.ts @@ -11,6 +11,7 @@ import { type CreateNextContextOptions } from "@trpc/server/adapters/next"; import superjson from "superjson"; import { ZodError } from "zod"; +// @ts-expect-error TODO: fix ts import { db } from "~/server/db"; /** diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc.ts b/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc.ts new file mode 100644 index 0000000..225febc --- /dev/null +++ b/src/app/menu/show-composer-mode/template/extras/src/server/api/trpc.ts @@ -0,0 +1,43 @@ +import { initTRPC, TRPCError } from "@trpc/server"; +import { type Session } from "next-auth"; +import superjson from "superjson"; +import { ZodError } from "zod"; + +type CreateContextOptions = { + session: Session | null; +}; + +export const createTRPCContext = (opts: CreateContextOptions) => { + const { session } = opts; + return { session }; +}; + +const t = initTRPC.context().create({ + transformer: superjson, + errorFormatter({ shape, error }) { + return { + ...shape, + data: { + ...shape.data, + zodError: + error.cause instanceof ZodError ? error.cause.flatten() : null, + }, + }; + }, +}); + +export const createTRPCRouter = t.router; +export const publicProcedure = t.procedure; + +const enforceUserIsAuthed = t.middleware(({ ctx, next }) => { + if (!ctx.session?.user) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + return next({ + ctx: { + session: { ...ctx.session, user: ctx.session.user }, + }, + }); +}); + +export const protectedProcedure = t.procedure.use(enforceUserIsAuthed); diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/auth/config/base.ts b/src/app/menu/show-composer-mode/template/extras/src/server/auth/config/base.ts index c88101a..9a5c095 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/auth/config/base.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/auth/config/base.ts @@ -1,3 +1,4 @@ +// @ts-expect-error TODO: fix ts import { type DefaultSession, type NextAuthConfig } from "next-auth"; import DiscordProvider from "next-auth/providers/discord"; @@ -8,6 +9,7 @@ import DiscordProvider from "next-auth/providers/discord"; * @see https://next-auth.js.org/getting-started/typescript#module-augmentation */ declare module "next-auth" { + // eslint-disable-next-line @typescript-eslint/consistent-type-definitions interface Session extends DefaultSession { user: { id: string; @@ -41,6 +43,7 @@ export const authConfig = { */ ], callbacks: { + // @ts-expect-error TODO: fix ts session: ({ session, token }) => ({ ...session, user: { diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/auth/config/with-drizzle.ts b/src/app/menu/show-composer-mode/template/extras/src/server/auth/config/with-drizzle.ts index 3ef82a2..e720c7e 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/auth/config/with-drizzle.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/auth/config/with-drizzle.ts @@ -1,13 +1,15 @@ import { DrizzleAdapter } from "@auth/drizzle-adapter"; -import { type DefaultSession, type NextAuthConfig } from "next-auth"; +import { type DefaultSession } from "next-auth"; import DiscordProvider from "next-auth/providers/discord"; +// @ts-expect-error TODO: fix ts import { db } from "~/server/db"; import { accounts, sessions, users, verificationTokens, + // @ts-expect-error TODO: fix ts } from "~/server/db/schema"; /** @@ -17,6 +19,7 @@ import { * @see https://next-auth.js.org/getting-started/typescript#module-augmentation */ declare module "next-auth" { + // eslint-disable-next-line @typescript-eslint/consistent-type-definitions interface Session extends DefaultSession { user: { id: string; @@ -56,6 +59,7 @@ export const authConfig = { verificationTokensTable: verificationTokens, }), callbacks: { + // @ts-expect-error TODO: fix ts session: ({ session, user }) => ({ ...session, user: { @@ -64,4 +68,5 @@ export const authConfig = { }, }), }, + // @ts-expect-error TODO: fix ts } satisfies NextAuthConfig; diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/auth/config/with-prisma.ts b/src/app/menu/show-composer-mode/template/extras/src/server/auth/config/with-prisma.ts index 9b8ec79..40552e2 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/auth/config/with-prisma.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/auth/config/with-prisma.ts @@ -1,7 +1,9 @@ import { PrismaAdapter } from "@auth/prisma-adapter"; +// @ts-expect-error TODO: fix ts import { type DefaultSession, type NextAuthConfig } from "next-auth"; import DiscordProvider from "next-auth/providers/discord"; +// @ts-expect-error TODO: fix ts import { db } from "~/server/db"; /** @@ -11,6 +13,7 @@ import { db } from "~/server/db"; * @see https://next-auth.js.org/getting-started/typescript#module-augmentation */ declare module "next-auth" { + // eslint-disable-next-line @typescript-eslint/consistent-type-definitions interface Session extends DefaultSession { user: { id: string; @@ -44,7 +47,9 @@ export const authConfig = { */ ], adapter: PrismaAdapter(db), + callbacks: { + // @ts-expect-error TODO: fix ts session: ({ session, user }) => ({ ...session, user: { diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/auth/index.ts b/src/app/menu/show-composer-mode/template/extras/src/server/auth/index.ts index fa6d0a9..c9f210c 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/auth/index.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/auth/index.ts @@ -1,8 +1,10 @@ import NextAuth from "next-auth"; import { cache } from "react"; +// @ts-expect-error TODO: fix ts import { authConfig } from "./config.js"; +// @ts-expect-error TODO: fix ts const { auth: uncachedAuth, handlers, signIn, signOut } = NextAuth(authConfig); const auth = cache(uncachedAuth); diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/db/db-prisma-planetscale.ts b/src/app/menu/show-composer-mode/template/extras/src/server/db/db-prisma-planetscale.ts index 5218893..ba1d84b 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/db/db-prisma-planetscale.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/db/db-prisma-planetscale.ts @@ -2,6 +2,7 @@ import { Client } from "@planetscale/database"; import { PrismaPlanetScale } from "@prisma/adapter-planetscale"; import { PrismaClient } from "@prisma/client"; +// @ts-expect-error TODO: fix ts import { env } from "~/env"; const psClient = new Client({ url: env.DATABASE_URL }); @@ -14,6 +15,7 @@ const createPrismaClient = () => }); const globalForPrisma = globalThis as unknown as { + // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents prisma: ReturnType | undefined; }; diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/db/db-prisma.ts b/src/app/menu/show-composer-mode/template/extras/src/server/db/db-prisma.ts index 07dc027..06f4bf2 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/db/db-prisma.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/db/db-prisma.ts @@ -1,5 +1,6 @@ import { PrismaClient } from "@prisma/client"; +// @ts-expect-error TODO: fix ts import { env } from "~/env"; const createPrismaClient = () => @@ -9,6 +10,7 @@ const createPrismaClient = () => }); const globalForPrisma = globalThis as unknown as { + // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents prisma: ReturnType | undefined; }; diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/db/index-drizzle/with-mysql.ts b/src/app/menu/show-composer-mode/template/extras/src/server/db/index-drizzle/with-mysql.ts index ca6b59f..676f68d 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/db/index-drizzle/with-mysql.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/db/index-drizzle/with-mysql.ts @@ -1,7 +1,10 @@ import { drizzle } from "drizzle-orm/mysql2"; import { createPool, type Pool } from "mysql2/promise"; +// @ts-expect-error TODO: fix ts import { env } from "~/env"; + +// @ts-expect-error TODO: fix ts import * as schema from "./schema.js"; /** diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/db/index-drizzle/with-planetscale.ts b/src/app/menu/show-composer-mode/template/extras/src/server/db/index-drizzle/with-planetscale.ts index efa6556..eda0fcb 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/db/index-drizzle/with-planetscale.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/db/index-drizzle/with-planetscale.ts @@ -1,7 +1,10 @@ import { Client } from "@planetscale/database"; import { drizzle } from "drizzle-orm/planetscale-serverless"; +// @ts-expect-error TODO: fix ts import { env } from "~/env"; + +// @ts-expect-error TODO: fix ts import * as schema from "./schema.js"; export const db = drizzle(new Client({ url: env.DATABASE_URL }), { schema }); diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/db/index-drizzle/with-postgres.ts b/src/app/menu/show-composer-mode/template/extras/src/server/db/index-drizzle/with-postgres.ts index c04b4da..f34cce2 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/db/index-drizzle/with-postgres.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/db/index-drizzle/with-postgres.ts @@ -1,7 +1,10 @@ import { drizzle } from "drizzle-orm/postgres-js"; import postgres from "postgres"; +// @ts-expect-error TODO: fix ts import { env } from "~/env"; + +// @ts-expect-error TODO: fix ts import * as schema from "./schema.js"; /** diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/db/index-drizzle/with-sqlite.ts b/src/app/menu/show-composer-mode/template/extras/src/server/db/index-drizzle/with-sqlite.ts index b83aeab..af10f1b 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/db/index-drizzle/with-sqlite.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/db/index-drizzle/with-sqlite.ts @@ -1,7 +1,10 @@ import { createClient, type Client } from "@libsql/client"; import { drizzle } from "drizzle-orm/libsql"; +// @ts-expect-error TODO: fix ts import { env } from "~/env"; + +// @ts-expect-error TODO: fix ts import * as schema from "./schema.js"; /** diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/base-mysql.ts b/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/base-mysql.ts index bfb0807..78686f9 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/base-mysql.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/base-mysql.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-deprecated */ // Example model schema from the Drizzle docs // https://orm.drizzle.team/docs/sql-schema-declaration @@ -30,5 +31,5 @@ export const posts = createTable( }, (example) => ({ nameIndex: index("name_idx").on(example.name), - }) + }), ); diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/base-planetscale.ts b/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/base-planetscale.ts index bfb0807..78686f9 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/base-planetscale.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/base-planetscale.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-deprecated */ // Example model schema from the Drizzle docs // https://orm.drizzle.team/docs/sql-schema-declaration @@ -30,5 +31,5 @@ export const posts = createTable( }, (example) => ({ nameIndex: index("name_idx").on(example.name), - }) + }), ); diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/base-postgres.ts b/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/base-postgres.ts index 3878a98..9990604 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/base-postgres.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/base-postgres.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-deprecated */ // Example model schema from the Drizzle docs // https://orm.drizzle.team/docs/sql-schema-declaration @@ -27,10 +28,10 @@ export const posts = createTable( .default(sql`CURRENT_TIMESTAMP`) .notNull(), updatedAt: timestamp("updated_at", { withTimezone: true }).$onUpdate( - () => new Date() + () => new Date(), ), }, (example) => ({ nameIndex: index("name_idx").on(example.name), - }) + }), ); diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/base-sqlite.ts b/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/base-sqlite.ts index cc74c86..e2309c2 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/base-sqlite.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/base-sqlite.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-deprecated */ // Example model schema from the Drizzle docs // https://orm.drizzle.team/docs/sql-schema-declaration @@ -21,10 +22,10 @@ export const posts = createTable( .default(sql`(unixepoch())`) .notNull(), updatedAt: int("updated_at", { mode: "timestamp" }).$onUpdate( - () => new Date() + () => new Date(), ), }, (example) => ({ nameIndex: index("name_idx").on(example.name), - }) + }), ); diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/with-auth-mysql.ts b/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/with-auth-mysql.ts index 96e9a85..1cf7ae4 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/with-auth-mysql.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/with-auth-mysql.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-deprecated */ import { relations, sql } from "drizzle-orm"; import { bigint, @@ -35,7 +36,7 @@ export const posts = createTable( (example) => ({ createdByIdIdx: index("created_by_idx").on(example.createdById), nameIndex: index("name_idx").on(example.name), - }) + }), ); export const users = createTable("user", { @@ -83,7 +84,7 @@ export const accounts = createTable( columns: [account.provider, account.providerAccountId], }), userIdIdx: index("account_user_id_idx").on(account.userId), - }) + }), ); export const accountsRelations = relations(accounts, ({ one }) => ({ @@ -103,7 +104,7 @@ export const sessions = createTable( }, (session) => ({ userIdIdx: index("session_user_id_idx").on(session.userId), - }) + }), ); export const sessionsRelations = relations(sessions, ({ one }) => ({ @@ -119,5 +120,5 @@ export const verificationTokens = createTable( }, (vt) => ({ compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }), - }) + }), ); diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/with-auth-planetscale.ts b/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/with-auth-planetscale.ts index a0b1d72..7703af8 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/with-auth-planetscale.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/with-auth-planetscale.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-deprecated */ import { relations, sql } from "drizzle-orm"; import { bigint, @@ -33,7 +34,7 @@ export const posts = createTable( (example) => ({ createdByIdIdx: index("created_by_idx").on(example.createdById), nameIndex: index("name_idx").on(example.name), - }) + }), ); export const users = createTable("user", { @@ -79,7 +80,7 @@ export const accounts = createTable( columns: [account.provider, account.providerAccountId], }), userIdIdx: index("accounts_user_id_idx").on(account.userId), - }) + }), ); export const accountsRelations = relations(accounts, ({ one }) => ({ @@ -97,7 +98,7 @@ export const sessions = createTable( }, (session) => ({ userIdIdx: index("session_user_id_idx").on(session.userId), - }) + }), ); export const sessionsRelations = relations(sessions, ({ one }) => ({ @@ -113,5 +114,5 @@ export const verificationTokens = createTable( }, (vt) => ({ compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }), - }) + }), ); diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/with-auth-postgres.ts b/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/with-auth-postgres.ts index 43e6243..6bc69e4 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/with-auth-postgres.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/with-auth-postgres.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-deprecated */ import { relations, sql } from "drizzle-orm"; import { index, @@ -30,13 +31,13 @@ export const posts = createTable( .default(sql`CURRENT_TIMESTAMP`) .notNull(), updatedAt: timestamp("updated_at", { withTimezone: true }).$onUpdate( - () => new Date() + () => new Date(), ), }, (example) => ({ createdByIdIdx: index("created_by_idx").on(example.createdById), nameIndex: index("name_idx").on(example.name), - }) + }), ); export const users = createTable("user", { @@ -83,7 +84,7 @@ export const accounts = createTable( columns: [account.provider, account.providerAccountId], }), userIdIdx: index("account_user_id_idx").on(account.userId), - }) + }), ); export const accountsRelations = relations(accounts, ({ one }) => ({ @@ -106,7 +107,7 @@ export const sessions = createTable( }, (session) => ({ userIdIdx: index("session_user_id_idx").on(session.userId), - }) + }), ); export const sessionsRelations = relations(sessions, ({ one }) => ({ @@ -125,5 +126,5 @@ export const verificationTokens = createTable( }, (vt) => ({ compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }), - }) + }), ); diff --git a/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/with-auth-sqlite.ts b/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/with-auth-sqlite.ts index 12ee290..f373424 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/with-auth-sqlite.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/server/db/schema-drizzle/with-auth-sqlite.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-deprecated */ import { relations, sql } from "drizzle-orm"; import { index, @@ -28,13 +29,13 @@ export const posts = createTable( .default(sql`(unixepoch())`) .notNull(), updatedAt: int("updatedAt", { mode: "timestamp" }).$onUpdate( - () => new Date() + () => new Date(), ), }, (example) => ({ createdByIdIdx: index("created_by_idx").on(example.createdById), nameIndex: index("name_idx").on(example.name), - }) + }), ); export const users = createTable("user", { @@ -78,7 +79,7 @@ export const accounts = createTable( columns: [account.provider, account.providerAccountId], }), userIdIdx: index("account_user_id_idx").on(account.userId), - }) + }), ); export const accountsRelations = relations(accounts, ({ one }) => ({ @@ -96,7 +97,7 @@ export const sessions = createTable( }, (session) => ({ userIdIdx: index("session_userId_idx").on(session.userId), - }) + }), ); export const sessionsRelations = relations(sessions, ({ one }) => ({ @@ -112,5 +113,5 @@ export const verificationTokens = createTable( }, (vt) => ({ compoundKey: primaryKey({ columns: [vt.identifier, vt.token] }), - }) + }), ); diff --git a/src/app/menu/show-composer-mode/template/extras/src/trpc/react.tsx b/src/app/menu/show-composer-mode/template/extras/src/trpc/react.tsx index fcb1c95..6d15b6d 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/trpc/react.tsx +++ b/src/app/menu/show-composer-mode/template/extras/src/trpc/react.tsx @@ -8,7 +8,7 @@ import { useState } from "react"; import React from "react"; import SuperJSON from "superjson"; -import { type framework } from "../server/api/root.js"; +import { type AppRouter } from "../server/api/root.js"; import { createQueryClient } from "./query-client.js"; let clientQueryClientSingleton: QueryClient | undefined = undefined; @@ -21,27 +21,27 @@ const getQueryClient = () => { return (clientQueryClientSingleton ??= createQueryClient()); }; -export const api = createTRPCReact(); +export const api = createTRPCReact(); /** * Inference helper for inputs. * * @example type HelloInput = RouterInputs['example']['hello'] */ -export type RouterInputs = inferRouterInputs; +export type RouterInputs = inferRouterInputs; /** * Inference helper for outputs. * * @example type HelloOutput = RouterOutputs['example']['hello'] */ -export type RouterOutputs = inferRouterOutputs; +export type RouterOutputs = inferRouterOutputs; export function TRPCReactProvider(props: { children: React.ReactNode }) { const queryClient = getQueryClient(); - const [trpcClient] = useState(() => - api.createClient({ + const [trpcClient] = useState(() => { + return api.createClient({ links: [ loggerLink({ enabled: (op) => @@ -58,11 +58,12 @@ export function TRPCReactProvider(props: { children: React.ReactNode }) { }, }), ], - }), - ); + }); + }); return ( + {/* @ts-expect-error cli codebase hasn't have access to node_modules */} {props.children} diff --git a/src/app/menu/show-composer-mode/template/extras/src/trpc/server.ts b/src/app/menu/show-composer-mode/template/extras/src/trpc/server.ts index 692fdf5..718f7d2 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/trpc/server.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/trpc/server.ts @@ -1,11 +1,11 @@ import "server-only"; - import { createHydrationHelpers } from "@trpc/react-query/rsc"; +// @ts-expect-error TODO: fix Next.js 15 import { headers } from "next/headers"; import { cache } from "react"; -import { createCaller, type framework } from "~/server/api/root"; -import { createTRPCContext } from "~/server/api/trpc"; +import { type AppRouter } from "./../server/api/root.js"; +import { createTRPCContext } from "./../server/api/trpc.js"; import { createQueryClient } from "./query-client.js"; /** @@ -17,14 +17,17 @@ const createContext = cache(async () => { heads.set("x-trpc-source", "rsc"); return createTRPCContext({ + // @ts-expect-error TODO: fix ts headers: heads, }); }); const getQueryClient = cache(createQueryClient); +// @ts-expect-error TODO: fix ts const caller = createCaller(createContext); -export const { trpc: api, HydrateClient } = createHydrationHelpers( +export const { trpc: api, HydrateClient } = createHydrationHelpers( caller, + // @ts-expect-error TODO: fix query import path getQueryClient, ); diff --git a/src/app/menu/show-composer-mode/template/extras/src/utils/api.ts b/src/app/menu/show-composer-mode/template/extras/src/utils/api.ts index a965868..7b4a074 100644 --- a/src/app/menu/show-composer-mode/template/extras/src/utils/api.ts +++ b/src/app/menu/show-composer-mode/template/extras/src/utils/api.ts @@ -1,56 +1,28 @@ +import { QueryClient } from "@tanstack/react-query"; /** * This is the client-side entrypoint for your tRPC API. It is used to create the `api` object which * contains the Next.js App-wrapper, as well as your type-safe React Query hooks. * * We also create a few inference helpers for input and output types. */ -import { httpBatchLink, loggerLink } from "@trpc/client"; -import { createTRPCNext } from "@trpc/next"; +import { createTRPCClient, httpBatchLink } from "@trpc/client"; +import { createTRPCReact } from "@trpc/react-query"; import { type inferRouterInputs, type inferRouterOutputs } from "@trpc/server"; -import superjson from "superjson"; +import SuperJSON from "superjson"; -import { type framework } from "~/server/api/root"; +import type { AppRouter } from "../server/api/root.js"; -const getBaseUrl = () => { - if (typeof window !== "undefined") return ""; // browser should use relative url - if (process.env.VERCEL_URL) return `https://${process.env.VERCEL_URL}`; // SSR should use vercel url - return `http://localhost:${process.env.PORT ?? 3000}`; // dev SSR should use localhost -}; +export const api = createTRPCReact(); -/** A set of type-safe react-query hooks for your tRPC API. */ -export const api = createTRPCNext({ - config() { - return { - /** - * Links used to determine request flow from client to server. - * - * @see https://trpc.io/docs/links - */ - links: [ - loggerLink({ - enabled: (opts) => - process.env.NODE_ENV === "development" || - (opts.direction === "down" && opts.result instanceof Error), - }), - httpBatchLink({ - /** - * Transformer used for data de-serialization from the server. - * - * @see https://trpc.io/docs/data-transformers - */ - transformer: superjson, - url: `${getBaseUrl()}/api/trpc`, - }), - ], - }; - }, - /** - * Whether tRPC should await queries when server rendering pages. - * - * @see https://trpc.io/docs/nextjs#ssr-boolean-default-false - */ - ssr: false, - transformer: superjson, +export const queryClient = new QueryClient(); + +export const trpcClient = createTRPCClient({ + links: [ + httpBatchLink({ + url: `${import.meta.env["VITE_API_URL"]}/trpc`, + transformer: SuperJSON, + }), + ], }); /** @@ -58,11 +30,11 @@ export const api = createTRPCNext({ * * @example type HelloInput = RouterInputs['example']['hello'] */ -export type RouterInputs = inferRouterInputs; +export type RouterInputs = inferRouterInputs; /** * Inference helper for outputs. * * @example type HelloOutput = RouterOutputs['example']['hello'] */ -export type RouterOutputs = inferRouterOutputs; +export type RouterOutputs = inferRouterOutputs; diff --git a/src/app/menu/show-composer-mode/template/extras/ts-config.json b/src/app/menu/show-composer-mode/template/extras/ts-config.json new file mode 100644 index 0000000..f88622d --- /dev/null +++ b/src/app/menu/show-composer-mode/template/extras/ts-config.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "baseUrl": ".", + "paths": { + "~/*": ["src/*"], + "~react-pages": ["./.vite/pages-generated.d.ts"] + } + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/src/app/menu/show-composer-mode/template/extras/ts-config.node.json b/src/app/menu/show-composer-mode/template/extras/ts-config.node.json new file mode 100644 index 0000000..42872c5 --- /dev/null +++ b/src/app/menu/show-composer-mode/template/extras/ts-config.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/src/app/menu/show-composer-mode/utils/addPackageDependency.ts b/src/app/menu/show-composer-mode/utils/addPackageDependency.ts index 4adaa2a..21d88e6 100644 --- a/src/app/menu/show-composer-mode/utils/addPackageDependency.ts +++ b/src/app/menu/show-composer-mode/utils/addPackageDependency.ts @@ -1,12 +1,12 @@ -import path from "pathe"; +import type { PackageJson } from "type-fest"; + import fs from "fs-extra"; +import path from "pathe"; import sortPackageJson from "sort-package-json"; -import { type PackageJson } from "type-fest"; -import { - dependencyVersionMap, - type AvailableDependencies, -} from "~/installers/dependencyVersionMap.js"; +import type { AvailableDependencies } from "../installers/dependencyVersionMap.js"; + +import { dependencyVersionMap } from "../installers/dependencyVersionMap.js"; export const addPackageDependency = (opts: { dependencies: AvailableDependencies[]; diff --git a/src/app/menu/show-composer-mode/utils/getReliverseCliVersion.ts b/src/app/menu/show-composer-mode/utils/getReliverseCliVersion.ts index 01ce4c2..6b3d6a9 100644 --- a/src/app/menu/show-composer-mode/utils/getReliverseCliVersion.ts +++ b/src/app/menu/show-composer-mode/utils/getReliverseCliVersion.ts @@ -1,8 +1,9 @@ +import type { PackageJson } from "type-fest"; + import fs from "fs-extra"; import path from "pathe"; -import { type PackageJson } from "type-fest"; -import { PKG_ROOT } from "~/consts.js"; +import { PKG_ROOT } from "~/app/db/constants.js"; export const getVersion = () => { const packageJsonPath = path.join(PKG_ROOT, "package.json"); diff --git a/src/app/menu/show-composer-mode/utils/getUserPkgManager.ts b/src/app/menu/show-composer-mode/utils/getUserPkgManager.ts index c57d3c3..61e3842 100644 --- a/src/app/menu/show-composer-mode/utils/getUserPkgManager.ts +++ b/src/app/menu/show-composer-mode/utils/getUserPkgManager.ts @@ -2,7 +2,7 @@ export type PackageManager = "npm" | "pnpm" | "yarn" | "bun"; export const getUserPkgManager: () => PackageManager = () => { // This environment variable is set by npm and yarn but pnpm seems less consistent - const userAgent = process.env.npm_config_user_agent; + const userAgent = process.env["npm_config_user_agent"]; if (userAgent) { if (userAgent.startsWith("yarn")) { diff --git a/src/app/menu/show-composer-mode/utils/isTTYError.ts b/src/app/menu/show-composer-mode/utils/isTTYError.ts index d227b82..45d2fac 100644 --- a/src/app/menu/show-composer-mode/utils/isTTYError.ts +++ b/src/app/menu/show-composer-mode/utils/isTTYError.ts @@ -1,4 +1,6 @@ +/* eslint-disable @typescript-eslint/no-useless-constructor */ export class IsTTYError extends Error { + // biome-ignore lint/complexity/noUselessConstructor: constructor(msg: string) { super(msg); } diff --git a/src/app/menu/show-composer-mode/utils/renderTitle.ts b/src/app/menu/show-composer-mode/utils/renderTitle.ts index 336e9d4..887d025 100644 --- a/src/app/menu/show-composer-mode/utils/renderTitle.ts +++ b/src/app/menu/show-composer-mode/utils/renderTitle.ts @@ -1,24 +1,8 @@ -import gradient from "gradient-string"; +import { createAsciiArt } from "@reliverse/prompts"; -import { TITLE_TEXT } from "~/app/db/constants.js"; -import { getUserPkgManager } from "~/app/menu/show-composer-mode/utils/getUserPkgManager.js"; - -const gradientTheme = { - blue: "#add7ff", - cyan: "#89ddff", - green: "#5de4c7", - magenta: "#fae4fc", - red: "#d0679d", - yellow: "#fffac2", -}; - -export const renderTitle = () => { - const titleGradient = gradient(Object.values(gradientTheme)); - - // resolves weird behavior where the ascii is offset - const pkgManager = getUserPkgManager(); - if (pkgManager === "yarn" || pkgManager === "pnpm") { - console.log(""); - } - console.log(titleGradient.multiline(TITLE_TEXT)); +export const renderTitle = async () => { + await createAsciiArt({ + message: "Reliverse", + font: "block", + }); }; diff --git a/src/app/menu/show-composer-mode/utils/renderVersionWarning.ts b/src/app/menu/show-composer-mode/utils/renderVersionWarning.ts index 1109eb0..eaebd58 100644 --- a/src/app/menu/show-composer-mode/utils/renderVersionWarning.ts +++ b/src/app/menu/show-composer-mode/utils/renderVersionWarning.ts @@ -47,6 +47,7 @@ function checkForLatestVersion(): Promise { (res) => { if (res.statusCode === 200) { let body = ""; + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands res.on("data", (data) => (body += data)); res.on("end", () => { resolve((JSON.parse(body) as DistTagsBody).latest); diff --git a/src/app/menu/showAnykeyPrompt.ts b/src/app/menu/showAnykeyPrompt.ts index 0f92115..6da686b 100644 --- a/src/app/menu/showAnykeyPrompt.ts +++ b/src/app/menu/showAnykeyPrompt.ts @@ -2,7 +2,7 @@ import { anykeyPrompt } from "@reliverse/prompts"; import pc from "picocolors"; export async function showAnykeyPrompt() { - const notification = `šŸ¤– Hello, my name is Reliverse! I'm your assistant for creating new web projects, integrating new features, and making advanced codebase modifications. āœØ I'm constantly evolving, with even more features on the way! In the future, I'll be able to work with not only web apps. Let's get started!\nā”‚ ============================\nā”‚ ${pc.bold("Press any key to continue...")}`; + const notification = `šŸ¤– Hello, my name is Reliverse! I'm your assistant for creating new web projects, integrating new features, and making advanced codebase modifications.\nāœØ I'm constantly evolving, with even more features on the way! In the future, I'll be able to work with not only web apps. Let's get started!\nā”‚ ============================\nā”‚ ${pc.bold("Press any key to continue...")}`; await anykeyPrompt(notification); } diff --git a/src/app/menu/showStartEndPrompt.ts b/src/app/menu/showStartEndPrompt.ts index 7bff706..2977b3e 100644 --- a/src/app/menu/showStartEndPrompt.ts +++ b/src/app/menu/showStartEndPrompt.ts @@ -6,7 +6,7 @@ export async function showStartPrompt() { titleColor: "inverse", clearConsole: true, packageName: "@reliverse/cli", - packageVersion: "1.4.3", + packageVersion: "1.4.8", }); } diff --git a/src/args/config/mod.ts b/src/args/config/mod.ts index d0a8ce2..07cd4bc 100644 --- a/src/args/config/mod.ts +++ b/src/args/config/mod.ts @@ -49,7 +49,7 @@ export default defineCommand({ projectCategory: rules.experimental?.projectCategory ?? "website", projectType: rules.experimental?.projectType ?? "development", projectDeployService: - rules.experimental?.projectDeployService ?? "Vercel", + rules.experimental?.projectDeployService ?? "vercel", projectDisplayName: rules.experimental?.projectDisplayName ?? "", projectDomain: rules.experimental?.projectDomain ?? "", projectState: rules.experimental?.projectState ?? "creating", @@ -64,9 +64,6 @@ export default defineCommand({ rules.experimental?.projectPackageManager ?? "npm", nodeVersion: rules.experimental?.nodeVersion ?? "latest", runtime: rules.experimental?.runtime ?? "nodejs", - - // Deployment - deployPlatform: rules.experimental?.deployPlatform ?? "Vercel", productionBranch: rules.experimental?.productionBranch ?? "main", deployUrl: rules.experimental?.deployUrl ?? "", diff --git a/src/args/login/impl.ts b/src/args/login/impl.ts index 43729b1..f8630d2 100644 --- a/src/args/login/impl.ts +++ b/src/args/login/impl.ts @@ -1,6 +1,6 @@ import type { ParsedUrlQuery } from "querystring"; -import { task } from "@reliverse/prompts"; +import { spinnerTaskPrompt } from "@reliverse/prompts"; import { listen } from "async-listen"; import http from "http"; import { customAlphabet } from "nanoid"; @@ -39,13 +39,13 @@ export async function auth({ }) { relinka("info", "Let's authenticate you..."); - await task({ + await spinnerTaskPrompt({ initialMessage: "Waiting for user confirmation...", successMessage: "Login cancelled. See you next time šŸ‘‹", errorMessage: "Authentication failed!", spinnerSolution: "ora", spinnerType: "arc", - action: async (updateMessage) => { + action: async (updateMessage: (message: string) => void) => { // Create a local HTTP server to handle the authentication callback const server = http.createServer(); let port: number | string | undefined; diff --git a/src/main.ts b/src/main.ts index 52001fe..8bcc4ad 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,12 +2,12 @@ import { defineCommand, errorHandler, runMain } from "@reliverse/prompts"; -import { logger } from "./app/menu/show-composer-mode/utils/logger.js"; +import { relinka } from "~/utils/console.js"; const main = defineCommand({ meta: { name: "reliverse", - version: "1.4.3", + version: "1.4.8", description: "https://docs.reliverse.org", }, subCommands: { diff --git a/src/types.ts b/src/types.ts index 0c47426..9ca5572 100644 --- a/src/types.ts +++ b/src/types.ts @@ -64,7 +64,12 @@ export type IntegrationCategory = export type IntegrationOptions = Record; -export type DeploymentService = "Vercel" | "Netlify" | "Railway" | "none"; +export type DeploymentService = + | "vercel" + | "deno" + | "netlify" + | "railway" + | "none"; export type PreferredLibraries = { stateManagement?: "zustand" | "jotai" | "redux-toolkit" | "none"; @@ -83,7 +88,7 @@ export type PreferredLibraries = { api?: "trpc" | "graphql" | "rest" | "none"; linting?: "eslint" | "none"; formatting?: "biome" | "none"; - deployment?: "vercel" | "none"; + deployment?: DeploymentService; payment?: "stripe" | "none"; analytics?: "vercel" | "none"; monitoring?: "sentry" | "none"; @@ -150,7 +155,7 @@ export type TemplateOption = | "blefnk/next-react-ts-src-minimal"; // design = graphic|video|audio|3d design -export type ProjectTypeOtions = "" | "development" | "design" | "marketing"; +export type ProjectTypeOptions = "" | "development" | "design" | "marketing"; export type ProjectCategory = | "" @@ -189,7 +194,7 @@ export type ReliverseConfig = { projectRepository?: string | undefined; projectState?: ProjectState | undefined; projectDomain?: string | undefined; - projectType?: ProjectTypeOtions | undefined; + projectType?: ProjectTypeOptions | undefined; projectCategory?: ProjectCategory | undefined; projectSubcategory?: ProjectSubcategory | undefined; projectTemplate?: TemplateOption | undefined; @@ -201,12 +206,8 @@ export type ReliverseConfig = { projectDisplayName?: string | undefined; nodeVersion?: string | undefined; runtime?: string | undefined; - - // Deployment - deployPlatform?: DeploymentService | undefined; productionBranch?: string | undefined; deployUrl?: string | undefined; - monorepo?: | { type: string; diff --git a/src/utils/configs/miscellaneousConfigHelpers.ts b/src/utils/configs/miscellaneousConfigHelpers.ts index 12e3be7..6e5abcc 100644 --- a/src/utils/configs/miscellaneousConfigHelpers.ts +++ b/src/utils/configs/miscellaneousConfigHelpers.ts @@ -139,15 +139,13 @@ export async function readConfig(cwd: string): Promise { rules.experimental?.projectRepository ?? config.experimental?.projectRepository ?? "", - - deployPlatform: rules.experimental?.deployPlatform ?? "Vercel", productionBranch: rules.experimental?.productionBranch ?? "main", deployUrl: rules.experimental?.deployUrl ?? "", projectActivation: rules.experimental?.projectActivation ?? "auto", projectCategory: rules.experimental?.projectCategory ?? "website", projectType: rules.experimental?.projectType ?? "development", projectDeployService: - rules.experimental?.projectDeployService ?? "Vercel", + rules.experimental?.projectDeployService ?? "vercel", projectDisplayName: rules.experimental?.projectDisplayName ?? "", projectDomain: rules.experimental?.projectDomain ?? "", projectState: rules.experimental?.projectState ?? "creating", diff --git a/src/utils/downloadGitRepo.ts b/src/utils/downloadGitRepo.ts index 7b88759..b81f7cf 100644 --- a/src/utils/downloadGitRepo.ts +++ b/src/utils/downloadGitRepo.ts @@ -26,11 +26,11 @@ export async function downloadGitRepo( // Check if directory contains only .reliverse const files = await fs.readdir(targetDir); - const hasOnlyReliverserules = + const hasOnlyReliverseConfig = files.length === 1 && files[0] === ".reliverse"; // If directory is not empty and doesn't contain only .reliverse, throw error - if (files.length > 0 && !hasOnlyReliverserules) { + if (files.length > 0 && !hasOnlyReliverseConfig) { throw new Error( `Target directory ${targetDir} is not empty and contains files other than .reliverse`, ); @@ -38,11 +38,11 @@ export async function downloadGitRepo( // Temporarily move .reliverse if it exists const parentDir = path.dirname(targetDir); - const tempReliverserulesPath = path.join(parentDir, ".reliverse"); + const tempReliverseConfigPath = path.join(parentDir, ".reliverse"); - if (hasOnlyReliverserules) { + if (hasOnlyReliverseConfig) { // Check if .reliverse already exists in parent directory - if (await fs.pathExists(tempReliverserulesPath)) { + if (await fs.pathExists(tempReliverseConfigPath)) { const choice = await selectPrompt({ title: ".reliverse already exists in parent directory. What would you like to do?", @@ -53,7 +53,7 @@ export async function downloadGitRepo( }); if (choice === "delete") { - await fs.remove(tempReliverserulesPath); + await fs.remove(tempReliverseConfigPath); } else { // Find appropriate backup name let backupPath = path.join(parentDir, ".reliverse.bak"); @@ -62,11 +62,14 @@ export async function downloadGitRepo( backupPath = path.join(parentDir, `.reliverse_${iteration}.bak`); iteration++; } - await fs.move(tempReliverserulesPath, backupPath); + await fs.move(tempReliverseConfigPath, backupPath); } } - await fs.move(path.join(targetDir, ".reliverse"), tempReliverserulesPath); + await fs.move( + path.join(targetDir, ".reliverse"), + tempReliverseConfigPath, + ); await fs.remove(targetDir); await fs.ensureDir(targetDir); } @@ -78,9 +81,9 @@ export async function downloadGitRepo( await git.clone(repoUrl, targetDir); // Restore .reliverse if it was moved - if (hasOnlyReliverserules) { + if (hasOnlyReliverseConfig) { await fs.move( - tempReliverserulesPath, + tempReliverseConfigPath, path.join(targetDir, ".reliverse"), { overwrite: true }, ); @@ -91,12 +94,12 @@ export async function downloadGitRepo( } catch (error) { // Restore .reliverse if operation failed if ( - hasOnlyReliverserules && - (await fs.pathExists(tempReliverserulesPath)) + hasOnlyReliverseConfig && + (await fs.pathExists(tempReliverseConfigPath)) ) { await fs.ensureDir(targetDir); await fs.move( - tempReliverserulesPath, + tempReliverseConfigPath, path.join(targetDir, ".reliverse"), { overwrite: true }, ); diff --git a/src/utils/handlers/codemods/convertToMonorepo.ts b/src/utils/handlers/codemods/convertToMonorepo.ts index be9900c..b742160 100644 --- a/src/utils/handlers/codemods/convertToMonorepo.ts +++ b/src/utils/handlers/codemods/convertToMonorepo.ts @@ -74,7 +74,7 @@ export async function convertToMonorepo( // Move other common files const commonFiles = [ "tsconfig.json", - ".eslintrc.js", + "eslint.config.js", ".prettierrc", "next.config.js", "postcss.config.js", diff --git a/tsconfig.json b/tsconfig.json index 514401a..a200e9c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -38,6 +38,7 @@ "build.optim.ts", "build.config.ts", "vitest.config.ts", + "src/**/*.js", "src/**/*.ts", "src/**/*.tsx", "addons/**/*.ts",