theokit 0.4.0-beta.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{actions-virtual-module-HWNTIEPI.js → actions-virtual-module-IY46EEEX.js} +2 -2
- package/dist/{actions-virtual-module-SEIPACG5.js → actions-virtual-module-XNHDNCZ6.js} +2 -2
- package/dist/add-2ZFFGGE4.js +0 -0
- package/dist/{app-typed-client-ITIYTBXO.js → app-typed-client-OUJO3V5J.js} +10 -5
- package/dist/{app-typed-client-62FYBLVJ.js.map → app-typed-client-OUJO3V5J.js.map} +1 -1
- package/dist/{app-typed-client-62FYBLVJ.js → app-typed-client-YOMWSA3F.js} +11 -6
- package/dist/{app-typed-client-ITIYTBXO.js.map → app-typed-client-YOMWSA3F.js.map} +1 -1
- package/dist/audit-log-BQWM5YLG.d.ts +60 -0
- package/dist/{aws-lambda-XYCGZH3I.js → aws-lambda-GKSTX6HD.js} +3 -3
- package/dist/body-parser-web-MUWBKZ3F.js +70 -0
- package/dist/body-parser-web-MUWBKZ3F.js.map +1 -0
- package/dist/broadcast-QOQGUNNK.js +0 -0
- package/dist/{build-HIGHJQQV.js → build-D4J7XECU.js} +31 -22
- package/dist/build-D4J7XECU.js.map +1 -0
- package/dist/build-request-body-preview-II2235E6.js +0 -0
- package/dist/{bun-K73TQEWC.js → bun-ISHSNFIO.js} +3 -3
- package/dist/{check-PZR67OY5.js → check-NXYPLM6M.js} +2 -2
- package/dist/{chunk-WZ7CPVQB.js → chunk-27LWMYNK.js} +28 -24
- package/dist/chunk-27LWMYNK.js.map +1 -0
- package/dist/{chunk-BIGBFZSU.js → chunk-2F4FROEQ.js} +1689 -1521
- package/dist/chunk-2F4FROEQ.js.map +1 -0
- package/dist/chunk-4HBI7DHP.js +20 -0
- package/dist/chunk-4HBI7DHP.js.map +1 -0
- package/dist/{chunk-X4JAX2U3.js → chunk-4S2UCILN.js} +180 -125
- package/dist/chunk-4S2UCILN.js.map +1 -0
- package/dist/{chunk-C52GSJPF.js → chunk-4S6YHNG2.js} +11 -8
- package/dist/chunk-4S6YHNG2.js.map +1 -0
- package/dist/chunk-5ODOE6EF.js +0 -0
- package/dist/{chunk-KJVEJILQ.js → chunk-5PU65T5W.js} +9 -3
- package/dist/chunk-5PU65T5W.js.map +1 -0
- package/dist/chunk-5QW7IQQU.js +36 -0
- package/dist/chunk-5QW7IQQU.js.map +1 -0
- package/dist/chunk-5Z2727GV.js +1324 -0
- package/dist/chunk-5Z2727GV.js.map +1 -0
- package/dist/{chunk-YLBSSEHX.js → chunk-6NEXVBPY.js} +5 -15
- package/dist/chunk-6NEXVBPY.js.map +1 -0
- package/dist/chunk-7MQOHNHE.js +36 -0
- package/dist/chunk-7MQOHNHE.js.map +1 -0
- package/dist/{chunk-TPCT3MXV.js → chunk-ABVITXFH.js} +405 -272
- package/dist/chunk-ABVITXFH.js.map +1 -0
- package/dist/chunk-ASDXVRJD.js +185 -0
- package/dist/chunk-ASDXVRJD.js.map +1 -0
- package/dist/chunk-B3L3TJVF.js +406 -0
- package/dist/chunk-B3L3TJVF.js.map +1 -0
- package/dist/{chunk-PB4GR7EP.js → chunk-C3ZZ56YZ.js} +1 -18
- package/dist/chunk-C3ZZ56YZ.js.map +1 -0
- package/dist/{chunk-O7335ZGH.js → chunk-CG563V5M.js} +5 -3
- package/dist/chunk-CG563V5M.js.map +1 -0
- package/dist/{chunk-5OFPQK6N.js → chunk-COXLEZCN.js} +2 -1
- package/dist/chunk-COXLEZCN.js.map +1 -0
- package/dist/{chunk-O4BLVPML.js → chunk-DNMSV3R3.js} +22 -28
- package/dist/chunk-DNMSV3R3.js.map +1 -0
- package/dist/chunk-E3JH6YUM.js +1 -0
- package/dist/chunk-ESC54TAK.js +220 -0
- package/dist/chunk-ESC54TAK.js.map +1 -0
- package/dist/chunk-FI7MPTJW.js +77 -0
- package/dist/chunk-FI7MPTJW.js.map +1 -0
- package/dist/chunk-H4J2LECY.js +201 -0
- package/dist/chunk-H4J2LECY.js.map +1 -0
- package/dist/chunk-IAJ2JVEH.js +256 -0
- package/dist/chunk-IAJ2JVEH.js.map +1 -0
- package/dist/{chunk-F3TG5FJV.js → chunk-IEZCAT2I.js} +7 -1
- package/dist/{chunk-F3TG5FJV.js.map → chunk-IEZCAT2I.js.map} +1 -1
- package/dist/chunk-JHEAWR3L.js +1 -0
- package/dist/chunk-JQSKBMXP.js +17 -0
- package/dist/chunk-JQSKBMXP.js.map +1 -0
- package/dist/{chunk-4QTKSLTO.js → chunk-K4M64WD6.js} +9 -5
- package/dist/{chunk-4QTKSLTO.js.map → chunk-K4M64WD6.js.map} +1 -1
- package/dist/chunk-KI7OWQUX.js +293 -0
- package/dist/chunk-KI7OWQUX.js.map +1 -0
- package/dist/chunk-KT3MWNZG.js +0 -0
- package/dist/{chunk-ZJW5FFYJ.js → chunk-LC5PYNJP.js} +2 -2
- package/dist/{chunk-JAAHOGKI.js → chunk-M2EY7KDR.js} +1223 -1124
- package/dist/chunk-M2EY7KDR.js.map +1 -0
- package/dist/{chunk-2VQKDRVF.js → chunk-MR7OLIC6.js} +3 -3
- package/dist/chunk-NM4WF3XD.js +63 -0
- package/dist/chunk-NM4WF3XD.js.map +1 -0
- package/dist/chunk-NPMCKOX4.js +1 -0
- package/dist/{chunk-VRHXOKRP.js → chunk-NZXH2LQQ.js} +86 -83
- package/dist/chunk-NZXH2LQQ.js.map +1 -0
- package/dist/{chunk-B6RP57M3.js → chunk-OLPB36O2.js} +4 -4
- package/dist/chunk-PIVX3DYW.js +142 -0
- package/dist/chunk-PIVX3DYW.js.map +1 -0
- package/dist/{chunk-7TOMTMHT.js → chunk-R7OC6UDK.js} +2 -1
- package/dist/chunk-R7OC6UDK.js.map +1 -0
- package/dist/chunk-RESN62GB.js +269 -0
- package/dist/chunk-RESN62GB.js.map +1 -0
- package/dist/{chunk-64JI5LTO.js → chunk-RMU7EBRO.js} +2 -2
- package/dist/chunk-RMU7EBRO.js.map +1 -0
- package/dist/chunk-TQYSPYKU.js +131 -0
- package/dist/chunk-TQYSPYKU.js.map +1 -0
- package/dist/chunk-TSLSIRBR.js +107 -0
- package/dist/chunk-TSLSIRBR.js.map +1 -0
- package/dist/{chunk-XP7YXRHO.js → chunk-U6TPSW4L.js} +37 -5
- package/dist/chunk-U6TPSW4L.js.map +1 -0
- package/dist/chunk-UD3LFGDL.js +45 -0
- package/dist/chunk-UD3LFGDL.js.map +1 -0
- package/dist/chunk-UUFYP2UM.js +161 -0
- package/dist/chunk-UUFYP2UM.js.map +1 -0
- package/dist/{chunk-OXIQI2XZ.js → chunk-VBZCVFSA.js} +2 -2
- package/dist/chunk-VMEWD57H.js +137 -0
- package/dist/chunk-VMEWD57H.js.map +1 -0
- package/dist/{chunk-TRH2L3FI.js → chunk-WEGALZEU.js} +3 -3
- package/dist/{chunk-ZOXNJV2O.js → chunk-WGHJD6I7.js} +35 -138
- package/dist/chunk-WGHJD6I7.js.map +1 -0
- package/dist/chunk-XMS5VRIK.js +0 -0
- package/dist/chunk-Y4H3JNVK.js +18 -0
- package/dist/chunk-Y4H3JNVK.js.map +1 -0
- package/dist/chunk-Z5IGLBWE.js +329 -0
- package/dist/chunk-Z5IGLBWE.js.map +1 -0
- package/dist/chunk-ZEGYW52B.js +26 -0
- package/dist/chunk-ZEGYW52B.js.map +1 -0
- package/dist/chunk-ZJQ4O76G.js +87 -0
- package/dist/chunk-ZJQ4O76G.js.map +1 -0
- package/dist/cli/index.js +32 -21
- package/dist/cli/index.js.map +1 -1
- package/dist/client/index.d.ts +107 -1
- package/dist/client/index.js +152 -6
- package/dist/client/index.js.map +1 -1
- package/dist/{cloudflare-LCAOTRYZ.js → cloudflare-OJGJ7R2D.js} +3 -3
- package/dist/configure-agent-registry-6YGTJYBC.js +0 -0
- package/dist/cookies-MJKMGJ7N.js +22 -0
- package/dist/csrf-BBrEZSBW.d.ts +107 -0
- package/dist/csrf-readiness-store-CjIoub3U.d.ts +43 -0
- package/dist/define-websocket-CdK94O-D.d.ts +64 -0
- package/dist/{deno-deploy-ZN4RE5HF.js → deno-deploy-BHWKFTOW.js} +3 -3
- package/dist/{dev-266MVCVA.js → dev-MJVSRZGF.js} +11 -11
- package/dist/{dev-emit-RBSFZZNO.js → dev-emit-5VPRCHOD.js} +39 -142
- package/dist/dev-emit-5VPRCHOD.js.map +1 -0
- package/dist/{dev-emit-NO6OZJOQ.js → dev-emit-HHP2XJXE.js} +5 -5
- package/dist/devtools/entry.js +185 -131
- package/dist/devtools/entry.js.map +1 -1
- package/dist/{dispatcher-AQJGI3HU.js → dispatcher-63LBXYLE.js} +2 -2
- package/dist/{dispatcher-E6QV32BO.js → dispatcher-EJME6C6M.js} +5 -2
- package/dist/dispatcher-EJME6C6M.js.map +1 -0
- package/dist/{dist-5V77Z223.js → dist-KRW6OVD4.js} +7 -7
- package/dist/dist-KRW6OVD4.js.map +1 -0
- package/dist/docker-EZDA5FT7.js +0 -0
- package/dist/error-envelope-BsNzzAV5.d.ts +62 -0
- package/dist/{generate-DT4IIAHF.js → generate-AZ2K6Z2Z.js} +75 -2
- package/dist/generate-AZ2K6Z2Z.js.map +1 -0
- package/dist/index-DWWEdFh5.d.ts +399 -0
- package/dist/index.d.ts +161 -1391
- package/dist/index.js +20 -8
- package/dist/index.js.map +1 -1
- package/dist/{info-FJ4NXKTB.js → info-47VB62MV.js} +5 -5
- package/dist/job-backend-CgC8Xf33.d.ts +68 -0
- package/dist/{lib-NL5JXGEB.js → lib-NST3PNZX.js} +31 -31
- package/dist/lib-NST3PNZX.js.map +1 -0
- package/dist/module-loader-Cfz7dgCP.d.ts +26 -0
- package/dist/{netlify-5VQ22Z2P.js → netlify-GSNSJGMN.js} +3 -3
- package/dist/{node-3APTLGWB.js → node-6I5B6RL3.js} +3 -3
- package/dist/node-6I5B6RL3.js.map +1 -0
- package/dist/{openapi-FNVSWZOL.js → openapi-NFLSNNVQ.js} +10 -10
- package/dist/plugin-runner-BGBkzgi0.d.ts +95 -0
- package/dist/plugin-types-DNJGxr4Z.d.ts +79 -0
- package/dist/{proper-lockfile-4Y3DP3RP.js → proper-lockfile-MVKMQGFK.js} +27 -27
- package/dist/proper-lockfile-MVKMQGFK.js.map +1 -0
- package/dist/rate-limit-BdNDZ3vt.d.ts +58 -0
- package/dist/registry-QHEZ3BWB.js +25 -0
- package/dist/router-RGZFX75G.js +0 -0
- package/dist/{routes-H4SPLFHJ.js → routes-3A4XGIEJ.js} +6 -6
- package/dist/{scan-C2BOKLSY.js → scan-NQFI5S7P.js} +2 -2
- package/dist/scan-NQFI5S7P.js.map +1 -0
- package/dist/schema-D3v8VB7o.d.ts +76 -0
- package/dist/{schema-VR6YNVLQ.js → schema-KZVOV333.js} +5 -3
- package/dist/schema-KZVOV333.js.map +1 -0
- package/dist/server/agent/index.d.ts +229 -0
- package/dist/server/agent/index.js +16 -0
- package/dist/server/agent/index.js.map +1 -0
- package/dist/server/auth/index.d.ts +46 -1
- package/dist/server/auth/index.js +10 -3
- package/dist/server/cost/index.d.ts +1 -3
- package/dist/server/cost/index.js +1 -1
- package/dist/server/cron/index.js +4 -2
- package/dist/server/define/index.d.ts +281 -0
- package/dist/server/define/index.js +26 -0
- package/dist/server/define/index.js.map +1 -0
- package/dist/server/http/index.d.ts +10 -0
- package/dist/server/http/index.js +74 -0
- package/dist/server/http/index.js.map +1 -0
- package/dist/server/index.d.ts +151 -1677
- package/dist/server/index.js +558 -1102
- package/dist/server/index.js.map +1 -1
- package/dist/server/jobs/index.d.ts +348 -2
- package/dist/server/jobs/index.js +5 -3
- package/dist/server/observability/index.d.ts +324 -0
- package/dist/server/observability/index.js +48 -0
- package/dist/server/observability/index.js.map +1 -0
- package/dist/server/plugins/index.d.ts +17 -0
- package/dist/server/plugins/index.js +17 -0
- package/dist/server/plugins/index.js.map +1 -0
- package/dist/server/rate-limit/index.d.ts +105 -0
- package/dist/server/rate-limit/index.js +25 -0
- package/dist/server/rate-limit/index.js.map +1 -0
- package/dist/server/realtime/index.d.ts +15 -0
- package/dist/server/realtime/index.js +8 -0
- package/dist/server/realtime/index.js.map +1 -0
- package/dist/server/scan/index.d.ts +106 -0
- package/dist/server/scan/index.js +43 -0
- package/dist/server/scan/index.js.map +1 -0
- package/dist/server/security/index.d.ts +193 -0
- package/dist/server/security/index.js +50 -0
- package/dist/server/security/index.js.map +1 -0
- package/dist/server/storage/index.d.ts +22 -0
- package/dist/server/storage/index.js +14 -0
- package/dist/server/storage/index.js.map +1 -0
- package/dist/server/webhook/index.d.ts +148 -0
- package/dist/server/webhook/index.js +19 -0
- package/dist/server/webhook/index.js.map +1 -0
- package/dist/server-error-to-envelope-NO4CCAFQ.js +8 -0
- package/dist/server-error-to-envelope-NO4CCAFQ.js.map +1 -0
- package/dist/server-error-to-envelope-UJK6OWDJ.js +134 -0
- package/dist/server-error-to-envelope-UJK6OWDJ.js.map +1 -0
- package/dist/server-routes-hmr-GZJGPWMS.js +0 -0
- package/dist/services-json-Z2QNGVPW.js +142 -0
- package/dist/services-json-Z2QNGVPW.js.map +1 -0
- package/dist/{services-typed-client-XWYYBWGW.js → services-typed-client-KK6RHRDG.js} +2 -2
- package/dist/{services-typed-client-ZX5JUHH3.js → services-typed-client-T7M5VPBP.js} +2 -2
- package/dist/{sqlite-vec-ARPNUBWT.js → sqlite-vec-54KB4RD3.js} +2 -2
- package/dist/sqlite-vec-54KB4RD3.js.map +1 -0
- package/dist/{start-HX3QUSOS.js → start-CGSZPBAG.js} +23 -23
- package/dist/start-CGSZPBAG.js.map +1 -0
- package/dist/{static-TZBVBDTB.js → static-QI5NQT2X.js} +6 -6
- package/dist/storage-manager-C4jsO0Tp.d.ts +89 -0
- package/dist/storage-manager-D5KBXXKB.js +0 -0
- package/dist/{storage-types-DtIVejC1.d.ts → storage-types-DsDTCPbp.d.ts} +1 -1
- package/dist/{theo-cloud-3K4DGB3S.js → theo-cloud-RSQCBNXL.js} +4 -4
- package/dist/theo-cloud-RSQCBNXL.js.map +1 -0
- package/dist/upgrade-readiness-GUJBCJIS.js +0 -0
- package/dist/{vercel-MWW24ABC.js → vercel-TKPZK62A.js} +3 -3
- package/dist/vite-plugin/index.d.ts +3 -1
- package/dist/vite-plugin/index.js +20 -8
- package/dist/{vite-plugin-IK3NOWXS.js → vite-plugin-CR4JDFUQ.js} +9 -9
- package/dist/vite-plugin-CR4JDFUQ.js.map +1 -0
- package/package.json +59 -10
- package/LICENSE +0 -201
- package/dist/build-HIGHJQQV.js.map +0 -1
- package/dist/chunk-5OFPQK6N.js.map +0 -1
- package/dist/chunk-64JI5LTO.js.map +0 -1
- package/dist/chunk-7TOMTMHT.js.map +0 -1
- package/dist/chunk-BIGBFZSU.js.map +0 -1
- package/dist/chunk-C52GSJPF.js.map +0 -1
- package/dist/chunk-CZM6WWWS.js +0 -2450
- package/dist/chunk-CZM6WWWS.js.map +0 -1
- package/dist/chunk-JAAHOGKI.js.map +0 -1
- package/dist/chunk-KJVEJILQ.js.map +0 -1
- package/dist/chunk-O4BLVPML.js.map +0 -1
- package/dist/chunk-O7335ZGH.js.map +0 -1
- package/dist/chunk-PB4GR7EP.js.map +0 -1
- package/dist/chunk-TPCT3MXV.js.map +0 -1
- package/dist/chunk-VRHXOKRP.js.map +0 -1
- package/dist/chunk-WZ7CPVQB.js.map +0 -1
- package/dist/chunk-X4JAX2U3.js.map +0 -1
- package/dist/chunk-XP7YXRHO.js.map +0 -1
- package/dist/chunk-YLBSSEHX.js.map +0 -1
- package/dist/chunk-ZOXNJV2O.js.map +0 -1
- package/dist/dev-emit-RBSFZZNO.js.map +0 -1
- package/dist/dispatcher-E6QV32BO.js.map +0 -1
- package/dist/dist-5V77Z223.js.map +0 -1
- package/dist/generate-DT4IIAHF.js.map +0 -1
- package/dist/index-li83Swxa.d.ts +0 -506
- package/dist/lib-NL5JXGEB.js.map +0 -1
- package/dist/proper-lockfile-4Y3DP3RP.js.map +0 -1
- package/dist/registry-4MZ26TZD.js +0 -25
- package/dist/schema-CjVly9c_.d.ts +0 -274
- package/dist/sqlite-vec-ARPNUBWT.js.map +0 -1
- package/dist/start-HX3QUSOS.js.map +0 -1
- package/dist/theo-cloud-3K4DGB3S.js.map +0 -1
- /package/dist/{actions-virtual-module-HWNTIEPI.js.map → actions-virtual-module-IY46EEEX.js.map} +0 -0
- /package/dist/{actions-virtual-module-SEIPACG5.js.map → actions-virtual-module-XNHDNCZ6.js.map} +0 -0
- /package/dist/{aws-lambda-XYCGZH3I.js.map → aws-lambda-GKSTX6HD.js.map} +0 -0
- /package/dist/{bun-K73TQEWC.js.map → bun-ISHSNFIO.js.map} +0 -0
- /package/dist/{check-PZR67OY5.js.map → check-NXYPLM6M.js.map} +0 -0
- /package/dist/{dispatcher-AQJGI3HU.js.map → chunk-E3JH6YUM.js.map} +0 -0
- /package/dist/{node-3APTLGWB.js.map → chunk-JHEAWR3L.js.map} +0 -0
- /package/dist/{chunk-ZJW5FFYJ.js.map → chunk-LC5PYNJP.js.map} +0 -0
- /package/dist/{chunk-2VQKDRVF.js.map → chunk-MR7OLIC6.js.map} +0 -0
- /package/dist/{scan-C2BOKLSY.js.map → chunk-NPMCKOX4.js.map} +0 -0
- /package/dist/{chunk-B6RP57M3.js.map → chunk-OLPB36O2.js.map} +0 -0
- /package/dist/{chunk-OXIQI2XZ.js.map → chunk-VBZCVFSA.js.map} +0 -0
- /package/dist/{chunk-TRH2L3FI.js.map → chunk-WEGALZEU.js.map} +0 -0
- /package/dist/{cloudflare-LCAOTRYZ.js.map → cloudflare-OJGJ7R2D.js.map} +0 -0
- /package/dist/{schema-VR6YNVLQ.js.map → cookies-MJKMGJ7N.js.map} +0 -0
- /package/dist/{deno-deploy-ZN4RE5HF.js.map → deno-deploy-BHWKFTOW.js.map} +0 -0
- /package/dist/{dev-266MVCVA.js.map → dev-MJVSRZGF.js.map} +0 -0
- /package/dist/{dev-emit-NO6OZJOQ.js.map → dev-emit-HHP2XJXE.js.map} +0 -0
- /package/dist/{vite-plugin-IK3NOWXS.js.map → dispatcher-63LBXYLE.js.map} +0 -0
- /package/dist/{info-FJ4NXKTB.js.map → info-47VB62MV.js.map} +0 -0
- /package/dist/{netlify-5VQ22Z2P.js.map → netlify-GSNSJGMN.js.map} +0 -0
- /package/dist/{openapi-FNVSWZOL.js.map → openapi-NFLSNNVQ.js.map} +0 -0
- /package/dist/{registry-4MZ26TZD.js.map → registry-QHEZ3BWB.js.map} +0 -0
- /package/dist/{routes-H4SPLFHJ.js.map → routes-3A4XGIEJ.js.map} +0 -0
- /package/dist/{services-typed-client-XWYYBWGW.js.map → services-typed-client-KK6RHRDG.js.map} +0 -0
- /package/dist/{services-typed-client-ZX5JUHH3.js.map → services-typed-client-T7M5VPBP.js.map} +0 -0
- /package/dist/{static-TZBVBDTB.js.map → static-QI5NQT2X.js.map} +0 -0
- /package/dist/{vercel-MWW24ABC.js.map → vercel-TKPZK62A.js.map} +0 -0
|
@@ -1,481 +1,770 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildServicesProxyConfig,
|
|
3
|
+
servicesConfigSchema
|
|
4
|
+
} from "./chunk-DNMSV3R3.js";
|
|
1
5
|
import {
|
|
2
6
|
broadcastToDevtools
|
|
3
7
|
} from "./chunk-7BIM27LV.js";
|
|
4
8
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} from "./chunk-
|
|
9
|
+
loadEnv,
|
|
10
|
+
resolveTransformer
|
|
11
|
+
} from "./chunk-PIVX3DYW.js";
|
|
12
|
+
import {
|
|
13
|
+
findSuggestion
|
|
14
|
+
} from "./chunk-5QW7IQQU.js";
|
|
15
|
+
import {
|
|
16
|
+
createPluginRunnerFromConfig
|
|
17
|
+
} from "./chunk-IAJ2JVEH.js";
|
|
18
|
+
import {
|
|
19
|
+
createRateLimiter
|
|
20
|
+
} from "./chunk-VMEWD57H.js";
|
|
21
|
+
import {
|
|
22
|
+
createViteLoader
|
|
23
|
+
} from "./chunk-JQSKBMXP.js";
|
|
24
|
+
import {
|
|
25
|
+
matchRoute,
|
|
26
|
+
scanServerRoutes,
|
|
27
|
+
scanWebSocketRoutes
|
|
28
|
+
} from "./chunk-OLPB36O2.js";
|
|
29
|
+
import {
|
|
30
|
+
scanServerActions
|
|
31
|
+
} from "./chunk-R7OC6UDK.js";
|
|
8
32
|
import {
|
|
9
|
-
BATCH_PATH,
|
|
10
33
|
CSP_REPORT_PATH,
|
|
11
34
|
CsrfReadinessStore,
|
|
12
|
-
TRACE_HEADER,
|
|
13
35
|
applySecurityHeaders,
|
|
36
|
+
handleCspReport,
|
|
37
|
+
handleCsrfReadiness
|
|
38
|
+
} from "./chunk-B3L3TJVF.js";
|
|
39
|
+
import {
|
|
40
|
+
generateNonce
|
|
41
|
+
} from "./chunk-C3ZZ56YZ.js";
|
|
42
|
+
import {
|
|
43
|
+
importUserModule
|
|
44
|
+
} from "./chunk-7MQOHNHE.js";
|
|
45
|
+
import {
|
|
46
|
+
BATCH_PATH,
|
|
47
|
+
TRACE_HEADER,
|
|
14
48
|
createCorsHandler,
|
|
15
|
-
createPluginRunnerFromConfig,
|
|
16
|
-
createRateLimiter,
|
|
17
|
-
createViteLoader,
|
|
18
49
|
executeAction,
|
|
19
50
|
executeRoute,
|
|
20
51
|
extractTraceId,
|
|
21
|
-
findSuggestion,
|
|
22
52
|
handleBatchRequest,
|
|
23
|
-
handleCspReport,
|
|
24
|
-
handleCsrfReadiness,
|
|
25
|
-
loadEnv,
|
|
26
|
-
logRequest,
|
|
27
|
-
resolveTransformer,
|
|
28
53
|
sendError
|
|
29
|
-
} from "./chunk-
|
|
30
|
-
import {
|
|
31
|
-
matchRoute,
|
|
32
|
-
scanServerRoutes,
|
|
33
|
-
scanWebSocketRoutes
|
|
34
|
-
} from "./chunk-B6RP57M3.js";
|
|
35
|
-
import {
|
|
36
|
-
scanServerActions
|
|
37
|
-
} from "./chunk-7TOMTMHT.js";
|
|
54
|
+
} from "./chunk-5Z2727GV.js";
|
|
38
55
|
import {
|
|
39
|
-
|
|
40
|
-
} from "./chunk-
|
|
56
|
+
logRequest
|
|
57
|
+
} from "./chunk-Z5IGLBWE.js";
|
|
41
58
|
|
|
42
59
|
// src/vite-plugin/index.ts
|
|
43
|
-
import { existsSync as
|
|
44
|
-
import { resolve as
|
|
60
|
+
import { existsSync as existsSync9 } from "fs";
|
|
61
|
+
import { resolve as resolve9, dirname as dirname4 } from "path";
|
|
45
62
|
import { fileURLToPath } from "url";
|
|
46
63
|
|
|
47
|
-
// src/
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
64
|
+
// src/vite-plugin/config-hook.ts
|
|
65
|
+
import { existsSync } from "fs";
|
|
66
|
+
import { resolve } from "path";
|
|
67
|
+
function runConfigHook(ctx) {
|
|
68
|
+
const ext = existsSync(resolve(ctx.theoSrcDir, "index.ts")) ? ".ts" : ".js";
|
|
69
|
+
const warmupClientFiles = [];
|
|
70
|
+
for (const candidate of ["app/layout.tsx", "app/page.tsx", "app/page.jsx"]) {
|
|
71
|
+
const full = resolve(ctx.projectRoot, candidate);
|
|
72
|
+
if (existsSync(full)) warmupClientFiles.push(`./${candidate}`);
|
|
73
|
+
}
|
|
74
|
+
const servicesProxy = ctx.services && Object.keys(ctx.services).length > 0 ? buildServicesProxyConfig(ctx.services) : void 0;
|
|
75
|
+
return {
|
|
76
|
+
envPrefix: "THEO_PUBLIC_",
|
|
77
|
+
optimizeDeps: {
|
|
78
|
+
...ctx.optimizeDepsInclude.length > 0 ? { include: ctx.optimizeDepsInclude } : {},
|
|
79
|
+
force: true
|
|
80
|
+
},
|
|
81
|
+
server: {
|
|
82
|
+
...warmupClientFiles.length > 0 ? { warmup: { clientFiles: warmupClientFiles } } : {},
|
|
83
|
+
...servicesProxy !== void 0 ? { proxy: servicesProxy } : {},
|
|
84
|
+
// Skip watching gitignored heavy dirs to avoid hitting ENOSPC
|
|
85
|
+
// (inotify watcher exhaustion).
|
|
86
|
+
watch: {
|
|
87
|
+
ignored: [
|
|
88
|
+
"**/referencias/**",
|
|
89
|
+
"**/.theokit/**",
|
|
90
|
+
"**/.theo/**",
|
|
91
|
+
"**/dist/**",
|
|
92
|
+
"**/node_modules/**"
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
resolve: {
|
|
97
|
+
alias: [
|
|
98
|
+
// Order matters: most-specific first so `theokit/X` doesn't
|
|
99
|
+
// get matched by the bare `theokit` alias.
|
|
100
|
+
{ find: "theokit/server", replacement: resolve(ctx.theoSrcDir, `server/index${ext}`) },
|
|
101
|
+
{ find: "theokit/client", replacement: resolve(ctx.theoSrcDir, `client/index${ext}`) },
|
|
102
|
+
{
|
|
103
|
+
// T2.1 (M5 lonely folders) moved the source from
|
|
104
|
+
// `react-query/index.ts` into `client/react-query.ts` (sibling of
|
|
105
|
+
// `client/index.ts`). The package.json export `./react-query`
|
|
106
|
+
// still points at the build artifact `./dist/react-query/index.{js,d.ts}`,
|
|
107
|
+
// but the dev-time source alias must follow the new location.
|
|
108
|
+
find: "theokit/react-query",
|
|
109
|
+
replacement: resolve(ctx.theoSrcDir, `client/react-query${ext}`)
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
find: "theokit/vite-plugin",
|
|
113
|
+
replacement: resolve(ctx.theoSrcDir, `vite-plugin/index${ext}`)
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
find: "theokit/adapters/web-shim",
|
|
117
|
+
replacement: resolve(ctx.theoSrcDir, `adapters/web-shim${ext}`)
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
find: "theokit/adapters/ws-shim",
|
|
121
|
+
replacement: resolve(ctx.theoSrcDir, `adapters/ws-shim${ext}`)
|
|
122
|
+
},
|
|
123
|
+
// T1.2 — devtools entry (DEV only; tree-shaken in build).
|
|
124
|
+
// Source layout: src/devtools/dom/entry.tsx (has /dom/ segment)
|
|
125
|
+
// Dist layout: dist/devtools/entry.js (tsup flattens /dom/)
|
|
126
|
+
{
|
|
127
|
+
find: "theokit/devtools/entry",
|
|
128
|
+
replacement: resolve(
|
|
129
|
+
ctx.theoSrcDir,
|
|
130
|
+
ext === ".ts" ? "devtools/dom/entry.tsx" : "devtools/entry.js"
|
|
131
|
+
)
|
|
132
|
+
},
|
|
133
|
+
{ find: "theokit", replacement: resolve(ctx.theoSrcDir, `index${ext}`) }
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
};
|
|
79
137
|
}
|
|
80
138
|
|
|
81
|
-
// src/
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
`
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
139
|
+
// src/config/load-config.ts
|
|
140
|
+
import { existsSync as existsSync2 } from "fs";
|
|
141
|
+
import { resolve as resolve2 } from "path";
|
|
142
|
+
|
|
143
|
+
// src/config/errors.ts
|
|
144
|
+
var TheoConfigError = class extends Error {
|
|
145
|
+
issues;
|
|
146
|
+
configPath;
|
|
147
|
+
constructor(issues, configPath) {
|
|
148
|
+
const issueLines = issues.map((i) => ` - ${i.field}: ${i.message}`).join("\n");
|
|
149
|
+
super(
|
|
150
|
+
`Invalid theo.config.ts
|
|
151
|
+
|
|
152
|
+
File: ${configPath}
|
|
153
|
+
|
|
154
|
+
` + (issueLines ? ` Issues:
|
|
155
|
+
${issueLines}
|
|
156
|
+
` : "")
|
|
157
|
+
);
|
|
158
|
+
this.name = "TheoConfigError";
|
|
159
|
+
this.issues = issues;
|
|
160
|
+
this.configPath = configPath;
|
|
98
161
|
}
|
|
99
|
-
|
|
100
|
-
React.createElement(StaticRouterProvider, { router, context, hydrate: false })
|
|
101
|
-
)`;
|
|
102
|
-
}
|
|
103
|
-
function hydrationDataExtractSnippet() {
|
|
104
|
-
return [
|
|
105
|
-
` const hydrationData = {`,
|
|
106
|
-
` loaderData: context.loaderData,`,
|
|
107
|
-
` actionData: context.actionData,`,
|
|
108
|
-
` errors: context.errors,`,
|
|
109
|
-
` }`
|
|
110
|
-
].join("\n");
|
|
111
|
-
}
|
|
112
|
-
function generateSingleShotEntry(options) {
|
|
113
|
-
const theoUiImport = options.theoUi ? `import { TheoUIProvider } from '@theokit/ui'
|
|
114
|
-
` : "";
|
|
115
|
-
const appTree = buildAppTreeJs(options);
|
|
116
|
-
return [
|
|
117
|
-
`import React, { Suspense } from 'react'`,
|
|
118
|
-
`import { renderToPipeableStream } from 'react-dom/server'`,
|
|
119
|
-
`import { createStaticHandler, createStaticRouter, StaticRouterProvider } from 'react-router'`,
|
|
120
|
-
`import { PassThrough } from 'node:stream'`,
|
|
121
|
-
`import { routes } from '/@theo/route-manifest'`,
|
|
122
|
-
theoUiImport,
|
|
123
|
-
`export async function render(url, options = {}) {`,
|
|
124
|
-
` const handler = createStaticHandler(routes)`,
|
|
125
|
-
` const request = new Request('http://localhost' + url)`,
|
|
126
|
-
` const context = await handler.query(request)`,
|
|
127
|
-
``,
|
|
128
|
-
` if (context instanceof Response) {`,
|
|
129
|
-
` return { redirect: context }`,
|
|
130
|
-
` }`,
|
|
131
|
-
``,
|
|
132
|
-
` const router = createStaticRouter(handler.dataRoutes, context)`,
|
|
133
|
-
` const app = ${appTree}`,
|
|
134
|
-
``,
|
|
135
|
-
hydrationDataExtractSnippet(),
|
|
136
|
-
``,
|
|
137
|
-
` return new Promise((resolve, reject) => {`,
|
|
138
|
-
` let html = ''`,
|
|
139
|
-
` let piped = false`,
|
|
140
|
-
` const passthrough = new PassThrough()`,
|
|
141
|
-
` passthrough.on('data', (chunk) => { html += chunk.toString() })`,
|
|
142
|
-
` passthrough.on('end', () => { resolve({ html, hydrationData }) })`,
|
|
143
|
-
` passthrough.on('error', reject)`,
|
|
144
|
-
``,
|
|
145
|
-
` // Pipe on onShellReady (Next.js pattern). Calling pipe() twice`,
|
|
146
|
-
` // throws "React currently only supports piping to one writable`,
|
|
147
|
-
` // stream". The \`piped\` flag is a belt-and-suspenders guard if`,
|
|
148
|
-
` // onShellReady fires unexpectedly more than once.`,
|
|
149
|
-
` // Forward options.nonce to React so every <script> tag React`,
|
|
150
|
-
` // emits (Suspense boundary scripts) carries the nonce attribute.`,
|
|
151
|
-
` const { pipe } = renderToPipeableStream(app, {`,
|
|
152
|
-
` nonce: options.nonce,`,
|
|
153
|
-
` onShellReady() { if (!piped) { piped = true; pipe(passthrough) } },`,
|
|
154
|
-
` onShellError(err) { reject(err) },`,
|
|
155
|
-
` onError(err) { console.error('[SSR Error]', err) },`,
|
|
156
|
-
` })`,
|
|
157
|
-
` })`,
|
|
158
|
-
`}`
|
|
159
|
-
].join("\n");
|
|
160
|
-
}
|
|
161
|
-
function streamingWebRenderer(appTree) {
|
|
162
|
-
return [
|
|
163
|
-
`// T2.3 \u2014 Web Standards streaming entry for edge runtimes (Cloudflare,`,
|
|
164
|
-
`// Bun, Deno, Vercel Edge). Uses renderToReadableStream and returns a`,
|
|
165
|
-
`// Response with the stream as body. Honors request.signal for client`,
|
|
166
|
-
`// disconnect cleanup.`,
|
|
167
|
-
`export async function renderStreamingWeb(request, options = {}) {`,
|
|
168
|
-
` const handler = createStaticHandler(routes)`,
|
|
169
|
-
` const url = new URL(request.url)`,
|
|
170
|
-
` const context = await handler.query(request)`,
|
|
171
|
-
``,
|
|
172
|
-
` if (context instanceof Response) {`,
|
|
173
|
-
` return context`,
|
|
174
|
-
` }`,
|
|
175
|
-
``,
|
|
176
|
-
` const router = createStaticRouter(handler.dataRoutes, context)`,
|
|
177
|
-
` const app = ${appTree}`,
|
|
178
|
-
``,
|
|
179
|
-
` const stream = await renderToReadableStream(app, {`,
|
|
180
|
-
` signal: request.signal,`,
|
|
181
|
-
` nonce: options.nonce,`,
|
|
182
|
-
` onError(err) { console.error('[SSR Web Stream Error]', err) },`,
|
|
183
|
-
` })`,
|
|
184
|
-
` return new Response(stream, {`,
|
|
185
|
-
` status: 200,`,
|
|
186
|
-
` headers: {`,
|
|
187
|
-
` 'Content-Type': 'text/html; charset=utf-8',`,
|
|
188
|
-
` 'Transfer-Encoding': 'chunked',`,
|
|
189
|
-
` },`,
|
|
190
|
-
` })`,
|
|
191
|
-
`}`
|
|
192
|
-
];
|
|
193
|
-
}
|
|
194
|
-
function streamingNodeRenderer(appTree) {
|
|
195
|
-
return [
|
|
196
|
-
`// T6.1 \u2014 Node streaming SSR entry (opt-in via theo.config.ts > ssrStreaming: true)`,
|
|
197
|
-
`// Flushes the shell as soon as it's ready, then streams Suspense boundaries.`,
|
|
198
|
-
`// EC-11: respects request.signal for client-disconnect cleanup.`,
|
|
199
|
-
`export async function renderStreaming(url, response, options = {}) {`,
|
|
200
|
-
` const handler = createStaticHandler(routes)`,
|
|
201
|
-
` const request = new Request('http://localhost' + url, { signal: options.signal })`,
|
|
202
|
-
` const context = await handler.query(request)`,
|
|
203
|
-
``,
|
|
204
|
-
` if (context instanceof Response) {`,
|
|
205
|
-
` return { redirect: context }`,
|
|
206
|
-
` }`,
|
|
207
|
-
``,
|
|
208
|
-
` const router = createStaticRouter(handler.dataRoutes, context)`,
|
|
209
|
-
` const app = ${appTree}`,
|
|
210
|
-
``,
|
|
211
|
-
` return new Promise((resolve, reject) => {`,
|
|
212
|
-
` let didError = false`,
|
|
213
|
-
` const stream = renderToPipeableStream(app, {`,
|
|
214
|
-
` nonce: options.nonce,`,
|
|
215
|
-
` onShellReady() {`,
|
|
216
|
-
` response.statusCode = didError ? 500 : 200`,
|
|
217
|
-
` response.setHeader('Content-Type', 'text/html; charset=utf-8')`,
|
|
218
|
-
` response.setHeader('Transfer-Encoding', 'chunked')`,
|
|
219
|
-
` stream.pipe(response)`,
|
|
220
|
-
` resolve({ streaming: true })`,
|
|
221
|
-
` },`,
|
|
222
|
-
` onShellError(err) { reject(err) },`,
|
|
223
|
-
` onError(err) {`,
|
|
224
|
-
` didError = true`,
|
|
225
|
-
` console.error('[SSR Stream Error]', err)`,
|
|
226
|
-
` },`,
|
|
227
|
-
` })`,
|
|
228
|
-
``,
|
|
229
|
-
` // EC-11: client disconnect cleanup`,
|
|
230
|
-
` if (options.signal) {`,
|
|
231
|
-
` options.signal.addEventListener('abort', () => { stream.abort() })`,
|
|
232
|
-
` }`,
|
|
233
|
-
` })`,
|
|
234
|
-
`}`
|
|
235
|
-
];
|
|
236
|
-
}
|
|
237
|
-
function backCompatRenderer(appTree) {
|
|
238
|
-
return [
|
|
239
|
-
`// Backward compatibility: keep the single-shot render export available so`,
|
|
240
|
-
`// callers that always used 'render()' don't break when streaming is on.`,
|
|
241
|
-
`export async function render(url, options = {}) {`,
|
|
242
|
-
` const handler = createStaticHandler(routes)`,
|
|
243
|
-
` const request = new Request('http://localhost' + url)`,
|
|
244
|
-
` const context = await handler.query(request)`,
|
|
245
|
-
``,
|
|
246
|
-
` if (context instanceof Response) {`,
|
|
247
|
-
` return { redirect: context }`,
|
|
248
|
-
` }`,
|
|
249
|
-
``,
|
|
250
|
-
` const router = createStaticRouter(handler.dataRoutes, context)`,
|
|
251
|
-
` const app = ${appTree}`,
|
|
252
|
-
``,
|
|
253
|
-
hydrationDataExtractSnippet(),
|
|
254
|
-
``,
|
|
255
|
-
` const { PassThrough } = await import('node:stream')`,
|
|
256
|
-
` return new Promise((resolve, reject) => {`,
|
|
257
|
-
` let html = ''`,
|
|
258
|
-
` let piped = false`,
|
|
259
|
-
` const passthrough = new PassThrough()`,
|
|
260
|
-
` passthrough.on('data', (chunk) => { html += chunk.toString() })`,
|
|
261
|
-
` passthrough.on('end', () => { resolve({ html, hydrationData }) })`,
|
|
262
|
-
` passthrough.on('error', reject)`,
|
|
263
|
-
``,
|
|
264
|
-
` // Pipe on onShellReady (Next.js pattern). nonce forwarded.`,
|
|
265
|
-
` const { pipe } = renderToPipeableStream(app, {`,
|
|
266
|
-
` nonce: options.nonce,`,
|
|
267
|
-
` onShellReady() { if (!piped) { piped = true; pipe(passthrough) } },`,
|
|
268
|
-
` onShellError(err) { reject(err) },`,
|
|
269
|
-
` onError(err) { console.error('[SSR Error]', err) },`,
|
|
270
|
-
` })`,
|
|
271
|
-
` })`,
|
|
272
|
-
`}`
|
|
273
|
-
];
|
|
274
|
-
}
|
|
275
|
-
function generateStreamingEntry(options) {
|
|
276
|
-
const theoUiImport = options.theoUi ? `import { TheoUIProvider } from '@theokit/ui'
|
|
277
|
-
` : "";
|
|
278
|
-
const appTree = buildAppTreeJs(options);
|
|
279
|
-
return [
|
|
280
|
-
`import React, { Suspense } from 'react'`,
|
|
281
|
-
`import { renderToPipeableStream, renderToReadableStream } from 'react-dom/server'`,
|
|
282
|
-
`import { createStaticHandler, createStaticRouter, StaticRouterProvider } from 'react-router'`,
|
|
283
|
-
`import { routes } from '/@theo/route-manifest'`,
|
|
284
|
-
theoUiImport,
|
|
285
|
-
``,
|
|
286
|
-
...streamingWebRenderer(appTree),
|
|
287
|
-
``,
|
|
288
|
-
...streamingNodeRenderer(appTree),
|
|
289
|
-
``,
|
|
290
|
-
...backCompatRenderer(appTree)
|
|
291
|
-
].join("\n");
|
|
292
|
-
}
|
|
162
|
+
};
|
|
293
163
|
|
|
294
|
-
// src/
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
164
|
+
// src/config/schema.ts
|
|
165
|
+
import { z as z8 } from "zod";
|
|
166
|
+
|
|
167
|
+
// src/config/schemas/header-safe.ts
|
|
168
|
+
import { z } from "zod";
|
|
169
|
+
var headerSafeString = z.string().refine((s) => !/[\r\n]/.test(s), { message: "Header value must not contain CR/LF" });
|
|
170
|
+
|
|
171
|
+
// src/config/schemas/rate-limit.ts
|
|
172
|
+
import { z as z2 } from "zod";
|
|
173
|
+
var baseRateLimitSchema = z2.object({
|
|
174
|
+
windowMs: z2.number().min(1),
|
|
175
|
+
max: z2.number().int().min(1)
|
|
176
|
+
});
|
|
177
|
+
var rateLimitSchema = z2.union([
|
|
178
|
+
baseRateLimitSchema,
|
|
179
|
+
z2.object({
|
|
180
|
+
default: baseRateLimitSchema.optional(),
|
|
181
|
+
routes: z2.record(z2.string(), baseRateLimitSchema).optional(),
|
|
182
|
+
keyBy: z2.union([
|
|
183
|
+
z2.enum(["ip", "session", "user"]),
|
|
184
|
+
z2.function({ input: z2.tuple([z2.unknown()]), output: z2.string() })
|
|
185
|
+
]).optional(),
|
|
186
|
+
cookieName: z2.string().min(1).optional()
|
|
187
|
+
})
|
|
188
|
+
]);
|
|
189
|
+
|
|
190
|
+
// src/config/schemas/upload.ts
|
|
191
|
+
import { z as z3 } from "zod";
|
|
192
|
+
var uploadSchema = z3.object({
|
|
193
|
+
maxFileSize: z3.number().min(1).default(10 * 1024 * 1024),
|
|
194
|
+
// 10MB
|
|
195
|
+
maxFiles: z3.number().int().min(1).default(10),
|
|
196
|
+
maxFieldSize: z3.number().min(1).default(1 * 1024 * 1024)
|
|
197
|
+
// 1MB
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// src/config/schemas/logging.ts
|
|
201
|
+
import { z as z4 } from "zod";
|
|
202
|
+
var loggingSchema = z4.object({
|
|
203
|
+
level: z4.enum(["debug", "info", "warn", "error", "silent"]).default("info")
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// src/config/schemas/cache.ts
|
|
207
|
+
import { z as z5 } from "zod";
|
|
208
|
+
var routeRuleSchema = z5.object({
|
|
209
|
+
maxAge: z5.number().nonnegative().finite().optional(),
|
|
210
|
+
swr: z5.number().nonnegative().finite().optional(),
|
|
211
|
+
tags: z5.array(z5.string()).optional()
|
|
212
|
+
});
|
|
213
|
+
var cacheSchema = z5.object({
|
|
214
|
+
enabled: z5.boolean().default(true),
|
|
215
|
+
/** 'memory' uses InMemoryCacheAdapter; otherwise pass a custom adapter instance. */
|
|
216
|
+
storage: z5.union([z5.literal("memory"), z5.custom()]).default("memory"),
|
|
217
|
+
maxEntries: z5.number().int().positive().default(1e3),
|
|
218
|
+
defaults: z5.object({
|
|
219
|
+
maxAge: z5.number().nonnegative().finite().default(1),
|
|
220
|
+
swr: z5.number().nonnegative().finite().optional(),
|
|
221
|
+
cacheErrors: z5.boolean().default(false)
|
|
222
|
+
}).default({ maxAge: 1, cacheErrors: false }),
|
|
223
|
+
keyDerivation: z5.object({
|
|
224
|
+
excludeQuery: z5.array(z5.string()).optional(),
|
|
225
|
+
sortQuery: z5.boolean().default(true),
|
|
226
|
+
lowercaseHost: z5.boolean().default(true)
|
|
227
|
+
}).default({ sortQuery: true, lowercaseHost: true }),
|
|
228
|
+
routeRules: z5.record(z5.string(), routeRuleSchema).optional()
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// src/config/schemas/security.ts
|
|
232
|
+
import { z as z6 } from "zod";
|
|
233
|
+
var securityHeadersSchema = z6.object({
|
|
234
|
+
csp: z6.union([headerSafeString, z6.literal(false)]).optional(),
|
|
235
|
+
// T6.1 — default flipped from 'report-only' to 'enforce' for 0.3.0.
|
|
236
|
+
// Users who want the old behaviour set `cspMode: 'report-only'`
|
|
237
|
+
// explicitly. See docs/migrating/0.2-to-0.3.md.
|
|
238
|
+
cspMode: z6.enum(["enforce", "report-only", "off"]).default("enforce"),
|
|
239
|
+
hsts: z6.union([headerSafeString, z6.literal(false)]).optional(),
|
|
240
|
+
frameOptions: z6.enum(["DENY", "SAMEORIGIN"]).default("DENY"),
|
|
241
|
+
contentTypeOptions: z6.literal("nosniff").default("nosniff"),
|
|
242
|
+
referrerPolicy: headerSafeString.default("strict-origin-when-cross-origin"),
|
|
243
|
+
/**
|
|
244
|
+
* T1.1 — Permissions-Policy directive string. EC-3-refined: rejects CR/LF.
|
|
245
|
+
* Pass `false` to suppress the header.
|
|
246
|
+
*/
|
|
247
|
+
permissionsPolicy: z6.union([headerSafeString, z6.literal(false)]).optional()
|
|
248
|
+
});
|
|
249
|
+
var disallowedConfigSchema = z6.object({
|
|
250
|
+
routes: z6.array(z6.union([z6.string(), z6.instanceof(RegExp)])),
|
|
251
|
+
behavior: z6.enum(["warn", "raise"]).default("raise")
|
|
252
|
+
});
|
|
253
|
+
var corsSchema = z6.object({
|
|
254
|
+
origins: z6.union([
|
|
255
|
+
z6.literal("*"),
|
|
256
|
+
headerSafeString,
|
|
257
|
+
z6.instanceof(RegExp),
|
|
258
|
+
z6.array(z6.union([headerSafeString, z6.instanceof(RegExp)])),
|
|
259
|
+
z6.function({ input: z6.tuple([z6.string()]), output: z6.boolean() })
|
|
260
|
+
]),
|
|
261
|
+
methods: z6.array(z6.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"])).optional(),
|
|
262
|
+
allowedHeaders: z6.array(headerSafeString).optional(),
|
|
263
|
+
exposedHeaders: z6.array(headerSafeString).optional(),
|
|
264
|
+
credentials: z6.boolean().default(false),
|
|
265
|
+
maxAge: z6.number().int().min(0).max(86400).default(600)
|
|
266
|
+
}).refine((c) => !(c.origins === "*" && c.credentials), {
|
|
267
|
+
message: 'CORS spec forbids origins:"*" with credentials:true \u2014 browsers ignore the wildcard'
|
|
268
|
+
});
|
|
269
|
+
var securitySchema = z6.object({
|
|
270
|
+
// T6.1 — default flipped from 'warn' to 'strict' for 0.3.0. Apps that
|
|
271
|
+
// grep their warn-mode logs from 0.2.x already know which endpoints
|
|
272
|
+
// break; opt back into 'warn' globally OR use disallowedRoutes for
|
|
273
|
+
// surgical migration. See docs/migrating/0.2-to-0.3.md.
|
|
274
|
+
csrf: z6.enum(["off", "warn", "strict"]).default("strict"),
|
|
275
|
+
headers: securityHeadersSchema.optional(),
|
|
276
|
+
/** T5.1 — per-route escalation (Rails disallowed_warnings pattern). */
|
|
277
|
+
disallowed: disallowedConfigSchema.optional(),
|
|
278
|
+
/** T1.2 — Cross-Origin Resource Sharing. Single global config; runs first in pipeline. */
|
|
279
|
+
cors: corsSchema.optional()
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// src/config/schemas/storage.ts
|
|
283
|
+
import { z as z7 } from "zod";
|
|
284
|
+
var tlsConfigSchema = z7.object({
|
|
285
|
+
rejectUnauthorized: z7.boolean().optional(),
|
|
286
|
+
caCert: z7.string().optional(),
|
|
287
|
+
clientCert: z7.string().optional(),
|
|
288
|
+
clientKey: z7.string().optional()
|
|
289
|
+
});
|
|
290
|
+
var serverConfigSchema = z7.object({
|
|
291
|
+
host: z7.string().min(1),
|
|
292
|
+
port: z7.number().int().positive().max(65535).optional(),
|
|
293
|
+
user: z7.string().min(1),
|
|
294
|
+
/** Password may be the empty string (some providers permit it). */
|
|
295
|
+
password: z7.string(),
|
|
296
|
+
tls: tlsConfigSchema.optional()
|
|
297
|
+
});
|
|
298
|
+
var postgresPoolConfigSchema = z7.object({
|
|
299
|
+
min: z7.number().int().nonnegative().optional(),
|
|
300
|
+
max: z7.number().int().positive().optional(),
|
|
301
|
+
connectionTimeoutMillis: z7.number().int().positive().optional(),
|
|
302
|
+
idleTimeoutMillis: z7.number().int().nonnegative().optional()
|
|
303
|
+
});
|
|
304
|
+
var postgresDatabaseConfigSchema = z7.object({
|
|
305
|
+
/** References a key in `storage.servers`. Resolved at `usePostgres()` call time. */
|
|
306
|
+
server: z7.string().min(1),
|
|
307
|
+
database: z7.string().min(1),
|
|
308
|
+
pool: postgresPoolConfigSchema.optional()
|
|
309
|
+
});
|
|
310
|
+
var redisServerConfigSchema = serverConfigSchema.extend({
|
|
311
|
+
db: z7.number().int().nonnegative().optional(),
|
|
312
|
+
maxRetriesPerRequest: z7.number().int().nonnegative().optional()
|
|
313
|
+
});
|
|
314
|
+
var storageSchema = z7.object({
|
|
315
|
+
servers: z7.record(z7.string(), serverConfigSchema).optional(),
|
|
316
|
+
databases: z7.record(z7.string(), postgresDatabaseConfigSchema).optional(),
|
|
317
|
+
redis: z7.record(z7.string(), redisServerConfigSchema).optional()
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// src/config/schema.ts
|
|
321
|
+
var theoConfigSchema = z8.object({
|
|
322
|
+
/**
|
|
323
|
+
* Plan v1.2 T2.1 — project identifier propagated into
|
|
324
|
+
* `.theo/services.json` v2 `project` field. DNS-1123 compatible (drives
|
|
325
|
+
* Gitea repo + ArgoCD App naming on TheoCloud). When omitted, the build
|
|
326
|
+
* emits services.json v1 with the legacy `services-bundle` fallback
|
|
327
|
+
* (ADR D10) and logs a deprecation warning.
|
|
328
|
+
*/
|
|
329
|
+
name: z8.string().max(63).refine(
|
|
330
|
+
// DNS-1123 equivalent expressed as explicit single-char anchors so
|
|
331
|
+
// security/detect-unsafe-regex stays clean (no backtracking).
|
|
332
|
+
(value) => value.length > 0 && /^[a-z0-9-]+$/u.test(value) && !value.startsWith("-") && !value.endsWith("-"),
|
|
333
|
+
"name must match DNS-1123 (lowercase alphanumeric+hyphens, 1-63 chars, no leading/trailing hyphen)"
|
|
334
|
+
).optional(),
|
|
335
|
+
appDir: z8.string().default("app"),
|
|
336
|
+
serverDir: z8.string().default("server"),
|
|
337
|
+
/**
|
|
338
|
+
* T2.2 / EC-4 — Build output directory. Must be a relative path inside
|
|
339
|
+
* the project root. Refused absolute or parent-relative paths to prevent
|
|
340
|
+
* `cleanOutDir` from wiping arbitrary locations (defense-in-depth on
|
|
341
|
+
* top of cleanOutDir's runtime EC-3 guard).
|
|
342
|
+
*/
|
|
343
|
+
distDir: z8.string().default(".theo").refine(
|
|
344
|
+
(d) => !/^([A-Za-z]:)?[/\\]/.test(d) && !d.startsWith(".."),
|
|
345
|
+
'distDir must be a relative path inside the project root (e.g., ".theo")'
|
|
346
|
+
),
|
|
347
|
+
/**
|
|
348
|
+
* Agent runtime configuration.
|
|
349
|
+
*
|
|
350
|
+
* - `maxRegistries` (T2.3, legacy): dev-mode cleanup of stale
|
|
351
|
+
* `.theokit/agents/<id>/` dirs by mtime. Now superseded by SDK's
|
|
352
|
+
* native registry GC but kept for backward-compat.
|
|
353
|
+
* - `registry` (Phase 6, Production-Readiness #2): forwarded to
|
|
354
|
+
* `Agent.registry.configure()` lazily on first request.
|
|
355
|
+
* * `maxAgents` — LRU cap. MUST be ≥ max-concurrent-active-conversations
|
|
356
|
+
* (EC-17 DOCUMENT): with maxAgents:1 and 2 concurrent chats, LRU evicts
|
|
357
|
+
* mid-stream → SDK aborts with code:'aborted'. Default 100 covers
|
|
358
|
+
* indie/small-team; tune up for high-traffic.
|
|
359
|
+
* * `idleTimeoutMs` — auto-evict idle agents (default 30 min). 0 disables
|
|
360
|
+
* idle eviction (LRU only).
|
|
361
|
+
*/
|
|
362
|
+
agents: z8.object({
|
|
363
|
+
maxRegistries: z8.number().int().positive().default(100),
|
|
364
|
+
registry: z8.object({
|
|
365
|
+
/** LRU cap — MUST be ≥ max-concurrent-active-conversations. */
|
|
366
|
+
maxAgents: z8.number().int().positive().max(1e4).default(100),
|
|
367
|
+
/** Idle eviction window in ms. 0 disables idle eviction (LRU only). */
|
|
368
|
+
idleTimeoutMs: z8.number().int().nonnegative().default(30 * 6e4)
|
|
369
|
+
}).optional()
|
|
370
|
+
}).optional(),
|
|
371
|
+
port: z8.number().int().min(1).max(65535).default(3e3),
|
|
372
|
+
ssr: z8.boolean().default(false),
|
|
373
|
+
/** When true (and ssr === true), use renderToPipeableStream with progressive
|
|
374
|
+
* shell flush instead of single-shot renderToString. Opt-in for streaming
|
|
375
|
+
* SSR; default false preserves the current behavior. */
|
|
376
|
+
ssrStreaming: z8.boolean().default(false),
|
|
377
|
+
rateLimit: rateLimitSchema.optional(),
|
|
378
|
+
upload: uploadSchema.optional(),
|
|
379
|
+
logging: loggingSchema.optional(),
|
|
380
|
+
security: securitySchema.optional(),
|
|
381
|
+
serialization: z8.enum(["json", "superjson"]).default("json"),
|
|
382
|
+
// Plugins are validated structurally at runtime by createPluginRunnerFromConfig.
|
|
383
|
+
// Zod only checks the shape minimally (must be array). Type-level safety is
|
|
384
|
+
// provided through defineConfig at the user surface.
|
|
385
|
+
plugins: z8.array(z8.unknown()).optional(),
|
|
386
|
+
/** Enable client-side batching of theoFetch calls and the
|
|
387
|
+
* /api/__theo_batch__ server endpoint. */
|
|
388
|
+
batching: z8.union([z8.boolean(), z8.object({ max: z8.number().int().positive().optional() })]).optional(),
|
|
389
|
+
/** T4.1 — Audit log. When `logger` is provided, framework events
|
|
390
|
+
* (csrf.warn, rate-limit.exceeded, session.rotated, csp.violation) are
|
|
391
|
+
* emitted to it. Default: noop. */
|
|
392
|
+
audit: z8.object({
|
|
393
|
+
logger: z8.unknown().optional()
|
|
394
|
+
}).optional(),
|
|
395
|
+
/** TheoUI auto-wire (T2.1). `false` = opt-out; object = explicit theme/fonts;
|
|
396
|
+
* undefined = enabled when @theokit/ui is detected in node_modules. */
|
|
397
|
+
ui: z8.union([
|
|
398
|
+
z8.literal(false),
|
|
399
|
+
z8.object({
|
|
400
|
+
theme: z8.enum(["violet-forge", "noir", "paper"]).optional(),
|
|
401
|
+
fonts: z8.enum(["bundled", "cdn"]).optional()
|
|
402
|
+
})
|
|
403
|
+
]).optional(),
|
|
404
|
+
/**
|
|
405
|
+
* Cache subsystem (caching-and-revalidation-plan).
|
|
406
|
+
* Default `undefined` keeps caching disabled (backward compatible).
|
|
407
|
+
* Pass `cache: {}` to opt in with defaults.
|
|
408
|
+
*/
|
|
409
|
+
cache: cacheSchema.optional(),
|
|
410
|
+
/**
|
|
411
|
+
* Devtools overlay (Phase 0.4.0+ — see docs/plans/devtools-plan.md).
|
|
412
|
+
*
|
|
413
|
+
* - `undefined` (default): devtools auto-injects in `pnpm dev`, NEVER in `vite build`.
|
|
414
|
+
* - `false`: devtools disabled entirely (Vite plugin skips injection even in dev).
|
|
415
|
+
* - `{ ... }`: devtools enabled with explicit defaults (position, theme).
|
|
416
|
+
*
|
|
417
|
+
* Tree-shaken to noop in prod via the dual-export pattern in
|
|
418
|
+
* `packages/theo/src/devtools/index.ts` (EC-17 positive prod check).
|
|
419
|
+
*/
|
|
420
|
+
devtools: z8.union([
|
|
421
|
+
z8.literal(false),
|
|
422
|
+
z8.object({
|
|
423
|
+
position: z8.enum(["top-left", "top-right", "bottom-left", "bottom-right"]).optional(),
|
|
424
|
+
theme: z8.enum(["light", "dark", "system"]).optional()
|
|
425
|
+
})
|
|
426
|
+
]).optional(),
|
|
427
|
+
/**
|
|
428
|
+
* Informative list of deploy adapters the app supports. Does NOT
|
|
429
|
+
* trigger per-build translations — only the `--target` CLI flag does
|
|
430
|
+
* (EC-201 / ADR D2). If `config.adapters` includes a target NOT
|
|
431
|
+
* matching `--target`, `theokit build` emits a cross-reference note.
|
|
432
|
+
*/
|
|
433
|
+
adapters: z8.array(z8.string()).optional(),
|
|
434
|
+
/**
|
|
435
|
+
* Extra package names to pre-bundle via Vite's `optimizeDeps.include`.
|
|
436
|
+
*
|
|
437
|
+
* Framework auto-includes `@theokit/ui` and `lucide-react` when present.
|
|
438
|
+
* Apps adding plugin peer-deps that rely on a runtime `import('<pkg>')`
|
|
439
|
+
* (dynamic specifier) — e.g. `@theokit/plugin-canvas` consumers
|
|
440
|
+
* installing `mermaid` for Mermaid SVG rendering — must declare those
|
|
441
|
+
* here so Vite can resolve the literal in dev mode without
|
|
442
|
+
* "Failed to resolve module specifier" errors. Production builds use
|
|
443
|
+
* tsup/esbuild bundling and are not affected.
|
|
444
|
+
*
|
|
445
|
+
* Example:
|
|
446
|
+
* viteOptimizeDeps: ['mermaid']
|
|
447
|
+
*/
|
|
448
|
+
viteOptimizeDeps: z8.array(z8.string()).optional(),
|
|
449
|
+
/**
|
|
450
|
+
* Jobs backend (ADR D3 + T2.1). When configured, every request gets
|
|
451
|
+
* `ctx.queue.enqueue` auto-wired via the outbox lifecycle. Pass a
|
|
452
|
+
* `JobBackend` instance (InMemoryJobBackend, PostgresJobBackend, etc.).
|
|
453
|
+
*/
|
|
454
|
+
jobs: z8.object({
|
|
455
|
+
backend: z8.custom((v) => typeof v === "object" && v !== null)
|
|
456
|
+
}).optional(),
|
|
457
|
+
/**
|
|
458
|
+
* StorageManager configuration (T1.1 / ADR-0007).
|
|
459
|
+
*
|
|
460
|
+
* Declares Postgres servers + databases + Redis servers. Consumed by
|
|
461
|
+
* `getStorageManager().configure()` at boot via `start.ts`.
|
|
462
|
+
*
|
|
463
|
+
* Default `undefined` means manager exists but is not configured — adapters
|
|
464
|
+
* relying on the manager (PostgresJobBackend.fromStorageManager, etc.) will
|
|
465
|
+
* throw with an actionable error on first use.
|
|
466
|
+
*/
|
|
467
|
+
storage: storageSchema.optional(),
|
|
468
|
+
/**
|
|
469
|
+
* Wave 2 — Polyglot services orchestration (T1.1 / ADR-0012/0013/0014/0015).
|
|
470
|
+
*
|
|
471
|
+
* Declarative external sidecar processes (Python FastAPI / Node Hono)
|
|
472
|
+
* that boot alongside the TheoKit TS app. Empty `services: {}` is the
|
|
473
|
+
* default and preserves Wave 1 behavior.
|
|
474
|
+
*/
|
|
475
|
+
services: servicesConfigSchema,
|
|
476
|
+
/**
|
|
477
|
+
* G2 — OpenAPI emit (build-time only).
|
|
478
|
+
*
|
|
479
|
+
* When present, `theokit build` writes a generated `openapi.json` from
|
|
480
|
+
* every `defineRoute()` body/query/params Zod schema. `undefined` keeps
|
|
481
|
+
* the framework backward-compatible (no emit). Pass `openapi: {}` to
|
|
482
|
+
* opt in with defaults.
|
|
483
|
+
*
|
|
484
|
+
* Defaults: spec 3.1.0 · servers `http://localhost:3000` · title
|
|
485
|
+
* "TheoKit App" · version "0.0.0" · outDir ".theo" (next to dist).
|
|
486
|
+
*
|
|
487
|
+
* The env var `THEOKIT_OPENAPI_SERVERS` (CSV of URLs) overrides
|
|
488
|
+
* `servers[].url` at emit time without rebuilding the config.
|
|
489
|
+
*/
|
|
490
|
+
openapi: z8.object({
|
|
491
|
+
servers: z8.array(
|
|
492
|
+
z8.object({
|
|
493
|
+
url: z8.string().url(),
|
|
494
|
+
description: z8.string().optional()
|
|
495
|
+
})
|
|
496
|
+
).default([{ url: "http://localhost:3000", description: "Local development" }]),
|
|
497
|
+
specVersion: z8.enum(["3.1.0", "3.0.3"]).default("3.1.0"),
|
|
498
|
+
title: z8.string().default("TheoKit App"),
|
|
499
|
+
version: z8.string().default("0.0.0"),
|
|
500
|
+
outDir: z8.string().default(".theo")
|
|
501
|
+
}).optional(),
|
|
502
|
+
/**
|
|
503
|
+
* G5 T1.3 — error envelope transformer hook (blueprint ADR D3).
|
|
504
|
+
*
|
|
505
|
+
* When present, runs at framework error-boundary time to enrich the
|
|
506
|
+
* envelope with consumer-defined extensions (`hint`, custom telemetry
|
|
507
|
+
* tags, etc.) BEFORE the envelope is serialized to the client. The
|
|
508
|
+
* function signature is preserved via the explicit `FormatErrorHook`
|
|
509
|
+
* type — TS inference flows from `theo.config.ts` into `@theo/client`
|
|
510
|
+
* codegen and `@theokit/ui` AgentErrorCard consumers.
|
|
511
|
+
*
|
|
512
|
+
* Use `z.custom<FormatErrorHook>(...)` because Zod's built-in
|
|
513
|
+
* `z.function()` discards its TS signature after parse; `z.custom`
|
|
514
|
+
* with a function-typed runtime guard preserves the call-site type
|
|
515
|
+
* without trading off Zod runtime validation.
|
|
516
|
+
*/
|
|
517
|
+
formatError: z8.custom((val) => typeof val === "function", {
|
|
518
|
+
message: "formatError must be a function"
|
|
519
|
+
}).optional()
|
|
520
|
+
}).refine((cfg) => !Object.values(cfg.services).some((s) => s.port === cfg.port), {
|
|
521
|
+
message: "service.port collides with TheoKit web port \u2014 change one of them",
|
|
522
|
+
path: ["services"]
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
// src/config/load-config.ts
|
|
526
|
+
var CONFIG_FILE = "theo.config.ts";
|
|
527
|
+
function deepMerge(base, override) {
|
|
528
|
+
const result = { ...base };
|
|
529
|
+
for (const key of Object.keys(override)) {
|
|
530
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
|
|
531
|
+
const baseVal = base[key];
|
|
532
|
+
const overVal = override[key];
|
|
533
|
+
if (overVal !== null && typeof overVal === "object" && !Array.isArray(overVal) && baseVal !== null && typeof baseVal === "object" && !Array.isArray(baseVal)) {
|
|
534
|
+
result[key] = deepMerge(
|
|
535
|
+
baseVal,
|
|
536
|
+
overVal
|
|
537
|
+
);
|
|
538
|
+
} else {
|
|
539
|
+
result[key] = overVal;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return result;
|
|
543
|
+
}
|
|
544
|
+
async function loadConfig(dir) {
|
|
545
|
+
loadEnv({ cwd: dir });
|
|
546
|
+
const configPath = resolve2(dir, CONFIG_FILE);
|
|
547
|
+
if (!existsSync2(configPath)) {
|
|
548
|
+
return theoConfigSchema.parse({});
|
|
549
|
+
}
|
|
550
|
+
let mod;
|
|
551
|
+
try {
|
|
552
|
+
mod = await importUserModule(configPath);
|
|
553
|
+
} catch (err) {
|
|
554
|
+
throw new TheoConfigError([{ field: "_file", message: err.message }], configPath);
|
|
555
|
+
}
|
|
556
|
+
const userConfig = mod.default;
|
|
557
|
+
if (userConfig == null || typeof userConfig !== "object") {
|
|
558
|
+
throw new TheoConfigError(
|
|
559
|
+
[
|
|
560
|
+
{
|
|
561
|
+
field: "_export",
|
|
562
|
+
message: "theo.config.ts must use export default defineConfig({...})"
|
|
563
|
+
}
|
|
564
|
+
],
|
|
565
|
+
configPath
|
|
308
566
|
);
|
|
309
567
|
}
|
|
310
|
-
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
] : [];
|
|
337
|
-
const renderBlock = ssr ? [
|
|
338
|
-
`if (el) {`,
|
|
339
|
-
` ;(async () => {`,
|
|
340
|
-
...preloadBlock.map((l) => " " + l),
|
|
341
|
-
renderCall,
|
|
342
|
-
` })()`,
|
|
343
|
-
`}`
|
|
344
|
-
] : [`if (el) {`, renderCall, `}`];
|
|
345
|
-
const manifestImports = ssr ? `import { routes, __theoPreloadMap } from '/@theo/route-manifest'` : `import { routes } from '/@theo/route-manifest'`;
|
|
346
|
-
const reactRouterImports = ssr ? `import { createBrowserRouter, RouterProvider, matchRoutes } from 'react-router'` : `import { createBrowserRouter, RouterProvider } from 'react-router'`;
|
|
347
|
-
return [
|
|
348
|
-
`import React, { Suspense } from 'react'`,
|
|
349
|
-
`import { ${rootMethod} } from 'react-dom/client'`,
|
|
350
|
-
reactRouterImports,
|
|
351
|
-
manifestImports,
|
|
352
|
-
`// T1.3 \u2014 side-effect import sets globalThis.__THEO_TRANSFORMER__ for theoFetch`,
|
|
353
|
-
`import '/@theo/runtime-config'`,
|
|
354
|
-
...theoUiImports,
|
|
355
|
-
``,
|
|
356
|
-
hydrationLine,
|
|
357
|
-
`const el = document.getElementById('root')`,
|
|
358
|
-
...renderBlock
|
|
359
|
-
].join("\n");
|
|
568
|
+
let rawConfig = userConfig;
|
|
569
|
+
const nodeEnv = process.env.NODE_ENV;
|
|
570
|
+
if (nodeEnv) {
|
|
571
|
+
const envFile = `theo.config.${nodeEnv}.ts`;
|
|
572
|
+
const envPath = resolve2(dir, envFile);
|
|
573
|
+
if (existsSync2(envPath)) {
|
|
574
|
+
try {
|
|
575
|
+
const envMod = await importUserModule(envPath);
|
|
576
|
+
const envConfig = envMod.default;
|
|
577
|
+
if (envConfig != null && typeof envConfig === "object") {
|
|
578
|
+
rawConfig = deepMerge(rawConfig, envConfig);
|
|
579
|
+
}
|
|
580
|
+
} catch (err) {
|
|
581
|
+
throw new TheoConfigError([{ field: "_file", message: err.message }], envPath);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
const result = theoConfigSchema.safeParse(rawConfig);
|
|
586
|
+
if (!result.success) {
|
|
587
|
+
const issues = result.error.issues.map((i) => ({
|
|
588
|
+
field: i.path.join("."),
|
|
589
|
+
message: i.message
|
|
590
|
+
}));
|
|
591
|
+
throw new TheoConfigError(issues, configPath);
|
|
592
|
+
}
|
|
593
|
+
return result.data;
|
|
360
594
|
}
|
|
361
595
|
|
|
362
|
-
// src/
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
370
|
-
function buildRoutePath(parents, segment) {
|
|
371
|
-
const joined = [...parents, segment].filter(Boolean).join("/");
|
|
372
|
-
return "/" + joined;
|
|
596
|
+
// src/vite-plugin/theoui-detect.ts
|
|
597
|
+
import { existsSync as existsSync3, readFileSync } from "fs";
|
|
598
|
+
import { dirname, join } from "path";
|
|
599
|
+
function resolveTheoUiConfig(raw) {
|
|
600
|
+
return {
|
|
601
|
+
theme: raw?.theme ?? "violet-forge",
|
|
602
|
+
fonts: raw?.fonts ?? "bundled"
|
|
603
|
+
};
|
|
373
604
|
}
|
|
374
|
-
function
|
|
375
|
-
|
|
376
|
-
|
|
605
|
+
function resolveExportSubpath(pkgRoot, subpath) {
|
|
606
|
+
const pkgJsonPath = join(pkgRoot, "package.json");
|
|
607
|
+
if (!existsSync3(pkgJsonPath)) return null;
|
|
608
|
+
try {
|
|
609
|
+
const pkg = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
610
|
+
const exportKey = `./${subpath}`;
|
|
611
|
+
const exp = pkg.exports?.[exportKey];
|
|
612
|
+
if (!exp) {
|
|
613
|
+
const fallback = join(pkgRoot, "dist", subpath);
|
|
614
|
+
return existsSync3(fallback) ? fallback : null;
|
|
615
|
+
}
|
|
616
|
+
let target;
|
|
617
|
+
if (typeof exp === "string") target = exp;
|
|
618
|
+
else if (typeof exp === "object") {
|
|
619
|
+
const e = exp;
|
|
620
|
+
target = e.import ?? e.default;
|
|
621
|
+
}
|
|
622
|
+
if (!target) return null;
|
|
623
|
+
const cleaned = target.replace(/^\.\//, "");
|
|
624
|
+
const candidate = join(pkgRoot, cleaned);
|
|
625
|
+
return existsSync3(candidate) ? candidate : null;
|
|
626
|
+
} catch {
|
|
627
|
+
return null;
|
|
377
628
|
}
|
|
378
629
|
}
|
|
379
|
-
|
|
380
|
-
const
|
|
381
|
-
const
|
|
382
|
-
if (
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
630
|
+
var defaultResolver = (specifier, projectRoot) => {
|
|
631
|
+
const parts = specifier.split("/");
|
|
632
|
+
const pkgName = parts[0]?.startsWith("@") && parts.length >= 2 ? `${parts[0]}/${parts[1]}` : parts[0];
|
|
633
|
+
if (!pkgName) return false;
|
|
634
|
+
const subpath = specifier.slice(pkgName.length + 1);
|
|
635
|
+
if (!subpath) return false;
|
|
636
|
+
let dir = projectRoot;
|
|
637
|
+
for (let depth = 0; depth < 10; depth++) {
|
|
638
|
+
const pkgRoot = join(dir, "node_modules", ...pkgName.split("/"));
|
|
639
|
+
if (existsSync3(pkgRoot)) {
|
|
640
|
+
const resolved = resolveExportSubpath(pkgRoot, subpath);
|
|
641
|
+
if (resolved) return true;
|
|
642
|
+
}
|
|
643
|
+
const parent = dirname(dir);
|
|
644
|
+
if (parent === dir) break;
|
|
645
|
+
dir = parent;
|
|
388
646
|
}
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
647
|
+
return false;
|
|
648
|
+
};
|
|
649
|
+
function isDeclaredInPackageJson(projectRoot) {
|
|
650
|
+
try {
|
|
651
|
+
const raw = readFileSync(join(projectRoot, "package.json"), "utf-8");
|
|
652
|
+
const pkg = JSON.parse(raw);
|
|
653
|
+
return Boolean(
|
|
654
|
+
pkg.dependencies?.["@theokit/ui"] ?? pkg.devDependencies?.["@theokit/ui"] ?? pkg.peerDependencies?.["@theokit/ui"]
|
|
655
|
+
);
|
|
656
|
+
} catch {
|
|
657
|
+
return false;
|
|
397
658
|
}
|
|
398
659
|
}
|
|
399
|
-
function
|
|
400
|
-
const
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
layoutState: { found: false }
|
|
404
|
-
};
|
|
405
|
-
walkRouteTree(tree, [], acc);
|
|
406
|
-
const staticImports = acc.staticImports;
|
|
407
|
-
const lazyPages = acc.lazyPages;
|
|
408
|
-
const layoutState = acc.layoutState;
|
|
409
|
-
const lines = [`import React, { Suspense } from 'react'`];
|
|
410
|
-
if (layoutState.found) {
|
|
411
|
-
lines.push(`import { Outlet } from 'react-router'`);
|
|
412
|
-
}
|
|
413
|
-
lines.push("");
|
|
414
|
-
for (const imp of staticImports) {
|
|
415
|
-
lines.push(`import ${imp.varName} from '${imp.importPath}'`);
|
|
660
|
+
function detectTheoUi(projectRoot, rawConfig, resolver = defaultResolver) {
|
|
661
|
+
const config = resolveTheoUiConfig(typeof rawConfig === "object" ? rawConfig : void 0);
|
|
662
|
+
if (rawConfig === false) {
|
|
663
|
+
return { enabled: false, config };
|
|
416
664
|
}
|
|
417
|
-
|
|
418
|
-
|
|
665
|
+
if (!isDeclaredInPackageJson(projectRoot)) {
|
|
666
|
+
return { enabled: false, config };
|
|
419
667
|
}
|
|
420
|
-
|
|
421
|
-
const
|
|
422
|
-
(
|
|
423
|
-
|
|
424
|
-
lines.push("export const __theoPreloadMap = {");
|
|
425
|
-
for (const e of preloadEntries) lines.push(e);
|
|
426
|
-
lines.push("}", "");
|
|
427
|
-
function buildChildrenArray(node, seg) {
|
|
428
|
-
const childConfigs = [];
|
|
429
|
-
if (node.page) {
|
|
430
|
-
const pageVar = safeVarName(seg, "Page");
|
|
431
|
-
const fallbackEl = node.loading ? `React.createElement(${safeVarName(seg, "Loading")})` : "null";
|
|
432
|
-
const pageElement = `React.createElement(Suspense, { fallback: ${fallbackEl} }, React.createElement(${pageVar}))`;
|
|
433
|
-
childConfigs.push(`{ index: true, element: ${pageElement} }`);
|
|
434
|
-
}
|
|
435
|
-
for (const child of node.children) {
|
|
436
|
-
childConfigs.push(genRouteConfig(child, false));
|
|
668
|
+
const probes = ["@theokit/ui/styles.css", "@theokit/ui/fonts.css"];
|
|
669
|
+
for (const probe of probes) {
|
|
670
|
+
if (resolver(probe, projectRoot)) {
|
|
671
|
+
return { enabled: true, config };
|
|
437
672
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
673
|
+
}
|
|
674
|
+
return { enabled: false, config };
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// src/vite-plugin/config-resolve.ts
|
|
678
|
+
var DEFAULT_RESOLVED = {
|
|
679
|
+
pluginRunner: void 0,
|
|
680
|
+
transformer: void 0,
|
|
681
|
+
resolvedBatching: void 0,
|
|
682
|
+
theoUi: void 0,
|
|
683
|
+
csrfMode: "strict",
|
|
684
|
+
securityHeaders: void 0,
|
|
685
|
+
disallowed: void 0,
|
|
686
|
+
cors: void 0,
|
|
687
|
+
auditLogger: void 0,
|
|
688
|
+
devtoolsEnabled: true,
|
|
689
|
+
openapi: void 0,
|
|
690
|
+
distDir: ".theo"
|
|
691
|
+
};
|
|
692
|
+
async function resolvePluginConfig(projectRoot) {
|
|
693
|
+
try {
|
|
694
|
+
const userConfig = await loadConfig(projectRoot);
|
|
695
|
+
const pluginRunner = await createPluginRunnerFromConfig(userConfig.plugins);
|
|
696
|
+
const transformer = resolveTransformer(userConfig.serialization);
|
|
697
|
+
let resolvedBatching;
|
|
698
|
+
if (userConfig.batching === true) {
|
|
699
|
+
resolvedBatching = {};
|
|
700
|
+
} else if (typeof userConfig.batching === "object") {
|
|
701
|
+
resolvedBatching = userConfig.batching;
|
|
441
702
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
703
|
+
const theoUi = detectTheoUi(projectRoot, userConfig.ui);
|
|
704
|
+
let auditLogger;
|
|
705
|
+
const maybeLogger = userConfig.audit?.logger;
|
|
706
|
+
if (maybeLogger && typeof maybeLogger.log === "function") {
|
|
707
|
+
auditLogger = maybeLogger;
|
|
446
708
|
}
|
|
447
|
-
return
|
|
709
|
+
return {
|
|
710
|
+
pluginRunner,
|
|
711
|
+
transformer,
|
|
712
|
+
resolvedBatching,
|
|
713
|
+
theoUi,
|
|
714
|
+
csrfMode: userConfig.security?.csrf ?? "strict",
|
|
715
|
+
securityHeaders: userConfig.security?.headers,
|
|
716
|
+
disallowed: userConfig.security?.disallowed,
|
|
717
|
+
cors: userConfig.security?.cors,
|
|
718
|
+
auditLogger,
|
|
719
|
+
devtoolsEnabled: userConfig.devtools !== false,
|
|
720
|
+
openapi: userConfig.openapi,
|
|
721
|
+
distDir: userConfig.distDir
|
|
722
|
+
};
|
|
723
|
+
} catch {
|
|
724
|
+
return DEFAULT_RESOLVED;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// src/vite-plugin/configure-server-hook.ts
|
|
729
|
+
import { resolve as resolve6, basename } from "path";
|
|
730
|
+
|
|
731
|
+
// src/devtools/server-side/route-manifest.ts
|
|
732
|
+
function buildPath(parents, segment) {
|
|
733
|
+
const joined = [...parents, segment].filter(Boolean).join("/");
|
|
734
|
+
return "/" + joined;
|
|
735
|
+
}
|
|
736
|
+
function walk(node, parents, layoutChain, acc) {
|
|
737
|
+
const path = buildPath(parents, node.segment);
|
|
738
|
+
const nextChain = node.layout ? [...layoutChain, node.layout] : layoutChain;
|
|
739
|
+
if (node.page) {
|
|
740
|
+
acc.routes.push({
|
|
741
|
+
path,
|
|
742
|
+
absoluteFilePath: node.page,
|
|
743
|
+
layoutChain: [...layoutChain],
|
|
744
|
+
// freeze chain at this leaf
|
|
745
|
+
hasLoading: Boolean(node.loading),
|
|
746
|
+
hasError: Boolean(node.error),
|
|
747
|
+
hasNotFound: Boolean(node.notFound)
|
|
748
|
+
});
|
|
448
749
|
}
|
|
449
|
-
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
if (node.layout) {
|
|
453
|
-
const layoutVar = safeVarName(seg, "Layout");
|
|
454
|
-
const pathPart = isRoot ? `path: '/'` : `path: '${node.segment}'`;
|
|
455
|
-
return `{ ${pathPart}, element: React.createElement(${layoutVar}, { children: React.createElement(Outlet) }), children: ${childrenArray} }`;
|
|
456
|
-
}
|
|
457
|
-
if (isRoot) {
|
|
458
|
-
if (node.children.length === 0 && !node.page && !node.notFound && !node.error) {
|
|
459
|
-
return `{ path: '/', children: [] }`;
|
|
460
|
-
}
|
|
461
|
-
return `{ path: '/', children: ${childrenArray} }`;
|
|
462
|
-
}
|
|
463
|
-
if (node.page && node.children.length === 0 && !node.error && !node.notFound) {
|
|
464
|
-
const pageVar = safeVarName(seg, "Page");
|
|
465
|
-
const fallbackEl = node.loading ? `React.createElement(${safeVarName(seg, "Loading")})` : "null";
|
|
466
|
-
const pageElement = `React.createElement(Suspense, { fallback: ${fallbackEl} }, React.createElement(${pageVar}))`;
|
|
467
|
-
return `{ path: '${node.segment}', element: ${pageElement} }`;
|
|
468
|
-
}
|
|
469
|
-
return `{ path: '${node.segment}', children: ${childrenArray} }`;
|
|
750
|
+
for (const child of node.children) {
|
|
751
|
+
const nextParents = node.segment ? [...parents, node.segment] : parents;
|
|
752
|
+
walk(child, nextParents, nextChain, acc);
|
|
470
753
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
754
|
+
}
|
|
755
|
+
function buildRouteManifest(tree) {
|
|
756
|
+
const acc = { routes: [] };
|
|
757
|
+
walk(tree, [], [], acc);
|
|
758
|
+
return { routes: acc.routes };
|
|
759
|
+
}
|
|
760
|
+
function broadcastRouteManifest(tree) {
|
|
761
|
+
const manifest = buildRouteManifest(tree);
|
|
762
|
+
broadcastToDevtools("theo:devtools:manifest", manifest);
|
|
474
763
|
}
|
|
475
764
|
|
|
476
765
|
// src/router/scan.ts
|
|
477
|
-
import { readdirSync, statSync, existsSync } from "fs";
|
|
478
|
-
import { join, resolve } from "path";
|
|
766
|
+
import { readdirSync, statSync, existsSync as existsSync4 } from "fs";
|
|
767
|
+
import { join as join2, resolve as resolve3 } from "path";
|
|
479
768
|
|
|
480
769
|
// src/router/types.ts
|
|
481
770
|
var ROUTE_FILE_NAMES = ["page", "layout", "error", "loading", "not-found"];
|
|
@@ -515,8 +804,8 @@ function attachRouteFiles(node, dir) {
|
|
|
515
804
|
if (node[key] !== void 0) continue;
|
|
516
805
|
for (const ext of ROUTE_FILE_EXTENSIONS) {
|
|
517
806
|
const filename = `${name}${ext}`;
|
|
518
|
-
if (
|
|
519
|
-
setRouteFile(node, key,
|
|
807
|
+
if (existsSync4(join2(dir, filename))) {
|
|
808
|
+
setRouteFile(node, key, resolve3(dir, filename));
|
|
520
809
|
break;
|
|
521
810
|
}
|
|
522
811
|
}
|
|
@@ -531,8 +820,10 @@ function scanDir(dir, segment, routePath) {
|
|
|
531
820
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
532
821
|
if (!entry.isDirectory()) continue;
|
|
533
822
|
if (entry.name.startsWith("_") || entry.name.startsWith(".")) continue;
|
|
534
|
-
const
|
|
535
|
-
const
|
|
823
|
+
const isRouteGroup = entry.name.startsWith("(") && entry.name.endsWith(")");
|
|
824
|
+
const urlSegment = isRouteGroup ? "" : entry.name;
|
|
825
|
+
const childPath = urlSegment ? routePath === "/" ? `/${urlSegment}` : `${routePath}/${urlSegment}` : routePath;
|
|
826
|
+
const child = scanDir(join2(dir, entry.name), isRouteGroup ? "" : entry.name, childPath);
|
|
536
827
|
if (nodeHasContent(child)) {
|
|
537
828
|
node.children.push(child);
|
|
538
829
|
}
|
|
@@ -540,7 +831,7 @@ function scanDir(dir, segment, routePath) {
|
|
|
540
831
|
return node;
|
|
541
832
|
}
|
|
542
833
|
function scanRoutes(appDir) {
|
|
543
|
-
if (!
|
|
834
|
+
if (!existsSync4(appDir)) {
|
|
544
835
|
throw new Error(`App directory does not exist: ${appDir}`);
|
|
545
836
|
}
|
|
546
837
|
if (!statSync(appDir).isDirectory()) {
|
|
@@ -680,10 +971,10 @@ async function handleBatchIfMatch(req, res, ctx) {
|
|
|
680
971
|
async function handleBatchInline(req, res, batching, requestId) {
|
|
681
972
|
try {
|
|
682
973
|
const chunks = [];
|
|
683
|
-
await new Promise((
|
|
974
|
+
await new Promise((resolve10, reject) => {
|
|
684
975
|
req.on("data", (c) => chunks.push(c));
|
|
685
976
|
req.on("end", () => {
|
|
686
|
-
|
|
977
|
+
resolve10();
|
|
687
978
|
});
|
|
688
979
|
req.on("error", reject);
|
|
689
980
|
});
|
|
@@ -829,542 +1120,483 @@ function createApiMiddleware(vite, serverDir, rateLimitConfigOrOptions) {
|
|
|
829
1120
|
};
|
|
830
1121
|
}
|
|
831
1122
|
|
|
832
|
-
// src/
|
|
833
|
-
import {
|
|
834
|
-
import { resolve as
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
}
|
|
1123
|
+
// src/vite-plugin/ssr-dev-middleware.ts
|
|
1124
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
1125
|
+
import { resolve as resolve4 } from "path";
|
|
1126
|
+
function isSsrRenderResult(value) {
|
|
1127
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1128
|
+
if (!("html" in value)) return false;
|
|
1129
|
+
return typeof value.html === "string";
|
|
1130
|
+
}
|
|
1131
|
+
function setupSsrDevMiddleware(server, opts) {
|
|
1132
|
+
server.middlewares.use((req, res, next) => {
|
|
1133
|
+
void (async () => {
|
|
1134
|
+
const url = req.url ?? "/";
|
|
1135
|
+
if (url.startsWith("/api/") || url.startsWith("/@") || url.startsWith("/node_modules/") || url.includes(".")) {
|
|
1136
|
+
next();
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
try {
|
|
1140
|
+
const indexPath = resolve4(opts.projectRoot, "index.html");
|
|
1141
|
+
let template = readFileSync2(indexPath, "utf-8");
|
|
1142
|
+
template = await server.transformIndexHtml(url, template);
|
|
1143
|
+
const nonce = generateNonce();
|
|
1144
|
+
applySecurityHeaders(
|
|
1145
|
+
res,
|
|
1146
|
+
opts.securityHeaders ?? {},
|
|
1147
|
+
{ production: process.env.NODE_ENV === "production" },
|
|
1148
|
+
{ nonce }
|
|
1149
|
+
);
|
|
1150
|
+
const mod = await server.ssrLoadModule(opts.virtualEntryServerId);
|
|
1151
|
+
const result = await mod.render(url, { nonce });
|
|
1152
|
+
if (result && typeof result === "object" && "redirect" in result) {
|
|
1153
|
+
res.writeHead(302, {
|
|
1154
|
+
Location: result.redirect.headers.get("location") ?? "/"
|
|
1155
|
+
});
|
|
1156
|
+
res.end();
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
let ssrHtml;
|
|
1160
|
+
let hydrationScript = "";
|
|
1161
|
+
if (typeof result === "string") {
|
|
1162
|
+
ssrHtml = result;
|
|
1163
|
+
} else if (isSsrRenderResult(result)) {
|
|
1164
|
+
ssrHtml = result.html;
|
|
1165
|
+
const dataJson = JSON.stringify(result.hydrationData).replace(/</g, "\\u003c");
|
|
1166
|
+
hydrationScript = `<script nonce="${nonce}">window.__staticRouterHydrationData=${dataJson}</script>`;
|
|
1167
|
+
} else {
|
|
1168
|
+
ssrHtml = "";
|
|
1169
|
+
}
|
|
1170
|
+
const rootDivMatch = /<div id=["']root["'][^>]*>/.exec(template);
|
|
1171
|
+
if (!rootDivMatch) {
|
|
1172
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
1173
|
+
res.end(template);
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
const splitIdx = template.indexOf(rootDivMatch[0]) + rootDivMatch[0].length;
|
|
1177
|
+
const html = template.slice(0, splitIdx) + ssrHtml + hydrationScript + template.slice(splitIdx);
|
|
1178
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
1179
|
+
res.end(html);
|
|
1180
|
+
} catch (err) {
|
|
1181
|
+
server.ssrFixStacktrace(err);
|
|
1182
|
+
console.error("[SSR Dev Error]", err);
|
|
1183
|
+
next();
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
})();
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
857
1189
|
|
|
858
|
-
// src/
|
|
859
|
-
import {
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
});
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
* Pass `false` to suppress the header.
|
|
922
|
-
*/
|
|
923
|
-
permissionsPolicy: z.union([headerSafeString, z.literal(false)]).optional()
|
|
924
|
-
});
|
|
925
|
-
var disallowedConfigSchema = z.object({
|
|
926
|
-
routes: z.array(z.union([z.string(), z.instanceof(RegExp)])),
|
|
927
|
-
behavior: z.enum(["warn", "raise"]).default("raise")
|
|
928
|
-
});
|
|
929
|
-
var corsSchema = z.object({
|
|
930
|
-
origins: z.union([
|
|
931
|
-
z.literal("*"),
|
|
932
|
-
headerSafeString,
|
|
933
|
-
z.instanceof(RegExp),
|
|
934
|
-
z.array(z.union([headerSafeString, z.instanceof(RegExp)])),
|
|
935
|
-
z.function().args(z.string()).returns(z.boolean())
|
|
936
|
-
]),
|
|
937
|
-
methods: z.array(z.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"])).optional(),
|
|
938
|
-
allowedHeaders: z.array(headerSafeString).optional(),
|
|
939
|
-
exposedHeaders: z.array(headerSafeString).optional(),
|
|
940
|
-
credentials: z.boolean().default(false),
|
|
941
|
-
maxAge: z.number().int().min(0).max(86400).default(600)
|
|
942
|
-
}).refine((c) => !(c.origins === "*" && c.credentials), {
|
|
943
|
-
message: 'CORS spec forbids origins:"*" with credentials:true \u2014 browsers ignore the wildcard'
|
|
944
|
-
});
|
|
945
|
-
var securitySchema = z.object({
|
|
946
|
-
// T6.1 — default flipped from 'warn' to 'strict' for 0.3.0. Apps that
|
|
947
|
-
// grep their warn-mode logs from 0.2.x already know which endpoints
|
|
948
|
-
// break; opt back into 'warn' globally OR use disallowedRoutes for
|
|
949
|
-
// surgical migration. See docs/migrating/0.2-to-0.3.md.
|
|
950
|
-
csrf: z.enum(["off", "warn", "strict"]).default("strict"),
|
|
951
|
-
headers: securityHeadersSchema.optional(),
|
|
952
|
-
/** T5.1 — per-route escalation (Rails disallowed_warnings pattern). */
|
|
953
|
-
disallowed: disallowedConfigSchema.optional(),
|
|
954
|
-
/** T1.2 — Cross-Origin Resource Sharing. Single global config; runs first in pipeline. */
|
|
955
|
-
cors: corsSchema.optional()
|
|
956
|
-
});
|
|
957
|
-
var tlsConfigSchema = z.object({
|
|
958
|
-
rejectUnauthorized: z.boolean().optional(),
|
|
959
|
-
caCert: z.string().optional(),
|
|
960
|
-
clientCert: z.string().optional(),
|
|
961
|
-
clientKey: z.string().optional()
|
|
962
|
-
});
|
|
963
|
-
var serverConfigSchema = z.object({
|
|
964
|
-
host: z.string().min(1),
|
|
965
|
-
port: z.number().int().positive().max(65535).optional(),
|
|
966
|
-
user: z.string().min(1),
|
|
967
|
-
/** Password may be the empty string (some providers permit it). */
|
|
968
|
-
password: z.string(),
|
|
969
|
-
tls: tlsConfigSchema.optional()
|
|
970
|
-
});
|
|
971
|
-
var postgresPoolConfigSchema = z.object({
|
|
972
|
-
min: z.number().int().nonnegative().optional(),
|
|
973
|
-
max: z.number().int().positive().optional(),
|
|
974
|
-
connectionTimeoutMillis: z.number().int().positive().optional(),
|
|
975
|
-
idleTimeoutMillis: z.number().int().nonnegative().optional()
|
|
976
|
-
});
|
|
977
|
-
var postgresDatabaseConfigSchema = z.object({
|
|
978
|
-
/** References a key in `storage.servers`. Resolved at `usePostgres()` call time. */
|
|
979
|
-
server: z.string().min(1),
|
|
980
|
-
database: z.string().min(1),
|
|
981
|
-
pool: postgresPoolConfigSchema.optional()
|
|
982
|
-
});
|
|
983
|
-
var redisServerConfigSchema = serverConfigSchema.extend({
|
|
984
|
-
db: z.number().int().nonnegative().optional(),
|
|
985
|
-
maxRetriesPerRequest: z.number().int().nonnegative().optional()
|
|
986
|
-
});
|
|
987
|
-
var storageSchema = z.object({
|
|
988
|
-
servers: z.record(z.string(), serverConfigSchema).optional(),
|
|
989
|
-
databases: z.record(z.string(), postgresDatabaseConfigSchema).optional(),
|
|
990
|
-
redis: z.record(z.string(), redisServerConfigSchema).optional()
|
|
991
|
-
});
|
|
992
|
-
var theoConfigSchema = z.object({
|
|
993
|
-
appDir: z.string().default("app"),
|
|
994
|
-
serverDir: z.string().default("server"),
|
|
995
|
-
/**
|
|
996
|
-
* T2.2 / EC-4 — Build output directory. Must be a relative path inside
|
|
997
|
-
* the project root. Refused absolute or parent-relative paths to prevent
|
|
998
|
-
* `cleanOutDir` from wiping arbitrary locations (defense-in-depth on
|
|
999
|
-
* top of cleanOutDir's runtime EC-3 guard).
|
|
1000
|
-
*/
|
|
1001
|
-
distDir: z.string().default(".theo").refine(
|
|
1002
|
-
(d) => !/^([A-Za-z]:)?[/\\]/.test(d) && !d.startsWith(".."),
|
|
1003
|
-
'distDir must be a relative path inside the project root (e.g., ".theo")'
|
|
1004
|
-
),
|
|
1005
|
-
/**
|
|
1006
|
-
* Agent runtime configuration.
|
|
1007
|
-
*
|
|
1008
|
-
* - `maxRegistries` (T2.3, legacy): dev-mode cleanup of stale
|
|
1009
|
-
* `.theokit/agents/<id>/` dirs by mtime. Now superseded by SDK's
|
|
1010
|
-
* native registry GC but kept for backward-compat.
|
|
1011
|
-
* - `registry` (Phase 6, Production-Readiness #2): forwarded to
|
|
1012
|
-
* `Agent.registry.configure()` lazily on first request.
|
|
1013
|
-
* * `maxAgents` — LRU cap. MUST be ≥ max-concurrent-active-conversations
|
|
1014
|
-
* (EC-17 DOCUMENT): with maxAgents:1 and 2 concurrent chats, LRU evicts
|
|
1015
|
-
* mid-stream → SDK aborts with code:'aborted'. Default 100 covers
|
|
1016
|
-
* indie/small-team; tune up for high-traffic.
|
|
1017
|
-
* * `idleTimeoutMs` — auto-evict idle agents (default 30 min). 0 disables
|
|
1018
|
-
* idle eviction (LRU only).
|
|
1019
|
-
*/
|
|
1020
|
-
agents: z.object({
|
|
1021
|
-
maxRegistries: z.number().int().positive().default(100),
|
|
1022
|
-
registry: z.object({
|
|
1023
|
-
/** LRU cap — MUST be ≥ max-concurrent-active-conversations. */
|
|
1024
|
-
maxAgents: z.number().int().positive().max(1e4).default(100),
|
|
1025
|
-
/** Idle eviction window in ms. 0 disables idle eviction (LRU only). */
|
|
1026
|
-
idleTimeoutMs: z.number().int().nonnegative().default(30 * 6e4)
|
|
1027
|
-
}).optional()
|
|
1028
|
-
}).optional(),
|
|
1029
|
-
port: z.number().int().min(1).max(65535).default(3e3),
|
|
1030
|
-
ssr: z.boolean().default(false),
|
|
1031
|
-
/** When true (and ssr === true), use renderToPipeableStream with progressive
|
|
1032
|
-
* shell flush instead of single-shot renderToString. Opt-in for streaming
|
|
1033
|
-
* SSR; default false preserves the current behavior. */
|
|
1034
|
-
ssrStreaming: z.boolean().default(false),
|
|
1035
|
-
rateLimit: rateLimitSchema.optional(),
|
|
1036
|
-
upload: uploadSchema.optional(),
|
|
1037
|
-
logging: loggingSchema.optional(),
|
|
1038
|
-
security: securitySchema.optional(),
|
|
1039
|
-
serialization: z.enum(["json", "superjson"]).default("json"),
|
|
1040
|
-
// Plugins are validated structurally at runtime by createPluginRunnerFromConfig.
|
|
1041
|
-
// Zod only checks the shape minimally (must be array). Type-level safety is
|
|
1042
|
-
// provided through defineConfig at the user surface.
|
|
1043
|
-
plugins: z.array(z.unknown()).optional(),
|
|
1044
|
-
/** Enable client-side batching of theoFetch calls and the
|
|
1045
|
-
* /api/__theo_batch__ server endpoint. */
|
|
1046
|
-
batching: z.union([z.boolean(), z.object({ max: z.number().int().positive().optional() })]).optional(),
|
|
1047
|
-
/** T4.1 — Audit log. When `logger` is provided, framework events
|
|
1048
|
-
* (csrf.warn, rate-limit.exceeded, session.rotated, csp.violation) are
|
|
1049
|
-
* emitted to it. Default: noop. */
|
|
1050
|
-
audit: z.object({
|
|
1051
|
-
logger: z.unknown().optional()
|
|
1052
|
-
}).optional(),
|
|
1053
|
-
/** TheoUI auto-wire (T2.1). `false` = opt-out; object = explicit theme/fonts;
|
|
1054
|
-
* undefined = enabled when @theokit/ui is detected in node_modules. */
|
|
1055
|
-
ui: z.union([
|
|
1056
|
-
z.literal(false),
|
|
1057
|
-
z.object({
|
|
1058
|
-
theme: z.enum(["violet-forge", "noir", "paper"]).optional(),
|
|
1059
|
-
fonts: z.enum(["bundled", "cdn"]).optional()
|
|
1190
|
+
// src/vite-plugin/ws-upgrade.ts
|
|
1191
|
+
import { resolve as resolve5 } from "path";
|
|
1192
|
+
function setupWsUpgrade(server, projectRoot) {
|
|
1193
|
+
const wsRoutes = scanWebSocketRoutes(resolve5(projectRoot, "server"));
|
|
1194
|
+
if (wsRoutes.length === 0) return;
|
|
1195
|
+
const wsHttpServer = server.httpServer;
|
|
1196
|
+
if (!wsHttpServer) return;
|
|
1197
|
+
void import("ws").then(({ WebSocketServer }) => {
|
|
1198
|
+
const wss = new WebSocketServer({ noServer: true });
|
|
1199
|
+
wsHttpServer.on("upgrade", (request, socket, head) => {
|
|
1200
|
+
void (async () => {
|
|
1201
|
+
const url = request.url ?? "/";
|
|
1202
|
+
if (!url.startsWith("/ws/")) return;
|
|
1203
|
+
const wsPath = url.split("?")[0];
|
|
1204
|
+
const match = wsRoutes.find((r) => r.wsPath === wsPath);
|
|
1205
|
+
if (!match) {
|
|
1206
|
+
socket.destroy();
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1209
|
+
try {
|
|
1210
|
+
const mod = await server.ssrLoadModule(match.filePath);
|
|
1211
|
+
const handler = mod.default ?? mod;
|
|
1212
|
+
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
1213
|
+
void handler.onOpen?.(ws, request);
|
|
1214
|
+
ws.on("message", (data) => {
|
|
1215
|
+
void handler.onMessage?.(ws, data.toString());
|
|
1216
|
+
});
|
|
1217
|
+
ws.on("close", (code, reason) => {
|
|
1218
|
+
void handler.onClose?.(ws, code, reason);
|
|
1219
|
+
});
|
|
1220
|
+
ws.on("error", (err) => {
|
|
1221
|
+
void handler.onError?.(ws, err);
|
|
1222
|
+
});
|
|
1223
|
+
});
|
|
1224
|
+
} catch {
|
|
1225
|
+
socket.destroy();
|
|
1226
|
+
}
|
|
1227
|
+
})();
|
|
1228
|
+
});
|
|
1229
|
+
}).catch(() => {
|
|
1230
|
+
console.warn(
|
|
1231
|
+
'[Theo] WebSocket routes found but "ws" package not installed. Run: npm install ws'
|
|
1232
|
+
);
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// src/vite-plugin/configure-server-hook.ts
|
|
1237
|
+
async function runConfigureServer(server, ctx) {
|
|
1238
|
+
ctx.isDevMode.value = true;
|
|
1239
|
+
globalThis.__theoViteHotServer = server;
|
|
1240
|
+
server.ws.on("theo:devtools:request-manifest", () => {
|
|
1241
|
+
try {
|
|
1242
|
+
const tree = scanRoutes(ctx.appDir);
|
|
1243
|
+
broadcastRouteManifest(tree);
|
|
1244
|
+
} catch {
|
|
1245
|
+
}
|
|
1246
|
+
});
|
|
1247
|
+
const serverDir = resolve6(ctx.projectRoot, "server");
|
|
1248
|
+
server.middlewares.use(
|
|
1249
|
+
createActionMiddleware(server, serverDir, {
|
|
1250
|
+
pluginRunner: ctx.pluginRunner,
|
|
1251
|
+
csrfMode: ctx.csrfMode,
|
|
1252
|
+
disallowed: ctx.disallowed
|
|
1060
1253
|
})
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
*/
|
|
1078
|
-
devtools: z.union([
|
|
1079
|
-
z.literal(false),
|
|
1080
|
-
z.object({
|
|
1081
|
-
position: z.enum(["top-left", "top-right", "bottom-left", "bottom-right"]).optional(),
|
|
1082
|
-
theme: z.enum(["light", "dark", "system"]).optional()
|
|
1254
|
+
);
|
|
1255
|
+
const servicesProxyPrefixes = ctx.services && Object.keys(ctx.services).length > 0 ? Object.values(ctx.services).map((s) => s.proxy) : [];
|
|
1256
|
+
const csrfReadinessStore = ctx.csrfReadinessStoreOverride ?? new CsrfReadinessStore();
|
|
1257
|
+
server.middlewares.use(
|
|
1258
|
+
createApiMiddleware(server, serverDir, {
|
|
1259
|
+
rateLimitConfig: ctx.rateLimit,
|
|
1260
|
+
pluginRunner: ctx.pluginRunner,
|
|
1261
|
+
batching: ctx.resolvedBatching,
|
|
1262
|
+
transformer: ctx.transformer,
|
|
1263
|
+
csrfMode: ctx.csrfMode,
|
|
1264
|
+
securityHeaders: ctx.securityHeaders,
|
|
1265
|
+
disallowed: ctx.disallowed,
|
|
1266
|
+
cors: ctx.cors,
|
|
1267
|
+
auditLogger: ctx.auditLogger,
|
|
1268
|
+
servicesProxyPrefixes,
|
|
1269
|
+
csrfReadinessStore
|
|
1083
1270
|
})
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
*/
|
|
1091
|
-
adapters: z.array(z.string()).optional(),
|
|
1092
|
-
/**
|
|
1093
|
-
* Extra package names to pre-bundle via Vite's `optimizeDeps.include`.
|
|
1094
|
-
*
|
|
1095
|
-
* Framework auto-includes `@theokit/ui` and `lucide-react` when present.
|
|
1096
|
-
* Apps adding plugin peer-deps that rely on a runtime `import('<pkg>')`
|
|
1097
|
-
* (dynamic specifier) — e.g. `@theokit/plugin-canvas` consumers
|
|
1098
|
-
* installing `mermaid` for Mermaid SVG rendering — must declare those
|
|
1099
|
-
* here so Vite can resolve the literal in dev mode without
|
|
1100
|
-
* "Failed to resolve module specifier" errors. Production builds use
|
|
1101
|
-
* tsup/esbuild bundling and are not affected.
|
|
1102
|
-
*
|
|
1103
|
-
* Example:
|
|
1104
|
-
* viteOptimizeDeps: ['mermaid']
|
|
1105
|
-
*/
|
|
1106
|
-
viteOptimizeDeps: z.array(z.string()).optional(),
|
|
1107
|
-
/**
|
|
1108
|
-
* Jobs backend (ADR D3 + T2.1). When configured, every request gets
|
|
1109
|
-
* `ctx.queue.enqueue` auto-wired via the outbox lifecycle. Pass a
|
|
1110
|
-
* `JobBackend` instance (InMemoryJobBackend, PostgresJobBackend, etc.).
|
|
1111
|
-
*/
|
|
1112
|
-
jobs: z.object({
|
|
1113
|
-
backend: z.custom((v) => typeof v === "object" && v !== null)
|
|
1114
|
-
}).optional(),
|
|
1115
|
-
/**
|
|
1116
|
-
* StorageManager configuration (T1.1 / ADR-0007).
|
|
1117
|
-
*
|
|
1118
|
-
* Declares Postgres servers + databases + Redis servers. Consumed by
|
|
1119
|
-
* `getStorageManager().configure()` at boot via `start.ts`.
|
|
1120
|
-
*
|
|
1121
|
-
* Default `undefined` means manager exists but is not configured — adapters
|
|
1122
|
-
* relying on the manager (PostgresJobBackend.fromStorageManager, etc.) will
|
|
1123
|
-
* throw with an actionable error on first use.
|
|
1124
|
-
*/
|
|
1125
|
-
storage: storageSchema.optional(),
|
|
1126
|
-
/**
|
|
1127
|
-
* Wave 2 — Polyglot services orchestration (T1.1 / ADR-0012/0013/0014/0015).
|
|
1128
|
-
*
|
|
1129
|
-
* Declarative external sidecar processes (Python FastAPI / Node Hono)
|
|
1130
|
-
* that boot alongside the TheoKit TS app. Empty `services: {}` is the
|
|
1131
|
-
* default and preserves Wave 1 behavior.
|
|
1132
|
-
*/
|
|
1133
|
-
services: servicesConfigSchema,
|
|
1134
|
-
/**
|
|
1135
|
-
* G2 — OpenAPI emit (build-time only).
|
|
1136
|
-
*
|
|
1137
|
-
* When present, `theokit build` writes a generated `openapi.json` from
|
|
1138
|
-
* every `defineRoute()` body/query/params Zod schema. `undefined` keeps
|
|
1139
|
-
* the framework backward-compatible (no emit). Pass `openapi: {}` to
|
|
1140
|
-
* opt in with defaults.
|
|
1141
|
-
*
|
|
1142
|
-
* Defaults: spec 3.1.0 · servers `http://localhost:3000` · title
|
|
1143
|
-
* "TheoKit App" · version "0.0.0" · outDir ".theo" (next to dist).
|
|
1144
|
-
*
|
|
1145
|
-
* The env var `THEOKIT_OPENAPI_SERVERS` (CSV of URLs) overrides
|
|
1146
|
-
* `servers[].url` at emit time without rebuilding the config.
|
|
1147
|
-
*/
|
|
1148
|
-
openapi: z.object({
|
|
1149
|
-
servers: z.array(
|
|
1150
|
-
z.object({
|
|
1151
|
-
url: z.string().url(),
|
|
1152
|
-
description: z.string().optional()
|
|
1153
|
-
})
|
|
1154
|
-
).default([{ url: "http://localhost:3000", description: "Local development" }]),
|
|
1155
|
-
specVersion: z.enum(["3.1.0", "3.0.3"]).default("3.1.0"),
|
|
1156
|
-
title: z.string().default("TheoKit App"),
|
|
1157
|
-
version: z.string().default("0.0.0"),
|
|
1158
|
-
outDir: z.string().default(".theo")
|
|
1159
|
-
}).optional()
|
|
1160
|
-
}).refine((cfg) => !Object.values(cfg.services).some((s) => s.port === cfg.port), {
|
|
1161
|
-
message: "service.port collides with TheoKit web port \u2014 change one of them",
|
|
1162
|
-
path: ["services"]
|
|
1163
|
-
});
|
|
1164
|
-
|
|
1165
|
-
// src/config/load-config.ts
|
|
1166
|
-
var CONFIG_FILE = "theo.config.ts";
|
|
1167
|
-
function deepMerge(base, override) {
|
|
1168
|
-
const result = { ...base };
|
|
1169
|
-
for (const key of Object.keys(override)) {
|
|
1170
|
-
if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
|
|
1171
|
-
const baseVal = base[key];
|
|
1172
|
-
const overVal = override[key];
|
|
1173
|
-
if (overVal !== null && typeof overVal === "object" && !Array.isArray(overVal) && baseVal !== null && typeof baseVal === "object" && !Array.isArray(baseVal)) {
|
|
1174
|
-
result[key] = deepMerge(
|
|
1175
|
-
baseVal,
|
|
1176
|
-
overVal
|
|
1271
|
+
);
|
|
1272
|
+
const configPath = resolve6(ctx.projectRoot, "theo.config.ts");
|
|
1273
|
+
server.watcher.on("change", (file) => {
|
|
1274
|
+
if (file === configPath) {
|
|
1275
|
+
console.warn(
|
|
1276
|
+
"\n[theokit] theo.config.ts changed; restart dev server for plugin changes to take effect\n"
|
|
1177
1277
|
);
|
|
1178
|
-
} else {
|
|
1179
|
-
result[key] = overVal;
|
|
1180
1278
|
}
|
|
1279
|
+
});
|
|
1280
|
+
if (ctx.ssrEnabled) {
|
|
1281
|
+
setupSsrDevMiddleware(server, {
|
|
1282
|
+
projectRoot: ctx.projectRoot,
|
|
1283
|
+
virtualEntryServerId: ctx.virtualEntryServerId,
|
|
1284
|
+
securityHeaders: ctx.securityHeaders
|
|
1285
|
+
});
|
|
1181
1286
|
}
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1287
|
+
function handleRouteChange(filePath) {
|
|
1288
|
+
if (!isRouteFile(basename(filePath))) return;
|
|
1289
|
+
if (!filePath.startsWith(ctx.appDir)) return;
|
|
1290
|
+
const mod = server.moduleGraph.getModuleById(ctx.resolvedManifestId);
|
|
1291
|
+
if (mod) {
|
|
1292
|
+
server.moduleGraph.invalidateModule(mod);
|
|
1293
|
+
server.ws.send({ type: "full-reload" });
|
|
1294
|
+
}
|
|
1189
1295
|
}
|
|
1190
|
-
|
|
1296
|
+
server.watcher.on("add", handleRouteChange);
|
|
1297
|
+
server.watcher.on("unlink", handleRouteChange);
|
|
1298
|
+
if (ctx.resolvedOpenApi !== void 0) {
|
|
1299
|
+
const openApiCfg = ctx.resolvedOpenApi;
|
|
1300
|
+
const { reEmitOpenApi } = await import("./dev-emit-5VPRCHOD.js");
|
|
1301
|
+
const openApiServerDir = resolve6(ctx.projectRoot, "server");
|
|
1302
|
+
const openApiDistDir = resolve6(ctx.projectRoot, ctx.resolvedDistDir);
|
|
1303
|
+
const isRouteFileForOpenApi = (file) => file.startsWith(openApiServerDir) && /\.(ts|tsx|js|mjs)$/.test(file);
|
|
1304
|
+
void reEmitOpenApi(openApiServerDir, openApiDistDir, openApiCfg);
|
|
1305
|
+
server.watcher.on("change", (file) => {
|
|
1306
|
+
if (isRouteFileForOpenApi(file)) {
|
|
1307
|
+
void reEmitOpenApi(openApiServerDir, openApiDistDir, openApiCfg);
|
|
1308
|
+
}
|
|
1309
|
+
});
|
|
1310
|
+
server.watcher.on("add", (file) => {
|
|
1311
|
+
if (isRouteFileForOpenApi(file)) {
|
|
1312
|
+
void reEmitOpenApi(openApiServerDir, openApiDistDir, openApiCfg);
|
|
1313
|
+
}
|
|
1314
|
+
});
|
|
1315
|
+
server.watcher.on("unlink", (file) => {
|
|
1316
|
+
if (isRouteFileForOpenApi(file)) {
|
|
1317
|
+
void reEmitOpenApi(openApiServerDir, openApiDistDir, openApiCfg);
|
|
1318
|
+
}
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
setupWsUpgrade(server, ctx.projectRoot);
|
|
1322
|
+
server.httpServer?.once("close", () => {
|
|
1323
|
+
;
|
|
1324
|
+
globalThis.__theoViteHotServer = void 0;
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
// src/vite-plugin/integrate-ui.ts
|
|
1329
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
|
|
1330
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
1331
|
+
import { pathToFileURL } from "url";
|
|
1332
|
+
|
|
1333
|
+
// src/vite-plugin/auto-detect.ts
|
|
1334
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
|
|
1335
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
1336
|
+
function isDeclared(name, projectRoot) {
|
|
1191
1337
|
try {
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1338
|
+
const raw = readFileSync3(join3(projectRoot, "package.json"), "utf-8");
|
|
1339
|
+
const pkg = JSON.parse(raw);
|
|
1340
|
+
return Boolean(
|
|
1341
|
+
pkg.dependencies?.[name] ?? pkg.devDependencies?.[name] ?? pkg.peerDependencies?.[name]
|
|
1342
|
+
);
|
|
1343
|
+
} catch {
|
|
1344
|
+
return false;
|
|
1195
1345
|
}
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1346
|
+
}
|
|
1347
|
+
function resolvePackageJson(name, cwd) {
|
|
1348
|
+
let dir = cwd;
|
|
1349
|
+
for (let depth = 0; depth < 10; depth++) {
|
|
1350
|
+
const pkgJsonPath = join3(dir, "node_modules", ...name.split("/"), "package.json");
|
|
1351
|
+
if (existsSync5(pkgJsonPath)) {
|
|
1352
|
+
try {
|
|
1353
|
+
const raw = readFileSync3(pkgJsonPath, "utf-8");
|
|
1354
|
+
const pkg = JSON.parse(raw);
|
|
1355
|
+
if (pkg.name === name) {
|
|
1356
|
+
return { path: pkgJsonPath, version: pkg.version };
|
|
1203
1357
|
}
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1358
|
+
} catch {
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
const parent = dirname2(dir);
|
|
1362
|
+
if (parent === dir) break;
|
|
1363
|
+
dir = parent;
|
|
1364
|
+
}
|
|
1365
|
+
return null;
|
|
1366
|
+
}
|
|
1367
|
+
function fallbackProbe(name, cwd) {
|
|
1368
|
+
const candidates = ["index.mjs", "dist/index.mjs", "dist/index.js", "index.js"];
|
|
1369
|
+
let dir = cwd;
|
|
1370
|
+
for (let depth = 0; depth < 10; depth++) {
|
|
1371
|
+
const pkgDir = join3(dir, "node_modules", ...name.split("/"));
|
|
1372
|
+
for (const candidate of candidates) {
|
|
1373
|
+
const entry = join3(pkgDir, candidate);
|
|
1374
|
+
if (existsSync5(entry)) return { resolvedEntry: entry };
|
|
1375
|
+
}
|
|
1376
|
+
const parent = dirname2(dir);
|
|
1377
|
+
if (parent === dir) break;
|
|
1378
|
+
dir = parent;
|
|
1379
|
+
}
|
|
1380
|
+
return null;
|
|
1381
|
+
}
|
|
1382
|
+
function findOwningPackageJson(entry, expectedName) {
|
|
1383
|
+
let dir = dirname2(entry);
|
|
1384
|
+
for (let i = 0; i < 10; i++) {
|
|
1385
|
+
const candidate = join3(dir, "package.json");
|
|
1386
|
+
if (existsSync5(candidate)) {
|
|
1387
|
+
try {
|
|
1388
|
+
const raw = readFileSync3(candidate, "utf-8");
|
|
1389
|
+
const pkg = JSON.parse(raw);
|
|
1390
|
+
if (pkg.name === expectedName) {
|
|
1391
|
+
return { path: candidate, version: pkg.version };
|
|
1392
|
+
}
|
|
1393
|
+
} catch {
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
const parent = dirname2(dir);
|
|
1397
|
+
if (parent === dir) break;
|
|
1398
|
+
dir = parent;
|
|
1399
|
+
}
|
|
1400
|
+
return null;
|
|
1401
|
+
}
|
|
1402
|
+
function detectPackage(name, cwd) {
|
|
1403
|
+
if (!isDeclared(name, cwd)) {
|
|
1404
|
+
return { installed: false };
|
|
1405
|
+
}
|
|
1406
|
+
const pkgJson = resolvePackageJson(name, cwd);
|
|
1407
|
+
if (pkgJson) {
|
|
1408
|
+
return {
|
|
1409
|
+
installed: true,
|
|
1410
|
+
version: pkgJson.version,
|
|
1411
|
+
resolvedPath: pkgJson.path
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
let dir = cwd;
|
|
1415
|
+
for (let i = 0; i < 10; i++) {
|
|
1416
|
+
const nmPath = join3(dir, "node_modules", ...name.split("/"));
|
|
1417
|
+
const nmPkgJson = join3(nmPath, "package.json");
|
|
1418
|
+
if (existsSync5(nmPkgJson)) {
|
|
1419
|
+
try {
|
|
1420
|
+
const raw = readFileSync3(nmPkgJson, "utf-8");
|
|
1421
|
+
const pkg = JSON.parse(raw);
|
|
1422
|
+
if (pkg.name === name) {
|
|
1423
|
+
return { installed: true, version: pkg.version, resolvedPath: nmPkgJson };
|
|
1424
|
+
}
|
|
1425
|
+
} catch {
|
|
1426
|
+
}
|
|
1427
|
+
return { installed: true };
|
|
1428
|
+
}
|
|
1429
|
+
const parent = dirname2(dir);
|
|
1430
|
+
if (parent === dir) break;
|
|
1431
|
+
dir = parent;
|
|
1432
|
+
}
|
|
1433
|
+
const probe = fallbackProbe(name, cwd);
|
|
1434
|
+
if (probe) {
|
|
1435
|
+
const owning = findOwningPackageJson(probe.resolvedEntry, name);
|
|
1436
|
+
if (owning) {
|
|
1437
|
+
return { installed: true, version: owning.version, resolvedPath: owning.path };
|
|
1438
|
+
}
|
|
1439
|
+
return { installed: true };
|
|
1207
1440
|
}
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1441
|
+
return { installed: false };
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
// src/vite-plugin/integrate-ui.ts
|
|
1445
|
+
function resolveConsumerImport(name, cwd) {
|
|
1446
|
+
let dir = cwd;
|
|
1447
|
+
for (let i = 0; i < 10; i++) {
|
|
1448
|
+
const pkgDir = join4(dir, "node_modules", ...name.split("/"));
|
|
1449
|
+
const pkgJsonPath = join4(pkgDir, "package.json");
|
|
1450
|
+
if (existsSync6(pkgJsonPath)) {
|
|
1214
1451
|
try {
|
|
1215
|
-
const
|
|
1216
|
-
const
|
|
1217
|
-
|
|
1218
|
-
|
|
1452
|
+
const raw = readFileSync4(pkgJsonPath, "utf-8");
|
|
1453
|
+
const pkg = JSON.parse(raw);
|
|
1454
|
+
let entry;
|
|
1455
|
+
const dotExport = pkg.exports?.["."];
|
|
1456
|
+
if (typeof dotExport === "string") entry = dotExport;
|
|
1457
|
+
else if (dotExport && typeof dotExport === "object") {
|
|
1458
|
+
entry = dotExport.import ?? dotExport.default;
|
|
1219
1459
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1460
|
+
entry ??= pkg.module ?? pkg.main;
|
|
1461
|
+
if (!entry) return null;
|
|
1462
|
+
const cleaned = entry.replace(/^\.\//, "");
|
|
1463
|
+
return pathToFileURL(join4(pkgDir, cleaned)).href;
|
|
1464
|
+
} catch {
|
|
1465
|
+
return null;
|
|
1222
1466
|
}
|
|
1223
1467
|
}
|
|
1468
|
+
const parent = dirname3(dir);
|
|
1469
|
+
if (parent === dir) break;
|
|
1470
|
+
dir = parent;
|
|
1224
1471
|
}
|
|
1225
|
-
|
|
1226
|
-
if (!result.success) {
|
|
1227
|
-
const issues = result.error.issues.map((i) => ({
|
|
1228
|
-
field: i.path.join("."),
|
|
1229
|
-
message: i.message
|
|
1230
|
-
}));
|
|
1231
|
-
throw new TheoConfigError(issues, configPath);
|
|
1232
|
-
}
|
|
1233
|
-
return result.data;
|
|
1472
|
+
return null;
|
|
1234
1473
|
}
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
return {
|
|
1241
|
-
theme: raw?.theme ?? "violet-forge",
|
|
1242
|
-
fonts: raw?.fonts ?? "bundled"
|
|
1243
|
-
};
|
|
1474
|
+
function isValidPlugin(value) {
|
|
1475
|
+
if (value === null || typeof value !== "object") return false;
|
|
1476
|
+
if (Array.isArray(value)) return false;
|
|
1477
|
+
if (!("name" in value)) return false;
|
|
1478
|
+
return typeof value.name === "string";
|
|
1244
1479
|
}
|
|
1245
|
-
function
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
const exp = pkg.exports?.[exportKey];
|
|
1252
|
-
if (!exp) {
|
|
1253
|
-
const fallback = join2(pkgRoot, "dist", subpath);
|
|
1254
|
-
return existsSync3(fallback) ? fallback : null;
|
|
1255
|
-
}
|
|
1256
|
-
let target;
|
|
1257
|
-
if (typeof exp === "string") target = exp;
|
|
1258
|
-
else if (typeof exp === "object") {
|
|
1259
|
-
const e = exp;
|
|
1260
|
-
target = e.import ?? e.default;
|
|
1480
|
+
function normalizePluginReturn(value) {
|
|
1481
|
+
if (Array.isArray(value)) {
|
|
1482
|
+
const result = [];
|
|
1483
|
+
for (const entry of value) {
|
|
1484
|
+
if (!isValidPlugin(entry)) return null;
|
|
1485
|
+
result.push(entry);
|
|
1261
1486
|
}
|
|
1262
|
-
|
|
1263
|
-
const cleaned = target.replace(/^\.\//, "");
|
|
1264
|
-
const candidate = join2(pkgRoot, cleaned);
|
|
1265
|
-
return existsSync3(candidate) ? candidate : null;
|
|
1266
|
-
} catch {
|
|
1267
|
-
return null;
|
|
1487
|
+
return result.length > 0 ? result : null;
|
|
1268
1488
|
}
|
|
1489
|
+
if (isValidPlugin(value)) return [value];
|
|
1490
|
+
return null;
|
|
1269
1491
|
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
const
|
|
1273
|
-
if (!
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
if (existsSync3(pkgRoot)) {
|
|
1280
|
-
const resolved = resolveExportSubpath(pkgRoot, subpath);
|
|
1281
|
-
if (resolved) return true;
|
|
1282
|
-
}
|
|
1283
|
-
const parent = dirname(dir);
|
|
1284
|
-
if (parent === dir) break;
|
|
1285
|
-
dir = parent;
|
|
1492
|
+
async function integrateUseTheoUI(cwd, opts) {
|
|
1493
|
+
if (opts?.enabled === false) return [];
|
|
1494
|
+
const uiDetect = detectPackage("@theokit/ui", cwd);
|
|
1495
|
+
if (!uiDetect.installed) return [];
|
|
1496
|
+
if (opts?.consumerTailwindConfig || opts?.consumerPostcssConfig) {
|
|
1497
|
+
console.info(
|
|
1498
|
+
`[theokit] Detected your tailwind.config / postcss.config \u2014 skipping auto-config. Extend with \`import preset from '@theokit/ui/preset'\` to apply UI theme.`
|
|
1499
|
+
);
|
|
1500
|
+
return [];
|
|
1286
1501
|
}
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1502
|
+
const tailwindDetect = detectPackage("@tailwindcss/vite", cwd);
|
|
1503
|
+
if (!tailwindDetect.installed) {
|
|
1504
|
+
console.warn(
|
|
1505
|
+
`[theokit] @theokit/ui detected but @tailwindcss/vite is not installed. Run \`pnpm add -D @tailwindcss/vite\` to enable styling.`
|
|
1506
|
+
);
|
|
1507
|
+
return [];
|
|
1508
|
+
}
|
|
1509
|
+
const tailwindUrl = resolveConsumerImport("@tailwindcss/vite", cwd);
|
|
1510
|
+
if (!tailwindUrl) {
|
|
1511
|
+
console.warn(
|
|
1512
|
+
`[theokit] @tailwindcss/vite installed but its package entry was not resolvable from ${cwd}. Skipping auto-config.`
|
|
1513
|
+
);
|
|
1514
|
+
return [];
|
|
1515
|
+
}
|
|
1516
|
+
let tailwindMod;
|
|
1517
|
+
let uiMod;
|
|
1290
1518
|
try {
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1519
|
+
tailwindMod = await import(tailwindUrl);
|
|
1520
|
+
} catch (err) {
|
|
1521
|
+
console.warn(
|
|
1522
|
+
`[theokit] @tailwindcss/vite dynamic import failed (${err.message}). Skipping auto-config.`
|
|
1295
1523
|
);
|
|
1296
|
-
|
|
1297
|
-
return false;
|
|
1524
|
+
return [];
|
|
1298
1525
|
}
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
const
|
|
1302
|
-
if (
|
|
1303
|
-
|
|
1526
|
+
const uiPkgInstalled = detectPackage("@theokit/ui", cwd);
|
|
1527
|
+
const uiPkgDir = uiPkgInstalled.resolvedPath ? uiPkgInstalled.resolvedPath.replace(/[\\/]package\.json$/, "") : null;
|
|
1528
|
+
const uiPluginPath = uiPkgDir ? join4(uiPkgDir, "dist", "vite-plugin.js") : null;
|
|
1529
|
+
if (!uiPluginPath || !existsSync6(uiPluginPath)) {
|
|
1530
|
+
console.warn(
|
|
1531
|
+
`[theokit] @theokit/ui/vite-plugin entry not found at ${String(uiPluginPath)}. Skipping auto-config.`
|
|
1532
|
+
);
|
|
1533
|
+
return [];
|
|
1304
1534
|
}
|
|
1305
|
-
|
|
1306
|
-
|
|
1535
|
+
try {
|
|
1536
|
+
uiMod = await import(pathToFileURL(uiPluginPath).href);
|
|
1537
|
+
} catch (err) {
|
|
1538
|
+
console.warn(
|
|
1539
|
+
`[theokit] @theokit/ui/vite-plugin dynamic import failed (${err.message}). Skipping auto-config.`
|
|
1540
|
+
);
|
|
1541
|
+
return [];
|
|
1307
1542
|
}
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1543
|
+
if (typeof tailwindMod.default !== "function") {
|
|
1544
|
+
console.warn(
|
|
1545
|
+
`[theokit] @tailwindcss/vite does not expose a default-export function. Skipping auto-config.`
|
|
1546
|
+
);
|
|
1547
|
+
return [];
|
|
1313
1548
|
}
|
|
1314
|
-
|
|
1549
|
+
if (typeof uiMod.default !== "function") {
|
|
1550
|
+
console.warn(
|
|
1551
|
+
`[theokit] @theokit/ui/vite-plugin does not expose a default-export function. Skipping auto-config.`
|
|
1552
|
+
);
|
|
1553
|
+
return [];
|
|
1554
|
+
}
|
|
1555
|
+
let tailwindPlugin;
|
|
1556
|
+
let uiPlugin;
|
|
1557
|
+
try {
|
|
1558
|
+
tailwindPlugin = tailwindMod.default();
|
|
1559
|
+
} catch (err) {
|
|
1560
|
+
console.warn(`[theokit] @tailwindcss/vite() threw: ${err.message}`);
|
|
1561
|
+
return [];
|
|
1562
|
+
}
|
|
1563
|
+
try {
|
|
1564
|
+
uiPlugin = uiMod.default({
|
|
1565
|
+
tailwind: false
|
|
1566
|
+
});
|
|
1567
|
+
} catch (err) {
|
|
1568
|
+
console.warn(`[theokit] @theokit/ui/vite-plugin() threw: ${err.message}`);
|
|
1569
|
+
return [];
|
|
1570
|
+
}
|
|
1571
|
+
const tailwindPlugins = normalizePluginReturn(tailwindPlugin);
|
|
1572
|
+
if (tailwindPlugins === null) {
|
|
1573
|
+
console.warn(`[theokit] @tailwindcss/vite returned unexpected shape. Skipping auto-config.`);
|
|
1574
|
+
return [];
|
|
1575
|
+
}
|
|
1576
|
+
const uiPlugins = normalizePluginReturn(uiPlugin);
|
|
1577
|
+
if (uiPlugins === null) {
|
|
1578
|
+
console.warn(
|
|
1579
|
+
`[theokit] @theokit/ui/vite-plugin returned unexpected shape. Skipping auto-config.`
|
|
1580
|
+
);
|
|
1581
|
+
return [];
|
|
1582
|
+
}
|
|
1583
|
+
return [...tailwindPlugins, ...uiPlugins];
|
|
1315
1584
|
}
|
|
1316
1585
|
|
|
1317
|
-
// src/vite-plugin/
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
csrfMode: "strict",
|
|
1324
|
-
securityHeaders: void 0,
|
|
1325
|
-
disallowed: void 0,
|
|
1326
|
-
cors: void 0,
|
|
1327
|
-
auditLogger: void 0,
|
|
1328
|
-
devtoolsEnabled: true,
|
|
1329
|
-
openapi: void 0,
|
|
1330
|
-
distDir: ".theo"
|
|
1331
|
-
};
|
|
1332
|
-
async function resolvePluginConfig(projectRoot) {
|
|
1333
|
-
try {
|
|
1334
|
-
const userConfig = await loadConfig(projectRoot);
|
|
1335
|
-
const pluginRunner = await createPluginRunnerFromConfig(userConfig.plugins);
|
|
1336
|
-
const transformer = resolveTransformer(userConfig.serialization);
|
|
1337
|
-
let resolvedBatching;
|
|
1338
|
-
if (userConfig.batching === true) {
|
|
1339
|
-
resolvedBatching = {};
|
|
1340
|
-
} else if (typeof userConfig.batching === "object") {
|
|
1341
|
-
resolvedBatching = userConfig.batching;
|
|
1342
|
-
}
|
|
1343
|
-
const theoUi = detectTheoUi(projectRoot, userConfig.ui);
|
|
1344
|
-
let auditLogger;
|
|
1345
|
-
const maybeLogger = userConfig.audit?.logger;
|
|
1346
|
-
if (maybeLogger && typeof maybeLogger.log === "function") {
|
|
1347
|
-
auditLogger = maybeLogger;
|
|
1348
|
-
}
|
|
1349
|
-
return {
|
|
1350
|
-
pluginRunner,
|
|
1351
|
-
transformer,
|
|
1352
|
-
resolvedBatching,
|
|
1353
|
-
theoUi,
|
|
1354
|
-
csrfMode: userConfig.security?.csrf ?? "strict",
|
|
1355
|
-
securityHeaders: userConfig.security?.headers,
|
|
1356
|
-
disallowed: userConfig.security?.disallowed,
|
|
1357
|
-
cors: userConfig.security?.cors,
|
|
1358
|
-
auditLogger,
|
|
1359
|
-
devtoolsEnabled: userConfig.devtools !== false,
|
|
1360
|
-
openapi: userConfig.openapi,
|
|
1361
|
-
distDir: userConfig.distDir
|
|
1362
|
-
};
|
|
1363
|
-
} catch {
|
|
1364
|
-
return DEFAULT_RESOLVED;
|
|
1586
|
+
// src/vite-plugin/resolve-theo-root.ts
|
|
1587
|
+
import { existsSync as existsSync7 } from "fs";
|
|
1588
|
+
import { resolve as resolve7 } from "path";
|
|
1589
|
+
function resolveTheoRootDir(currentDir) {
|
|
1590
|
+
if (existsSync7(resolve7(currentDir, "client"))) {
|
|
1591
|
+
return currentDir;
|
|
1365
1592
|
}
|
|
1593
|
+
return resolve7(currentDir, "..");
|
|
1366
1594
|
}
|
|
1367
1595
|
|
|
1596
|
+
// src/vite-plugin/transform-html-hook.ts
|
|
1597
|
+
import { existsSync as existsSync8 } from "fs";
|
|
1598
|
+
import { resolve as resolve8 } from "path";
|
|
1599
|
+
|
|
1368
1600
|
// src/vite-plugin/inject-devtools.ts
|
|
1369
1601
|
var DEVTOOLS_VIRTUAL_ID = "/@theo/devtools/entry.js";
|
|
1370
1602
|
var DEVTOOLS_RESOLVED_ID = "\0@theo/devtools/entry.js";
|
|
@@ -1450,385 +1682,461 @@ function injectStylesheets(html, opts) {
|
|
|
1450
1682
|
${html}`, injected: tagsToInject };
|
|
1451
1683
|
}
|
|
1452
1684
|
|
|
1453
|
-
// src/vite-plugin/
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1685
|
+
// src/vite-plugin/transform-html-hook.ts
|
|
1686
|
+
function runTransformIndexHtml(html, ctx) {
|
|
1687
|
+
const entry = injectEntryClient(html);
|
|
1688
|
+
if (entry.warning) console.warn(entry.warning);
|
|
1689
|
+
let next = entry.html;
|
|
1690
|
+
const devtools = injectDevtoolsScript(next, {
|
|
1691
|
+
isDev: ctx.isDevMode.value,
|
|
1692
|
+
enabled: ctx.devtoolsEnabled
|
|
1693
|
+
});
|
|
1694
|
+
if (devtools.warning) console.warn(devtools.warning);
|
|
1695
|
+
next = devtools.html;
|
|
1696
|
+
const styles = injectStylesheets(next, {
|
|
1697
|
+
isDev: ctx.isDevMode.value,
|
|
1698
|
+
hasPackage: (name) => existsSync8(resolve8(ctx.projectRoot, "node_modules", ...name.split("/")))
|
|
1699
|
+
});
|
|
1700
|
+
next = styles.html;
|
|
1701
|
+
return next;
|
|
1702
|
+
}
|
|
1457
1703
|
|
|
1458
|
-
// src/
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
try {
|
|
1463
|
-
const raw = readFileSync2(join3(projectRoot, "package.json"), "utf-8");
|
|
1464
|
-
const pkg = JSON.parse(raw);
|
|
1465
|
-
return Boolean(
|
|
1466
|
-
pkg.dependencies?.[name] ?? pkg.devDependencies?.[name] ?? pkg.peerDependencies?.[name]
|
|
1467
|
-
);
|
|
1468
|
-
} catch {
|
|
1469
|
-
return false;
|
|
1704
|
+
// src/router/entry-server.ts
|
|
1705
|
+
function generateEntryServer(options = {}) {
|
|
1706
|
+
if (options.streaming) {
|
|
1707
|
+
return generateStreamingEntry(options);
|
|
1470
1708
|
}
|
|
1709
|
+
return generateSingleShotEntry(options);
|
|
1471
1710
|
}
|
|
1472
|
-
function
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
}
|
|
1483
|
-
} catch {
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
const parent = dirname2(dir);
|
|
1487
|
-
if (parent === dir) break;
|
|
1488
|
-
dir = parent;
|
|
1711
|
+
function buildAppTreeJs(options) {
|
|
1712
|
+
const theme = options.theoUi?.theme ?? "violet-forge";
|
|
1713
|
+
if (options.theoUi) {
|
|
1714
|
+
return [
|
|
1715
|
+
`React.createElement(TheoUIProvider, { theme: { defaultTheme: '${theme}' } },`,
|
|
1716
|
+
` React.createElement(Suspense, { fallback: null },`,
|
|
1717
|
+
` React.createElement(StaticRouterProvider, { router, context, hydrate: false })`,
|
|
1718
|
+
` )`,
|
|
1719
|
+
` )`
|
|
1720
|
+
].join("\n");
|
|
1489
1721
|
}
|
|
1490
|
-
return null
|
|
1722
|
+
return `React.createElement(Suspense, { fallback: null },
|
|
1723
|
+
React.createElement(StaticRouterProvider, { router, context, hydrate: false })
|
|
1724
|
+
)`;
|
|
1491
1725
|
}
|
|
1492
|
-
function
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
}
|
|
1501
|
-
const parent = dirname2(dir);
|
|
1502
|
-
if (parent === dir) break;
|
|
1503
|
-
dir = parent;
|
|
1504
|
-
}
|
|
1505
|
-
return null;
|
|
1726
|
+
function hydrationDataExtractSnippet() {
|
|
1727
|
+
return [
|
|
1728
|
+
` const hydrationData = {`,
|
|
1729
|
+
` loaderData: context.loaderData,`,
|
|
1730
|
+
` actionData: context.actionData,`,
|
|
1731
|
+
` errors: context.errors,`,
|
|
1732
|
+
` }`
|
|
1733
|
+
].join("\n");
|
|
1506
1734
|
}
|
|
1507
|
-
function
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
const
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1735
|
+
function generateSingleShotEntry(options) {
|
|
1736
|
+
const theoUiImport = options.theoUi ? `import { TheoUIProvider } from '@theokit/ui'
|
|
1737
|
+
` : "";
|
|
1738
|
+
const appTree = buildAppTreeJs(options);
|
|
1739
|
+
return [
|
|
1740
|
+
`import React, { Suspense } from 'react'`,
|
|
1741
|
+
`import { renderToPipeableStream } from 'react-dom/server'`,
|
|
1742
|
+
`import { createStaticHandler, createStaticRouter, StaticRouterProvider } from 'react-router'`,
|
|
1743
|
+
`import { PassThrough } from 'node:stream'`,
|
|
1744
|
+
`import { routes } from '/@theo/route-manifest'`,
|
|
1745
|
+
theoUiImport,
|
|
1746
|
+
`export async function render(url, options = {}) {`,
|
|
1747
|
+
` const handler = createStaticHandler(routes)`,
|
|
1748
|
+
` const request = new Request('http://localhost' + url)`,
|
|
1749
|
+
` const context = await handler.query(request)`,
|
|
1750
|
+
``,
|
|
1751
|
+
` if (context instanceof Response) {`,
|
|
1752
|
+
` return { redirect: context }`,
|
|
1753
|
+
` }`,
|
|
1754
|
+
``,
|
|
1755
|
+
` const router = createStaticRouter(handler.dataRoutes, context)`,
|
|
1756
|
+
` const app = ${appTree}`,
|
|
1757
|
+
``,
|
|
1758
|
+
hydrationDataExtractSnippet(),
|
|
1759
|
+
``,
|
|
1760
|
+
` return new Promise((resolve, reject) => {`,
|
|
1761
|
+
` let html = ''`,
|
|
1762
|
+
` let piped = false`,
|
|
1763
|
+
` const passthrough = new PassThrough()`,
|
|
1764
|
+
` passthrough.on('data', (chunk) => { html += chunk.toString() })`,
|
|
1765
|
+
` passthrough.on('end', () => { resolve({ html, hydrationData }) })`,
|
|
1766
|
+
` passthrough.on('error', reject)`,
|
|
1767
|
+
``,
|
|
1768
|
+
` // Pipe on onShellReady (Next.js pattern). Calling pipe() twice`,
|
|
1769
|
+
` // throws "React currently only supports piping to one writable`,
|
|
1770
|
+
` // stream". The \`piped\` flag is a belt-and-suspenders guard if`,
|
|
1771
|
+
` // onShellReady fires unexpectedly more than once.`,
|
|
1772
|
+
` // Forward options.nonce to React so every <script> tag React`,
|
|
1773
|
+
` // emits (Suspense boundary scripts) carries the nonce attribute.`,
|
|
1774
|
+
` const { pipe } = renderToPipeableStream(app, {`,
|
|
1775
|
+
` nonce: options.nonce,`,
|
|
1776
|
+
` onShellReady() { if (!piped) { piped = true; pipe(passthrough) } },`,
|
|
1777
|
+
` onShellError(err) { reject(err) },`,
|
|
1778
|
+
` onError(err) { console.error('[SSR Error]', err) },`,
|
|
1779
|
+
` })`,
|
|
1780
|
+
` })`,
|
|
1781
|
+
`}`
|
|
1782
|
+
].join("\n");
|
|
1783
|
+
}
|
|
1784
|
+
function streamingWebRenderer(appTree) {
|
|
1785
|
+
return [
|
|
1786
|
+
`// T2.3 \u2014 Web Standards streaming entry for edge runtimes (Cloudflare,`,
|
|
1787
|
+
`// Bun, Deno, Vercel Edge). Uses renderToReadableStream and returns a`,
|
|
1788
|
+
`// Response with the stream as body. Honors request.signal for client`,
|
|
1789
|
+
`// disconnect cleanup.`,
|
|
1790
|
+
`export async function renderStreamingWeb(request, options = {}) {`,
|
|
1791
|
+
` const handler = createStaticHandler(routes)`,
|
|
1792
|
+
` const url = new URL(request.url)`,
|
|
1793
|
+
` const context = await handler.query(request)`,
|
|
1794
|
+
``,
|
|
1795
|
+
` if (context instanceof Response) {`,
|
|
1796
|
+
` return context`,
|
|
1797
|
+
` }`,
|
|
1798
|
+
``,
|
|
1799
|
+
` const router = createStaticRouter(handler.dataRoutes, context)`,
|
|
1800
|
+
` const app = ${appTree}`,
|
|
1801
|
+
``,
|
|
1802
|
+
` const stream = await renderToReadableStream(app, {`,
|
|
1803
|
+
` signal: request.signal,`,
|
|
1804
|
+
` nonce: options.nonce,`,
|
|
1805
|
+
` onError(err) { console.error('[SSR Web Stream Error]', err) },`,
|
|
1806
|
+
` })`,
|
|
1807
|
+
` return new Response(stream, {`,
|
|
1808
|
+
` status: 200,`,
|
|
1809
|
+
` headers: {`,
|
|
1810
|
+
` 'Content-Type': 'text/html; charset=utf-8',`,
|
|
1811
|
+
` 'Transfer-Encoding': 'chunked',`,
|
|
1812
|
+
` },`,
|
|
1813
|
+
` })`,
|
|
1814
|
+
`}`
|
|
1815
|
+
];
|
|
1816
|
+
}
|
|
1817
|
+
function streamingNodeRenderer(appTree) {
|
|
1818
|
+
return [
|
|
1819
|
+
`// T6.1 \u2014 Node streaming SSR entry (opt-in via theo.config.ts > ssrStreaming: true)`,
|
|
1820
|
+
`// Flushes the shell as soon as it's ready, then streams Suspense boundaries.`,
|
|
1821
|
+
`// EC-11: respects request.signal for client-disconnect cleanup.`,
|
|
1822
|
+
`export async function renderStreaming(url, response, options = {}) {`,
|
|
1823
|
+
` const handler = createStaticHandler(routes)`,
|
|
1824
|
+
` const request = new Request('http://localhost' + url, { signal: options.signal })`,
|
|
1825
|
+
` const context = await handler.query(request)`,
|
|
1826
|
+
``,
|
|
1827
|
+
` if (context instanceof Response) {`,
|
|
1828
|
+
` return { redirect: context }`,
|
|
1829
|
+
` }`,
|
|
1830
|
+
``,
|
|
1831
|
+
` const router = createStaticRouter(handler.dataRoutes, context)`,
|
|
1832
|
+
` const app = ${appTree}`,
|
|
1833
|
+
``,
|
|
1834
|
+
` return new Promise((resolve, reject) => {`,
|
|
1835
|
+
` let didError = false`,
|
|
1836
|
+
` const stream = renderToPipeableStream(app, {`,
|
|
1837
|
+
` nonce: options.nonce,`,
|
|
1838
|
+
` onShellReady() {`,
|
|
1839
|
+
` response.statusCode = didError ? 500 : 200`,
|
|
1840
|
+
` response.setHeader('Content-Type', 'text/html; charset=utf-8')`,
|
|
1841
|
+
` response.setHeader('Transfer-Encoding', 'chunked')`,
|
|
1842
|
+
` stream.pipe(response)`,
|
|
1843
|
+
` resolve({ streaming: true })`,
|
|
1844
|
+
` },`,
|
|
1845
|
+
` onShellError(err) { reject(err) },`,
|
|
1846
|
+
` onError(err) {`,
|
|
1847
|
+
` didError = true`,
|
|
1848
|
+
` console.error('[SSR Stream Error]', err)`,
|
|
1849
|
+
` },`,
|
|
1850
|
+
` })`,
|
|
1851
|
+
``,
|
|
1852
|
+
` // EC-11: client disconnect cleanup`,
|
|
1853
|
+
` if (options.signal) {`,
|
|
1854
|
+
` options.signal.addEventListener('abort', () => { stream.abort() })`,
|
|
1855
|
+
` }`,
|
|
1856
|
+
` })`,
|
|
1857
|
+
`}`
|
|
1858
|
+
];
|
|
1859
|
+
}
|
|
1860
|
+
function backCompatRenderer(appTree) {
|
|
1861
|
+
return [
|
|
1862
|
+
`// Backward compatibility: keep the single-shot render export available so`,
|
|
1863
|
+
`// callers that always used 'render()' don't break when streaming is on.`,
|
|
1864
|
+
`export async function render(url, options = {}) {`,
|
|
1865
|
+
` const handler = createStaticHandler(routes)`,
|
|
1866
|
+
` const request = new Request('http://localhost' + url)`,
|
|
1867
|
+
` const context = await handler.query(request)`,
|
|
1868
|
+
``,
|
|
1869
|
+
` if (context instanceof Response) {`,
|
|
1870
|
+
` return { redirect: context }`,
|
|
1871
|
+
` }`,
|
|
1872
|
+
``,
|
|
1873
|
+
` const router = createStaticRouter(handler.dataRoutes, context)`,
|
|
1874
|
+
` const app = ${appTree}`,
|
|
1875
|
+
``,
|
|
1876
|
+
hydrationDataExtractSnippet(),
|
|
1877
|
+
``,
|
|
1878
|
+
` const { PassThrough } = await import('node:stream')`,
|
|
1879
|
+
` return new Promise((resolve, reject) => {`,
|
|
1880
|
+
` let html = ''`,
|
|
1881
|
+
` let piped = false`,
|
|
1882
|
+
` const passthrough = new PassThrough()`,
|
|
1883
|
+
` passthrough.on('data', (chunk) => { html += chunk.toString() })`,
|
|
1884
|
+
` passthrough.on('end', () => { resolve({ html, hydrationData }) })`,
|
|
1885
|
+
` passthrough.on('error', reject)`,
|
|
1886
|
+
``,
|
|
1887
|
+
` // Pipe on onShellReady (Next.js pattern). nonce forwarded.`,
|
|
1888
|
+
` const { pipe } = renderToPipeableStream(app, {`,
|
|
1889
|
+
` nonce: options.nonce,`,
|
|
1890
|
+
` onShellReady() { if (!piped) { piped = true; pipe(passthrough) } },`,
|
|
1891
|
+
` onShellError(err) { reject(err) },`,
|
|
1892
|
+
` onError(err) { console.error('[SSR Error]', err) },`,
|
|
1893
|
+
` })`,
|
|
1894
|
+
` })`,
|
|
1895
|
+
`}`
|
|
1896
|
+
];
|
|
1897
|
+
}
|
|
1898
|
+
function generateStreamingEntry(options) {
|
|
1899
|
+
const theoUiImport = options.theoUi ? `import { TheoUIProvider } from '@theokit/ui'
|
|
1900
|
+
` : "";
|
|
1901
|
+
const appTree = buildAppTreeJs(options);
|
|
1902
|
+
return [
|
|
1903
|
+
`import React, { Suspense } from 'react'`,
|
|
1904
|
+
`import { renderToPipeableStream, renderToReadableStream } from 'react-dom/server'`,
|
|
1905
|
+
`import { createStaticHandler, createStaticRouter, StaticRouterProvider } from 'react-router'`,
|
|
1906
|
+
`import { routes } from '/@theo/route-manifest'`,
|
|
1907
|
+
theoUiImport,
|
|
1908
|
+
``,
|
|
1909
|
+
...streamingWebRenderer(appTree),
|
|
1910
|
+
``,
|
|
1911
|
+
...streamingNodeRenderer(appTree),
|
|
1912
|
+
``,
|
|
1913
|
+
...backCompatRenderer(appTree)
|
|
1914
|
+
].join("\n");
|
|
1526
1915
|
}
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
const
|
|
1532
|
-
if (
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
if (existsSync4(nmPkgJson)) {
|
|
1544
|
-
try {
|
|
1545
|
-
const raw = readFileSync2(nmPkgJson, "utf-8");
|
|
1546
|
-
const pkg = JSON.parse(raw);
|
|
1547
|
-
if (pkg.name === name) {
|
|
1548
|
-
return { installed: true, version: pkg.version, resolvedPath: nmPkgJson };
|
|
1549
|
-
}
|
|
1550
|
-
} catch {
|
|
1551
|
-
}
|
|
1552
|
-
return { installed: true };
|
|
1553
|
-
}
|
|
1554
|
-
const parent = dirname2(dir);
|
|
1555
|
-
if (parent === dir) break;
|
|
1556
|
-
dir = parent;
|
|
1557
|
-
}
|
|
1558
|
-
const probe = fallbackProbe(name, cwd);
|
|
1559
|
-
if (probe) {
|
|
1560
|
-
const owning = findOwningPackageJson(probe.resolvedEntry, name);
|
|
1561
|
-
if (owning) {
|
|
1562
|
-
return { installed: true, version: owning.version, resolvedPath: owning.path };
|
|
1563
|
-
}
|
|
1564
|
-
return { installed: true };
|
|
1916
|
+
|
|
1917
|
+
// src/router/entry.ts
|
|
1918
|
+
function generateEntryClient(ssr, opts = {}) {
|
|
1919
|
+
const rootMethod = ssr ? "hydrateRoot" : "createRoot";
|
|
1920
|
+
const theoUiImports = [];
|
|
1921
|
+
if (opts.theoUi) {
|
|
1922
|
+
theoUiImports.push(
|
|
1923
|
+
`// T2.2 \u2014 TheoUI CSS auto-injected (config.ui)`,
|
|
1924
|
+
`import '@theokit/ui/styles.css'`
|
|
1925
|
+
);
|
|
1926
|
+
const fontsModule = opts.theoUi.fonts === "cdn" ? "fonts-cdn.css" : "fonts.css";
|
|
1927
|
+
theoUiImports.push(
|
|
1928
|
+
`import '@theokit/ui/${fontsModule}'`,
|
|
1929
|
+
`// T2.3 \u2014 TheoUIProvider auto-wraps RouterProvider`,
|
|
1930
|
+
`import { TheoUIProvider } from '@theokit/ui'`
|
|
1931
|
+
);
|
|
1565
1932
|
}
|
|
1566
|
-
|
|
1933
|
+
const theme = opts.theoUi?.theme ?? "violet-forge";
|
|
1934
|
+
const routerTree = opts.theoUi ? `React.createElement(TheoUIProvider, { theme: { defaultTheme: '${theme}' } },
|
|
1935
|
+
React.createElement(Suspense, { fallback: null },
|
|
1936
|
+
React.createElement(RouterProvider, { router })
|
|
1937
|
+
)
|
|
1938
|
+
)` : `React.createElement(Suspense, { fallback: null },
|
|
1939
|
+
React.createElement(RouterProvider, { router })
|
|
1940
|
+
)`;
|
|
1941
|
+
const renderCall = ssr ? ` ${rootMethod}(el,
|
|
1942
|
+
${routerTree}
|
|
1943
|
+
)` : ` ${rootMethod}(el).render(
|
|
1944
|
+
${routerTree}
|
|
1945
|
+
)`;
|
|
1946
|
+
const hydrationLine = ssr ? `const router = createBrowserRouter(routes, { hydrationData: window.__staticRouterHydrationData })` : `const router = createBrowserRouter(routes)`;
|
|
1947
|
+
const preloadBlock = ssr ? [
|
|
1948
|
+
`// Phase 4 \u2014 preload matched-route modules before hydrate (EC-3 safeguard)`,
|
|
1949
|
+
`const __theoMatches = matchRoutes(routes, window.location.pathname) ?? []`,
|
|
1950
|
+
`const __theoPreloadPaths = __theoMatches`,
|
|
1951
|
+
` .map((m) => m.route && (m.route).path)`,
|
|
1952
|
+
` .filter((p) => typeof p === 'string' && p in __theoPreloadMap)`,
|
|
1953
|
+
`const __theoPreloadPromise = Promise.all(`,
|
|
1954
|
+
` __theoPreloadPaths.map((p) => __theoPreloadMap[p]().catch((err) => { console.error('[theo] preload failed', p, err); return null }))`,
|
|
1955
|
+
`)`,
|
|
1956
|
+
`const __theoTimeout = new Promise((resolve) => setTimeout(() => resolve('timeout'), 1500))`,
|
|
1957
|
+
`await Promise.race([__theoPreloadPromise, __theoTimeout])`,
|
|
1958
|
+
""
|
|
1959
|
+
] : [];
|
|
1960
|
+
const renderBlock = ssr ? [
|
|
1961
|
+
`if (el) {`,
|
|
1962
|
+
` ;(async () => {`,
|
|
1963
|
+
...preloadBlock.map((l) => " " + l),
|
|
1964
|
+
renderCall,
|
|
1965
|
+
` })()`,
|
|
1966
|
+
`}`
|
|
1967
|
+
] : [`if (el) {`, renderCall, `}`];
|
|
1968
|
+
const manifestImports = ssr ? `import { routes, __theoPreloadMap } from '/@theo/route-manifest'` : `import { routes } from '/@theo/route-manifest'`;
|
|
1969
|
+
const reactRouterImports = ssr ? `import { createBrowserRouter, RouterProvider, matchRoutes } from 'react-router'` : `import { createBrowserRouter, RouterProvider } from 'react-router'`;
|
|
1970
|
+
return [
|
|
1971
|
+
`import React, { Suspense } from 'react'`,
|
|
1972
|
+
`import { ${rootMethod} } from 'react-dom/client'`,
|
|
1973
|
+
reactRouterImports,
|
|
1974
|
+
manifestImports,
|
|
1975
|
+
`// T1.3 \u2014 side-effect import sets globalThis.__THEO_TRANSFORMER__ for theoFetch`,
|
|
1976
|
+
`import '/@theo/runtime-config'`,
|
|
1977
|
+
...theoUiImports,
|
|
1978
|
+
``,
|
|
1979
|
+
hydrationLine,
|
|
1980
|
+
`const el = document.getElementById('root')`,
|
|
1981
|
+
...renderBlock
|
|
1982
|
+
].join("\n");
|
|
1567
1983
|
}
|
|
1568
1984
|
|
|
1569
|
-
// src/
|
|
1570
|
-
function
|
|
1571
|
-
|
|
1572
|
-
for (let i = 0; i < 10; i++) {
|
|
1573
|
-
const pkgDir = join4(dir, "node_modules", ...name.split("/"));
|
|
1574
|
-
const pkgJsonPath = join4(pkgDir, "package.json");
|
|
1575
|
-
if (existsSync5(pkgJsonPath)) {
|
|
1576
|
-
try {
|
|
1577
|
-
const raw = readFileSync3(pkgJsonPath, "utf-8");
|
|
1578
|
-
const pkg = JSON.parse(raw);
|
|
1579
|
-
let entry;
|
|
1580
|
-
const dotExport = pkg.exports?.["."];
|
|
1581
|
-
if (typeof dotExport === "string") entry = dotExport;
|
|
1582
|
-
else if (dotExport && typeof dotExport === "object") {
|
|
1583
|
-
entry = dotExport.import ?? dotExport.default;
|
|
1584
|
-
}
|
|
1585
|
-
entry ??= pkg.module ?? pkg.main;
|
|
1586
|
-
if (!entry) return null;
|
|
1587
|
-
const cleaned = entry.replace(/^\.\//, "");
|
|
1588
|
-
return pathToFileURL2(join4(pkgDir, cleaned)).href;
|
|
1589
|
-
} catch {
|
|
1590
|
-
return null;
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1593
|
-
const parent = dirname3(dir);
|
|
1594
|
-
if (parent === dir) break;
|
|
1595
|
-
dir = parent;
|
|
1596
|
-
}
|
|
1597
|
-
return null;
|
|
1985
|
+
// src/router/generate.ts
|
|
1986
|
+
function normalizePath(p) {
|
|
1987
|
+
return p.replace(/\\/g, "/");
|
|
1598
1988
|
}
|
|
1599
|
-
function
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
if (!("name" in value)) return false;
|
|
1603
|
-
return typeof value.name === "string";
|
|
1989
|
+
function safeVarName(segment, prefix) {
|
|
1990
|
+
const safe = segment.replace(/[^a-zA-Z0-9]/g, "_") || "root";
|
|
1991
|
+
return `${prefix}_${safe}`;
|
|
1604
1992
|
}
|
|
1605
|
-
function
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
for (const entry of value) {
|
|
1609
|
-
if (!isValidPlugin(entry)) return null;
|
|
1610
|
-
result.push(entry);
|
|
1611
|
-
}
|
|
1612
|
-
return result.length > 0 ? result : null;
|
|
1613
|
-
}
|
|
1614
|
-
if (isValidPlugin(value)) return [value];
|
|
1615
|
-
return null;
|
|
1993
|
+
function buildRoutePath(parents, segment) {
|
|
1994
|
+
const joined = [...parents, segment].filter(Boolean).join("/");
|
|
1995
|
+
return "/" + joined;
|
|
1616
1996
|
}
|
|
1617
|
-
|
|
1618
|
-
if (
|
|
1619
|
-
|
|
1620
|
-
if (!uiDetect.installed) return [];
|
|
1621
|
-
if (opts?.consumerTailwindConfig || opts?.consumerPostcssConfig) {
|
|
1622
|
-
console.info(
|
|
1623
|
-
`[theokit] Detected your tailwind.config / postcss.config \u2014 skipping auto-config. Extend with \`import preset from '@theokit/ui/preset'\` to apply UI theme.`
|
|
1624
|
-
);
|
|
1625
|
-
return [];
|
|
1626
|
-
}
|
|
1627
|
-
const tailwindDetect = detectPackage("@tailwindcss/vite", cwd);
|
|
1628
|
-
if (!tailwindDetect.installed) {
|
|
1629
|
-
console.warn(
|
|
1630
|
-
`[theokit] @theokit/ui detected but @tailwindcss/vite is not installed. Run \`pnpm add -D @tailwindcss/vite\` to enable styling.`
|
|
1631
|
-
);
|
|
1632
|
-
return [];
|
|
1633
|
-
}
|
|
1634
|
-
const tailwindUrl = resolveConsumerImport("@tailwindcss/vite", cwd);
|
|
1635
|
-
if (!tailwindUrl) {
|
|
1636
|
-
console.warn(
|
|
1637
|
-
`[theokit] @tailwindcss/vite installed but its package entry was not resolvable from ${cwd}. Skipping auto-config.`
|
|
1638
|
-
);
|
|
1639
|
-
return [];
|
|
1640
|
-
}
|
|
1641
|
-
let tailwindMod;
|
|
1642
|
-
let uiMod;
|
|
1643
|
-
try {
|
|
1644
|
-
tailwindMod = await import(tailwindUrl);
|
|
1645
|
-
} catch (err) {
|
|
1646
|
-
console.warn(
|
|
1647
|
-
`[theokit] @tailwindcss/vite dynamic import failed (${err.message}). Skipping auto-config.`
|
|
1648
|
-
);
|
|
1649
|
-
return [];
|
|
1650
|
-
}
|
|
1651
|
-
const uiPkgInstalled = detectPackage("@theokit/ui", cwd);
|
|
1652
|
-
const uiPkgDir = uiPkgInstalled.resolvedPath ? uiPkgInstalled.resolvedPath.replace(/[\\/]package\.json$/, "") : null;
|
|
1653
|
-
const uiPluginPath = uiPkgDir ? join4(uiPkgDir, "dist", "vite-plugin.js") : null;
|
|
1654
|
-
if (!uiPluginPath || !existsSync5(uiPluginPath)) {
|
|
1655
|
-
console.warn(
|
|
1656
|
-
`[theokit] @theokit/ui/vite-plugin entry not found at ${String(uiPluginPath)}. Skipping auto-config.`
|
|
1657
|
-
);
|
|
1658
|
-
return [];
|
|
1659
|
-
}
|
|
1660
|
-
try {
|
|
1661
|
-
uiMod = await import(pathToFileURL2(uiPluginPath).href);
|
|
1662
|
-
} catch (err) {
|
|
1663
|
-
console.warn(
|
|
1664
|
-
`[theokit] @theokit/ui/vite-plugin dynamic import failed (${err.message}). Skipping auto-config.`
|
|
1665
|
-
);
|
|
1666
|
-
return [];
|
|
1667
|
-
}
|
|
1668
|
-
if (typeof tailwindMod.default !== "function") {
|
|
1669
|
-
console.warn(
|
|
1670
|
-
`[theokit] @tailwindcss/vite does not expose a default-export function. Skipping auto-config.`
|
|
1671
|
-
);
|
|
1672
|
-
return [];
|
|
1997
|
+
function pushIf(staticImports, filePath, varName) {
|
|
1998
|
+
if (filePath !== void 0) {
|
|
1999
|
+
staticImports.push({ varName, importPath: normalizePath(filePath) });
|
|
1673
2000
|
}
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
2001
|
+
}
|
|
2002
|
+
function walkRouteTree(node, parents, acc) {
|
|
2003
|
+
const seg = node.segment || "root";
|
|
2004
|
+
const routePath = buildRoutePath(parents, node.segment);
|
|
2005
|
+
if (node.page !== void 0) {
|
|
2006
|
+
acc.lazyPages.push({
|
|
2007
|
+
varName: safeVarName(seg, "Page"),
|
|
2008
|
+
importPath: normalizePath(node.page),
|
|
2009
|
+
routePath
|
|
2010
|
+
});
|
|
1679
2011
|
}
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
2012
|
+
if (node.layout !== void 0) acc.layoutState.found = true;
|
|
2013
|
+
pushIf(acc.staticImports, node.layout, safeVarName(seg, "Layout"));
|
|
2014
|
+
pushIf(acc.staticImports, node.error, safeVarName(seg, "Error"));
|
|
2015
|
+
pushIf(acc.staticImports, node.loading, safeVarName(seg, "Loading"));
|
|
2016
|
+
pushIf(acc.staticImports, node.notFound, safeVarName(seg, "NotFound"));
|
|
2017
|
+
for (const child of node.children) {
|
|
2018
|
+
const nextParents = node.segment ? [...parents, node.segment] : parents;
|
|
2019
|
+
walkRouteTree(child, nextParents, acc);
|
|
1687
2020
|
}
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
2021
|
+
}
|
|
2022
|
+
function generateRouteManifest(tree) {
|
|
2023
|
+
const acc = {
|
|
2024
|
+
staticImports: [],
|
|
2025
|
+
lazyPages: [],
|
|
2026
|
+
layoutState: { found: false }
|
|
2027
|
+
};
|
|
2028
|
+
walkRouteTree(tree, [], acc);
|
|
2029
|
+
const staticImports = acc.staticImports;
|
|
2030
|
+
const lazyPages = acc.lazyPages;
|
|
2031
|
+
const layoutState = acc.layoutState;
|
|
2032
|
+
const lines = [`import React, { Suspense } from 'react'`];
|
|
2033
|
+
if (layoutState.found) {
|
|
2034
|
+
lines.push(`import { Outlet } from 'react-router'`);
|
|
1695
2035
|
}
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
return [];
|
|
2036
|
+
lines.push("");
|
|
2037
|
+
for (const imp of staticImports) {
|
|
2038
|
+
lines.push(`import ${imp.varName} from '${imp.importPath}'`);
|
|
1700
2039
|
}
|
|
1701
|
-
const
|
|
1702
|
-
|
|
1703
|
-
console.warn(
|
|
1704
|
-
`[theokit] @theokit/ui/vite-plugin returned unexpected shape. Skipping auto-config.`
|
|
1705
|
-
);
|
|
1706
|
-
return [];
|
|
2040
|
+
for (const lp of lazyPages) {
|
|
2041
|
+
lines.push(`const ${lp.varName} = React.lazy(() => import('${lp.importPath}'))`);
|
|
1707
2042
|
}
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
2043
|
+
lines.push("");
|
|
2044
|
+
const preloadEntries = lazyPages.map(
|
|
2045
|
+
(lp) => ` '${lp.routePath}': () => import('${lp.importPath}'),`
|
|
2046
|
+
);
|
|
2047
|
+
lines.push("export const __theoPreloadMap = {");
|
|
2048
|
+
for (const e of preloadEntries) lines.push(e);
|
|
2049
|
+
lines.push("}", "");
|
|
2050
|
+
function buildChildrenArray(node, seg) {
|
|
2051
|
+
const childConfigs = [];
|
|
2052
|
+
if (node.page) {
|
|
2053
|
+
const pageVar = safeVarName(seg, "Page");
|
|
2054
|
+
const fallbackEl = node.loading ? `React.createElement(${safeVarName(seg, "Loading")})` : "null";
|
|
2055
|
+
const pageElement = `React.createElement(Suspense, { fallback: ${fallbackEl} }, React.createElement(${pageVar}))`;
|
|
2056
|
+
childConfigs.push(`{ index: true, element: ${pageElement} }`);
|
|
2057
|
+
}
|
|
2058
|
+
for (const child of node.children) {
|
|
2059
|
+
childConfigs.push(genRouteConfig(child, false));
|
|
2060
|
+
}
|
|
2061
|
+
if (node.notFound) {
|
|
2062
|
+
const nfVar = safeVarName(seg, "NotFound");
|
|
2063
|
+
childConfigs.push(`{ path: '*', element: React.createElement(${nfVar}) }`);
|
|
2064
|
+
}
|
|
2065
|
+
let arr = `[${childConfigs.join(", ")}]`;
|
|
2066
|
+
if (node.error) {
|
|
2067
|
+
const errVar = safeVarName(seg, "Error");
|
|
2068
|
+
arr = `[{ errorElement: React.createElement(${errVar}), children: ${arr} }]`;
|
|
2069
|
+
}
|
|
2070
|
+
return arr;
|
|
1717
2071
|
}
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
}
|
|
1729
|
-
function setupSsrDevMiddleware(server, opts) {
|
|
1730
|
-
server.middlewares.use((req, res, next) => {
|
|
1731
|
-
void (async () => {
|
|
1732
|
-
const url = req.url ?? "/";
|
|
1733
|
-
if (url.startsWith("/api/") || url.startsWith("/@") || url.startsWith("/node_modules/") || url.includes(".")) {
|
|
1734
|
-
next();
|
|
1735
|
-
return;
|
|
1736
|
-
}
|
|
1737
|
-
try {
|
|
1738
|
-
const indexPath = resolve4(opts.projectRoot, "index.html");
|
|
1739
|
-
let template = readFileSync4(indexPath, "utf-8");
|
|
1740
|
-
template = await server.transformIndexHtml(url, template);
|
|
1741
|
-
const nonce = generateNonce();
|
|
1742
|
-
applySecurityHeaders(
|
|
1743
|
-
res,
|
|
1744
|
-
opts.securityHeaders ?? {},
|
|
1745
|
-
{ production: process.env.NODE_ENV === "production" },
|
|
1746
|
-
{ nonce }
|
|
1747
|
-
);
|
|
1748
|
-
const mod = await server.ssrLoadModule(opts.virtualEntryServerId);
|
|
1749
|
-
const result = await mod.render(url, { nonce });
|
|
1750
|
-
if (result && typeof result === "object" && "redirect" in result) {
|
|
1751
|
-
res.writeHead(302, {
|
|
1752
|
-
Location: result.redirect.headers.get("location") ?? "/"
|
|
1753
|
-
});
|
|
1754
|
-
res.end();
|
|
1755
|
-
return;
|
|
1756
|
-
}
|
|
1757
|
-
let ssrHtml;
|
|
1758
|
-
let hydrationScript = "";
|
|
1759
|
-
if (typeof result === "string") {
|
|
1760
|
-
ssrHtml = result;
|
|
1761
|
-
} else if (isSsrRenderResult(result)) {
|
|
1762
|
-
ssrHtml = result.html;
|
|
1763
|
-
const dataJson = JSON.stringify(result.hydrationData).replace(/</g, "\\u003c");
|
|
1764
|
-
hydrationScript = `<script nonce="${nonce}">window.__staticRouterHydrationData=${dataJson}</script>`;
|
|
1765
|
-
} else {
|
|
1766
|
-
ssrHtml = "";
|
|
1767
|
-
}
|
|
1768
|
-
const rootDivMatch = /<div id=["']root["'][^>]*>/.exec(template);
|
|
1769
|
-
if (!rootDivMatch) {
|
|
1770
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
1771
|
-
res.end(template);
|
|
1772
|
-
return;
|
|
1773
|
-
}
|
|
1774
|
-
const splitIdx = template.indexOf(rootDivMatch[0]) + rootDivMatch[0].length;
|
|
1775
|
-
const html = template.slice(0, splitIdx) + ssrHtml + hydrationScript + template.slice(splitIdx);
|
|
1776
|
-
res.writeHead(200, { "Content-Type": "text/html" });
|
|
1777
|
-
res.end(html);
|
|
1778
|
-
} catch (err) {
|
|
1779
|
-
server.ssrFixStacktrace(err);
|
|
1780
|
-
console.error("[SSR Dev Error]", err);
|
|
1781
|
-
next();
|
|
1782
|
-
return;
|
|
2072
|
+
function genRouteConfig(node, isRoot) {
|
|
2073
|
+
const seg = node.segment || "root";
|
|
2074
|
+
const childrenArray = buildChildrenArray(node, seg);
|
|
2075
|
+
if (node.layout) {
|
|
2076
|
+
const layoutVar = safeVarName(seg, "Layout");
|
|
2077
|
+
const pathPart = isRoot ? `path: '/'` : `path: '${node.segment}'`;
|
|
2078
|
+
return `{ ${pathPart}, element: React.createElement(${layoutVar}, { children: React.createElement(Outlet) }), children: ${childrenArray} }`;
|
|
2079
|
+
}
|
|
2080
|
+
if (isRoot) {
|
|
2081
|
+
if (node.children.length === 0 && !node.page && !node.notFound && !node.error) {
|
|
2082
|
+
return `{ path: '/', children: [] }`;
|
|
1783
2083
|
}
|
|
1784
|
-
|
|
1785
|
-
|
|
2084
|
+
return `{ path: '/', children: ${childrenArray} }`;
|
|
2085
|
+
}
|
|
2086
|
+
if (node.page && node.children.length === 0 && !node.error && !node.notFound) {
|
|
2087
|
+
const pageVar = safeVarName(seg, "Page");
|
|
2088
|
+
const fallbackEl = node.loading ? `React.createElement(${safeVarName(seg, "Loading")})` : "null";
|
|
2089
|
+
const pageElement = `React.createElement(Suspense, { fallback: ${fallbackEl} }, React.createElement(${pageVar}))`;
|
|
2090
|
+
return `{ path: '${node.segment}', element: ${pageElement} }`;
|
|
2091
|
+
}
|
|
2092
|
+
return `{ path: '${node.segment}', children: ${childrenArray} }`;
|
|
2093
|
+
}
|
|
2094
|
+
const routeConfig = genRouteConfig(tree, true);
|
|
2095
|
+
lines.push(`export const routes = [${routeConfig}]`);
|
|
2096
|
+
return lines.join("\n");
|
|
1786
2097
|
}
|
|
1787
2098
|
|
|
1788
|
-
// src/vite-plugin/
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
if (
|
|
1793
|
-
|
|
1794
|
-
if (
|
|
1795
|
-
void
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
const wsPath = url.split("?")[0];
|
|
1802
|
-
const match = wsRoutes.find((r) => r.wsPath === wsPath);
|
|
1803
|
-
if (!match) {
|
|
1804
|
-
socket.destroy();
|
|
1805
|
-
return;
|
|
1806
|
-
}
|
|
1807
|
-
try {
|
|
1808
|
-
const mod = await server.ssrLoadModule(match.filePath);
|
|
1809
|
-
const handler = mod.default ?? mod;
|
|
1810
|
-
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
1811
|
-
void handler.onOpen?.(ws, request);
|
|
1812
|
-
ws.on("message", (data) => {
|
|
1813
|
-
void handler.onMessage?.(ws, data.toString());
|
|
1814
|
-
});
|
|
1815
|
-
ws.on("close", (code, reason) => {
|
|
1816
|
-
void handler.onClose?.(ws, code, reason);
|
|
1817
|
-
});
|
|
1818
|
-
ws.on("error", (err) => {
|
|
1819
|
-
void handler.onError?.(ws, err);
|
|
1820
|
-
});
|
|
1821
|
-
});
|
|
1822
|
-
} catch {
|
|
1823
|
-
socket.destroy();
|
|
1824
|
-
}
|
|
1825
|
-
})();
|
|
2099
|
+
// src/vite-plugin/virtual-modules-hook.ts
|
|
2100
|
+
function resolveVirtualModuleId(id, ids) {
|
|
2101
|
+
if (id === ids.VIRTUAL_ENTRY_ID) return ids.RESOLVED_ENTRY_ID;
|
|
2102
|
+
if (id === ids.VIRTUAL_MANIFEST_ID) return ids.RESOLVED_MANIFEST_ID;
|
|
2103
|
+
if (id === ids.VIRTUAL_ENTRY_SERVER_ID) return ids.RESOLVED_ENTRY_SERVER_ID;
|
|
2104
|
+
if (id === ids.VIRTUAL_RUNTIME_CONFIG_ID) return ids.RESOLVED_RUNTIME_CONFIG_ID;
|
|
2105
|
+
if (id === DEVTOOLS_VIRTUAL_ID) return DEVTOOLS_RESOLVED_ID;
|
|
2106
|
+
return void 0;
|
|
2107
|
+
}
|
|
2108
|
+
function loadVirtualModule(id, ctx) {
|
|
2109
|
+
if (id === ctx.ids.RESOLVED_ENTRY_ID) {
|
|
2110
|
+
return generateEntryClient(ctx.ssrEnabled, {
|
|
2111
|
+
theoUi: ctx.theoUi?.enabled ? { fonts: ctx.theoUi.config.fonts, theme: ctx.theoUi.config.theme } : void 0
|
|
1826
2112
|
});
|
|
1827
|
-
}
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
);
|
|
1831
|
-
|
|
2113
|
+
}
|
|
2114
|
+
if (id === ctx.ids.RESOLVED_MANIFEST_ID) {
|
|
2115
|
+
const tree = scanRoutes(ctx.appDir);
|
|
2116
|
+
broadcastRouteManifest(tree);
|
|
2117
|
+
return generateRouteManifest(tree);
|
|
2118
|
+
}
|
|
2119
|
+
if (id === ctx.ids.RESOLVED_ENTRY_SERVER_ID) {
|
|
2120
|
+
return generateEntryServer({
|
|
2121
|
+
streaming: ctx.streamingEnabled,
|
|
2122
|
+
theoUi: ctx.theoUi?.enabled ? { theme: ctx.theoUi.config.theme } : void 0
|
|
2123
|
+
});
|
|
2124
|
+
}
|
|
2125
|
+
if (id === ctx.ids.RESOLVED_RUNTIME_CONFIG_ID) {
|
|
2126
|
+
const tName = ctx.transformer?.name ?? "json";
|
|
2127
|
+
return [
|
|
2128
|
+
`// Generated by Theo Vite plugin`,
|
|
2129
|
+
`;(globalThis).__THEO_TRANSFORMER__ = ${JSON.stringify(tName)}`,
|
|
2130
|
+
`export const TRANSFORMER_NAME = ${JSON.stringify(tName)}`
|
|
2131
|
+
].join("\n");
|
|
2132
|
+
}
|
|
2133
|
+
if (id === DEVTOOLS_RESOLVED_ID) {
|
|
2134
|
+
return [
|
|
2135
|
+
`// Theo devtools \u2014 virtual entry (DEV only)`,
|
|
2136
|
+
`import('theokit/devtools/entry').catch((e) => console.error('[theo devtools] mount failed', e))`
|
|
2137
|
+
].join("\n");
|
|
2138
|
+
}
|
|
2139
|
+
return void 0;
|
|
1832
2140
|
}
|
|
1833
2141
|
|
|
1834
2142
|
// src/vite-plugin/integrations.ts
|
|
@@ -1941,8 +2249,8 @@ function findConsumerConfig(projectRoot, basename2) {
|
|
|
1941
2249
|
let dir = projectRoot;
|
|
1942
2250
|
for (let level = 0; level < 3; level++) {
|
|
1943
2251
|
for (const ext of extensions) {
|
|
1944
|
-
const candidate =
|
|
1945
|
-
if (
|
|
2252
|
+
const candidate = resolve9(dir, `${basename2}${ext}`);
|
|
2253
|
+
if (existsSync9(candidate)) return candidate;
|
|
1946
2254
|
}
|
|
1947
2255
|
const parent = dirname4(dir);
|
|
1948
2256
|
if (parent === dir) break;
|
|
@@ -1961,7 +2269,7 @@ async function theoPluginAsync(rootOrOptions) {
|
|
|
1961
2269
|
});
|
|
1962
2270
|
const servicesPlugins = [];
|
|
1963
2271
|
if (options.services && Object.keys(options.services).length > 0) {
|
|
1964
|
-
const { servicesTypedClientPlugin } = await import("./services-typed-client-
|
|
2272
|
+
const { servicesTypedClientPlugin } = await import("./services-typed-client-KK6RHRDG.js");
|
|
1965
2273
|
servicesPlugins.push(
|
|
1966
2274
|
servicesTypedClientPlugin({
|
|
1967
2275
|
cwd: projectRoot,
|
|
@@ -1969,20 +2277,20 @@ async function theoPluginAsync(rootOrOptions) {
|
|
|
1969
2277
|
})
|
|
1970
2278
|
);
|
|
1971
2279
|
}
|
|
1972
|
-
const { appTypedClientPlugin } = await import("./app-typed-client-
|
|
2280
|
+
const { appTypedClientPlugin } = await import("./app-typed-client-YOMWSA3F.js");
|
|
1973
2281
|
const appClientPlugin = appTypedClientPlugin({
|
|
1974
2282
|
cwd: projectRoot,
|
|
1975
|
-
serverDir:
|
|
1976
|
-
distDir:
|
|
2283
|
+
serverDir: resolve9(projectRoot, "server"),
|
|
2284
|
+
distDir: resolve9(projectRoot, ".theo")
|
|
1977
2285
|
});
|
|
1978
|
-
const { actionsVirtualModule } = await import("./actions-virtual-module-
|
|
2286
|
+
const { actionsVirtualModule } = await import("./actions-virtual-module-XNHDNCZ6.js");
|
|
1979
2287
|
const actionsPlugin = actionsVirtualModule({
|
|
1980
|
-
serverDir:
|
|
1981
|
-
distDir:
|
|
2288
|
+
serverDir: resolve9(projectRoot, "server"),
|
|
2289
|
+
distDir: resolve9(projectRoot, ".theo")
|
|
1982
2290
|
});
|
|
1983
2291
|
const { serverRoutesHmrPlugin } = await import("./server-routes-hmr-PBHSKCQJ.js");
|
|
1984
2292
|
const routesHmrPlugin = serverRoutesHmrPlugin({
|
|
1985
|
-
serverDir:
|
|
2293
|
+
serverDir: resolve9(projectRoot, "server")
|
|
1986
2294
|
});
|
|
1987
2295
|
return [
|
|
1988
2296
|
theoPlugin(rootOrOptions),
|
|
@@ -1995,10 +2303,10 @@ async function theoPluginAsync(rootOrOptions) {
|
|
|
1995
2303
|
}
|
|
1996
2304
|
function buildOptimizeDepsInclude(projectRoot, viteOptimizeDeps) {
|
|
1997
2305
|
const include = [];
|
|
1998
|
-
if (
|
|
2306
|
+
if (existsSync9(resolve9(projectRoot, "node_modules", "@theokit", "ui"))) {
|
|
1999
2307
|
include.push("@theokit/ui");
|
|
2000
2308
|
}
|
|
2001
|
-
if (
|
|
2309
|
+
if (existsSync9(resolve9(projectRoot, "node_modules", "lucide-react"))) {
|
|
2002
2310
|
include.push("lucide-react");
|
|
2003
2311
|
}
|
|
2004
2312
|
include.push("devalue");
|
|
@@ -2011,10 +2319,20 @@ function buildOptimizeDepsInclude(projectRoot, viteOptimizeDeps) {
|
|
|
2011
2319
|
}
|
|
2012
2320
|
return include;
|
|
2013
2321
|
}
|
|
2322
|
+
var VIRTUAL_MODULE_IDS = {
|
|
2323
|
+
VIRTUAL_ENTRY_ID,
|
|
2324
|
+
RESOLVED_ENTRY_ID,
|
|
2325
|
+
VIRTUAL_MANIFEST_ID,
|
|
2326
|
+
RESOLVED_MANIFEST_ID,
|
|
2327
|
+
VIRTUAL_ENTRY_SERVER_ID,
|
|
2328
|
+
RESOLVED_ENTRY_SERVER_ID,
|
|
2329
|
+
VIRTUAL_RUNTIME_CONFIG_ID,
|
|
2330
|
+
RESOLVED_RUNTIME_CONFIG_ID
|
|
2331
|
+
};
|
|
2014
2332
|
function theoPlugin(rootOrOptions) {
|
|
2015
2333
|
const options = typeof rootOrOptions === "string" ? { root: rootOrOptions } : rootOrOptions ?? {};
|
|
2016
2334
|
const projectRoot = options.root ?? process.cwd();
|
|
2017
|
-
const appDir =
|
|
2335
|
+
const appDir = resolve9(projectRoot, "app");
|
|
2018
2336
|
const ssrEnabled = options.ssr ?? false;
|
|
2019
2337
|
const currentDir = dirname4(fileURLToPath(import.meta.url));
|
|
2020
2338
|
const theoSrcDir = resolveTheoRootDir(currentDir);
|
|
@@ -2030,7 +2348,7 @@ function theoPlugin(rootOrOptions) {
|
|
|
2030
2348
|
let devtoolsEnabled = true;
|
|
2031
2349
|
let resolvedOpenApi;
|
|
2032
2350
|
let resolvedDistDir = ".theo";
|
|
2033
|
-
|
|
2351
|
+
const isDevModeRef = { value: false };
|
|
2034
2352
|
let configLoadedOnce = false;
|
|
2035
2353
|
return {
|
|
2036
2354
|
name: "theo",
|
|
@@ -2051,223 +2369,73 @@ function theoPlugin(rootOrOptions) {
|
|
|
2051
2369
|
resolvedOpenApi = resolved.openapi;
|
|
2052
2370
|
resolvedDistDir = resolved.distDir;
|
|
2053
2371
|
},
|
|
2054
|
-
//
|
|
2055
|
-
//
|
|
2056
|
-
// @tailwindcss/vite auto-chain lives in
|
|
2057
|
-
// Vite drops plugins returned from a
|
|
2372
|
+
// T2.6 (M6) — config() body extracted to config-hook.ts. The hook
|
|
2373
|
+
// owns optimizeDeps + warmup + services proxy + alias cascade.
|
|
2374
|
+
// The @theokit/ui + @tailwindcss/vite auto-chain lives in
|
|
2375
|
+
// `theoPluginAsync` because Vite drops plugins returned from a
|
|
2376
|
+
// child plugin's config() hook.
|
|
2058
2377
|
config() {
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
}
|
|
2066
|
-
const servicesProxy = options.services && Object.keys(options.services).length > 0 ? buildServicesProxyConfig(options.services) : void 0;
|
|
2067
|
-
return {
|
|
2068
|
-
envPrefix: "THEO_PUBLIC_",
|
|
2069
|
-
optimizeDeps: {
|
|
2070
|
-
...optimizeDepsInclude.length > 0 ? { include: optimizeDepsInclude } : {},
|
|
2071
|
-
force: true
|
|
2072
|
-
},
|
|
2073
|
-
server: {
|
|
2074
|
-
...warmupClientFiles.length > 0 ? { warmup: { clientFiles: warmupClientFiles } } : {},
|
|
2075
|
-
...servicesProxy !== void 0 ? { proxy: servicesProxy } : {},
|
|
2076
|
-
// Skip watching gitignored heavy dirs to avoid hitting ENOSPC
|
|
2077
|
-
// (inotify watcher exhaustion). `referencias/` are deep-research
|
|
2078
|
-
// clones; `.theokit/` is per-conversation cache; `.theo/` is
|
|
2079
|
-
// build output. None should trigger HMR.
|
|
2080
|
-
watch: {
|
|
2081
|
-
ignored: [
|
|
2082
|
-
"**/referencias/**",
|
|
2083
|
-
"**/.theokit/**",
|
|
2084
|
-
"**/.theo/**",
|
|
2085
|
-
"**/dist/**",
|
|
2086
|
-
"**/node_modules/**"
|
|
2087
|
-
]
|
|
2088
|
-
}
|
|
2089
|
-
},
|
|
2090
|
-
resolve: {
|
|
2091
|
-
alias: [
|
|
2092
|
-
// Order matters: most-specific first so `theokit/X` doesn't
|
|
2093
|
-
// get matched by the bare `theokit` alias.
|
|
2094
|
-
{ find: "theokit/server", replacement: resolve6(theoSrcDir, `server/index${ext}`) },
|
|
2095
|
-
{ find: "theokit/client", replacement: resolve6(theoSrcDir, `client/index${ext}`) },
|
|
2096
|
-
{
|
|
2097
|
-
find: "theokit/react-query",
|
|
2098
|
-
replacement: resolve6(theoSrcDir, `react-query/index${ext}`)
|
|
2099
|
-
},
|
|
2100
|
-
{
|
|
2101
|
-
find: "theokit/vite-plugin",
|
|
2102
|
-
replacement: resolve6(theoSrcDir, `vite-plugin/index${ext}`)
|
|
2103
|
-
},
|
|
2104
|
-
{
|
|
2105
|
-
find: "theokit/adapters/web-shim",
|
|
2106
|
-
replacement: resolve6(theoSrcDir, `adapters/web-shim${ext}`)
|
|
2107
|
-
},
|
|
2108
|
-
{
|
|
2109
|
-
find: "theokit/adapters/ws-shim",
|
|
2110
|
-
replacement: resolve6(theoSrcDir, `adapters/ws-shim${ext}`)
|
|
2111
|
-
},
|
|
2112
|
-
// T1.2 — devtools entry (DEV only; tree-shaken in build because
|
|
2113
|
-
// the script tag is never injected by inject-devtools.ts in build mode)
|
|
2114
|
-
{
|
|
2115
|
-
find: "theokit/devtools/entry",
|
|
2116
|
-
replacement: resolve6(theoSrcDir, `devtools/entry${ext === ".ts" ? ".tsx" : ".js"}`)
|
|
2117
|
-
},
|
|
2118
|
-
{ find: "theokit", replacement: resolve6(theoSrcDir, `index${ext}`) }
|
|
2119
|
-
]
|
|
2120
|
-
}
|
|
2121
|
-
};
|
|
2378
|
+
return runConfigHook({
|
|
2379
|
+
projectRoot,
|
|
2380
|
+
theoSrcDir,
|
|
2381
|
+
services: options.services,
|
|
2382
|
+
optimizeDepsInclude: buildOptimizeDepsInclude(projectRoot, options.viteOptimizeDeps)
|
|
2383
|
+
});
|
|
2122
2384
|
},
|
|
2123
|
-
//
|
|
2124
|
-
//
|
|
2125
|
-
//
|
|
2126
|
-
// Vite-injected content. Idempotent: if the user already wrote
|
|
2127
|
-
// the script tag, the HTML is returned unchanged.
|
|
2385
|
+
// T2.6 (M6) — transformIndexHtml hook extracted to transform-html-hook.ts.
|
|
2386
|
+
// Preserves the canonical 3-step order (entry-client → devtools → styles)
|
|
2387
|
+
// so EC-10 (Vite hook ordering side effects) is honored.
|
|
2128
2388
|
transformIndexHtml: {
|
|
2129
2389
|
order: "pre",
|
|
2130
2390
|
handler(html) {
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
isDev: isDevMode,
|
|
2136
|
-
enabled: devtoolsEnabled
|
|
2391
|
+
return runTransformIndexHtml(html, {
|
|
2392
|
+
isDevMode: isDevModeRef,
|
|
2393
|
+
devtoolsEnabled,
|
|
2394
|
+
projectRoot
|
|
2137
2395
|
});
|
|
2138
|
-
if (devtools.warning) console.warn(devtools.warning);
|
|
2139
|
-
next = devtools.html;
|
|
2140
|
-
const styles = injectStylesheets(next, {
|
|
2141
|
-
isDev: isDevMode,
|
|
2142
|
-
hasPackage: (name) => existsSync7(resolve6(projectRoot, "node_modules", ...name.split("/")))
|
|
2143
|
-
});
|
|
2144
|
-
next = styles.html;
|
|
2145
|
-
return next;
|
|
2146
2396
|
}
|
|
2147
2397
|
},
|
|
2398
|
+
// T2.6 (M6) — virtual-module resolveId + load dispatch extracted to
|
|
2399
|
+
// virtual-modules-hook.ts. Pure structural split; identity branches
|
|
2400
|
+
// preserved.
|
|
2148
2401
|
resolveId(id) {
|
|
2149
|
-
|
|
2150
|
-
if (id === VIRTUAL_MANIFEST_ID) return RESOLVED_MANIFEST_ID;
|
|
2151
|
-
if (id === VIRTUAL_ENTRY_SERVER_ID) return RESOLVED_ENTRY_SERVER_ID;
|
|
2152
|
-
if (id === VIRTUAL_RUNTIME_CONFIG_ID) return RESOLVED_RUNTIME_CONFIG_ID;
|
|
2153
|
-
if (id === DEVTOOLS_VIRTUAL_ID) return DEVTOOLS_RESOLVED_ID;
|
|
2402
|
+
return resolveVirtualModuleId(id, VIRTUAL_MODULE_IDS);
|
|
2154
2403
|
},
|
|
2155
2404
|
load(id) {
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
return generateRouteManifest(tree);
|
|
2165
|
-
}
|
|
2166
|
-
if (id === RESOLVED_ENTRY_SERVER_ID) {
|
|
2167
|
-
return generateEntryServer({
|
|
2168
|
-
streaming: options.ssrStreaming === true,
|
|
2169
|
-
theoUi: theoUi?.enabled ? { theme: theoUi.config.theme } : void 0
|
|
2170
|
-
});
|
|
2171
|
-
}
|
|
2172
|
-
if (id === RESOLVED_RUNTIME_CONFIG_ID) {
|
|
2173
|
-
const tName = transformer?.name ?? "json";
|
|
2174
|
-
return [
|
|
2175
|
-
`// Generated by Theo Vite plugin`,
|
|
2176
|
-
`;(globalThis).__THEO_TRANSFORMER__ = ${JSON.stringify(tName)}`,
|
|
2177
|
-
`export const TRANSFORMER_NAME = ${JSON.stringify(tName)}`
|
|
2178
|
-
].join("\n");
|
|
2179
|
-
}
|
|
2180
|
-
if (id === DEVTOOLS_RESOLVED_ID) {
|
|
2181
|
-
return [
|
|
2182
|
-
`// Theo devtools \u2014 virtual entry (DEV only)`,
|
|
2183
|
-
`import('theokit/devtools/entry').catch((e) => console.error('[theo devtools] mount failed', e))`
|
|
2184
|
-
].join("\n");
|
|
2185
|
-
}
|
|
2405
|
+
return loadVirtualModule(id, {
|
|
2406
|
+
ids: VIRTUAL_MODULE_IDS,
|
|
2407
|
+
appDir,
|
|
2408
|
+
ssrEnabled,
|
|
2409
|
+
streamingEnabled: options.ssrStreaming === true,
|
|
2410
|
+
theoUi,
|
|
2411
|
+
transformer
|
|
2412
|
+
});
|
|
2186
2413
|
},
|
|
2414
|
+
// T2.6 (M6) — configureServer body extracted to configure-server-hook.ts.
|
|
2415
|
+
// The hook owns middleware order, ws subscriptions, watcher handlers,
|
|
2416
|
+
// OpenAPI re-emit, and WS upgrade. EC-10 honored (every effect preserved
|
|
2417
|
+
// in its original ordering).
|
|
2187
2418
|
async configureServer(server) {
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
transformer,
|
|
2209
|
-
csrfMode,
|
|
2210
|
-
securityHeaders,
|
|
2211
|
-
disallowed,
|
|
2212
|
-
cors,
|
|
2213
|
-
auditLogger,
|
|
2214
|
-
servicesProxyPrefixes,
|
|
2215
|
-
csrfReadinessStore
|
|
2216
|
-
})
|
|
2217
|
-
);
|
|
2218
|
-
const configPath = resolve6(projectRoot, "theo.config.ts");
|
|
2219
|
-
server.watcher.on("change", (file) => {
|
|
2220
|
-
if (file === configPath) {
|
|
2221
|
-
console.warn(
|
|
2222
|
-
"\n[theokit] theo.config.ts changed; restart dev server for plugin changes to take effect\n"
|
|
2223
|
-
);
|
|
2224
|
-
}
|
|
2225
|
-
});
|
|
2226
|
-
if (ssrEnabled) {
|
|
2227
|
-
setupSsrDevMiddleware(server, {
|
|
2228
|
-
projectRoot,
|
|
2229
|
-
virtualEntryServerId: VIRTUAL_ENTRY_SERVER_ID,
|
|
2230
|
-
securityHeaders
|
|
2231
|
-
});
|
|
2232
|
-
}
|
|
2233
|
-
function handleRouteChange(filePath) {
|
|
2234
|
-
if (!isRouteFile(basename(filePath))) return;
|
|
2235
|
-
if (!filePath.startsWith(appDir)) return;
|
|
2236
|
-
const mod = server.moduleGraph.getModuleById(RESOLVED_MANIFEST_ID);
|
|
2237
|
-
if (mod) {
|
|
2238
|
-
server.moduleGraph.invalidateModule(mod);
|
|
2239
|
-
server.ws.send({ type: "full-reload" });
|
|
2240
|
-
}
|
|
2241
|
-
}
|
|
2242
|
-
server.watcher.on("add", handleRouteChange);
|
|
2243
|
-
server.watcher.on("unlink", handleRouteChange);
|
|
2244
|
-
if (resolvedOpenApi !== void 0) {
|
|
2245
|
-
const openApiCfg = resolvedOpenApi;
|
|
2246
|
-
const { reEmitOpenApi } = await import("./dev-emit-RBSFZZNO.js");
|
|
2247
|
-
const openApiServerDir = resolve6(projectRoot, "server");
|
|
2248
|
-
const openApiDistDir = resolve6(projectRoot, resolvedDistDir);
|
|
2249
|
-
const isRouteFileForOpenApi = (file) => file.startsWith(openApiServerDir) && /\.(ts|tsx|js|mjs)$/.test(file);
|
|
2250
|
-
void reEmitOpenApi(openApiServerDir, openApiDistDir, openApiCfg);
|
|
2251
|
-
server.watcher.on("change", (file) => {
|
|
2252
|
-
if (isRouteFileForOpenApi(file)) {
|
|
2253
|
-
void reEmitOpenApi(openApiServerDir, openApiDistDir, openApiCfg);
|
|
2254
|
-
}
|
|
2255
|
-
});
|
|
2256
|
-
server.watcher.on("add", (file) => {
|
|
2257
|
-
if (isRouteFileForOpenApi(file)) {
|
|
2258
|
-
void reEmitOpenApi(openApiServerDir, openApiDistDir, openApiCfg);
|
|
2259
|
-
}
|
|
2260
|
-
});
|
|
2261
|
-
server.watcher.on("unlink", (file) => {
|
|
2262
|
-
if (isRouteFileForOpenApi(file)) {
|
|
2263
|
-
void reEmitOpenApi(openApiServerDir, openApiDistDir, openApiCfg);
|
|
2264
|
-
}
|
|
2265
|
-
});
|
|
2266
|
-
}
|
|
2267
|
-
setupWsUpgrade(server, projectRoot);
|
|
2268
|
-
server.httpServer?.once("close", () => {
|
|
2269
|
-
;
|
|
2270
|
-
globalThis.__theoViteHotServer = void 0;
|
|
2419
|
+
await runConfigureServer(server, {
|
|
2420
|
+
projectRoot,
|
|
2421
|
+
appDir,
|
|
2422
|
+
resolvedDistDir,
|
|
2423
|
+
ssrEnabled,
|
|
2424
|
+
isDevMode: isDevModeRef,
|
|
2425
|
+
pluginRunner,
|
|
2426
|
+
transformer,
|
|
2427
|
+
resolvedBatching,
|
|
2428
|
+
csrfMode,
|
|
2429
|
+
securityHeaders,
|
|
2430
|
+
disallowed,
|
|
2431
|
+
cors,
|
|
2432
|
+
auditLogger,
|
|
2433
|
+
resolvedOpenApi,
|
|
2434
|
+
services: options.services,
|
|
2435
|
+
rateLimit: options.rateLimit,
|
|
2436
|
+
csrfReadinessStoreOverride: options.csrfReadinessStore,
|
|
2437
|
+
virtualEntryServerId: VIRTUAL_ENTRY_SERVER_ID,
|
|
2438
|
+
resolvedManifestId: RESOLVED_MANIFEST_ID
|
|
2271
2439
|
});
|
|
2272
2440
|
}
|
|
2273
2441
|
};
|
|
@@ -2278,10 +2446,10 @@ export {
|
|
|
2278
2446
|
theoConfigSchema,
|
|
2279
2447
|
deepMerge,
|
|
2280
2448
|
loadConfig,
|
|
2281
|
-
generateEntryClient,
|
|
2282
|
-
generateRouteManifest,
|
|
2283
2449
|
isRouteFile,
|
|
2284
2450
|
scanRoutes,
|
|
2451
|
+
generateEntryClient,
|
|
2452
|
+
generateRouteManifest,
|
|
2285
2453
|
defineTheoIntegration,
|
|
2286
2454
|
IntegrationVirtualModulePrefixError,
|
|
2287
2455
|
IntegrationRouteCollisionError,
|
|
@@ -2289,4 +2457,4 @@ export {
|
|
|
2289
2457
|
theoPluginAsync,
|
|
2290
2458
|
theoPlugin
|
|
2291
2459
|
};
|
|
2292
|
-
//# sourceMappingURL=chunk-
|
|
2460
|
+
//# sourceMappingURL=chunk-2F4FROEQ.js.map
|